siddhm11 Claude Opus 5.5 commited on
Commit
6edee5d
·
1 Parent(s): f23a740

perf(feed): fetch candidate vectors and details side by side

Browse files

The multi-interest build fetched its candidates' vectors (Qdrant, in
Europe) and then their details (the sidecar, else Turso, in Mumbai), one
after the other, though neither needs the other. They now run together
and the build waits for the slower one. The feed is unchanged: a test
checks the same ranked list and tags whichever fetch finishes first.

Measured 2026-10-23 from a laptop in India against the live services,
12 saves in 3 interests, four fresh processes each: vectors about
0.85 s, details 0.2-0.47 s, build 1.9-2.2 s either way; side by side
the vector fetch slowed under contention, so the saving there was small
and noisy. The Space is the case that matters: its own probe takes
978 ms for SELECT 1 to Turso (112 ms from the laptop), so there the
details are likely the long half. feed_tier1 now reports both halves
(cand_vectors_ms, cand_details_ms) next to their wall time
(candidate_meta_ms), so the saving can be read off production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

app/routers/recommendations.py CHANGED
@@ -948,12 +948,24 @@ async def _multi_interest_recommend(
948
  # get_paper_vectors is now LRU-cached by arxiv_id (qdrant_svc),
949
  # so warm cache makes this cheap; only fresh papers pay the
950
  # disk-read cost.
 
 
 
 
 
951
  t0_cand_meta = time.time()
952
- cand_vectors = await qdrant_svc.get_paper_vectors(candidate_ids)
953
- timing["cand_vectors_ms"] = int((time.time() - t0_cand_meta) * 1000)
954
- t0_details = time.time()
955
- cand_meta = await turso_svc.fetch_metadata_batch(candidate_ids)
956
- timing["cand_details_ms"] = int((time.time() - t0_details) * 1000)
 
 
 
 
 
 
 
957
  cand_missing = [cid for cid in candidate_ids if cid not in cand_meta]
958
  if cand_missing:
959
  try:
 
948
  # get_paper_vectors is now LRU-cached by arxiv_id (qdrant_svc),
949
  # so warm cache makes this cheap; only fresh papers pay the
950
  # disk-read cost.
951
+ #
952
+ # The two fetches need nothing from each other (vectors from Qdrant,
953
+ # details from the sidecar or Turso), so they run side by side and the
954
+ # build waits for the slower one, not both. Measured 2026-10-23 against
955
+ # the live services, 12 saves in 3 interests: 0.85 s and 0.2-0.47 s.
956
  t0_cand_meta = time.time()
957
+
958
+ async def _timed(key: str, coro):
959
+ t0 = time.time()
960
+ try:
961
+ return await coro
962
+ finally:
963
+ timing[key] = int((time.time() - t0) * 1000)
964
+
965
+ cand_vectors, cand_meta = await asyncio.gather(
966
+ _timed("cand_vectors_ms", qdrant_svc.get_paper_vectors(candidate_ids)),
967
+ _timed("cand_details_ms", turso_svc.fetch_metadata_batch(candidate_ids)),
968
+ )
969
  cand_missing = [cid for cid in candidate_ids if cid not in cand_meta]
970
  if cand_missing:
971
  try:
tests/test_feed_build_timing.py CHANGED
@@ -99,3 +99,20 @@ async def test_a_served_page_records_what_the_reader_waited_for(monkeypatch):
99
  stats = runtime_stats.timings()
100
  assert {"page_ms", "log_ms", "cards_ms", "request_ms"} <= stats["feed_first_page"].keys()
101
  assert "request_ms" in stats["feed_next_page"] and "total_ms" in stats["feed_tier1"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  stats = runtime_stats.timings()
100
  assert {"page_ms", "log_ms", "cards_ms", "request_ms"} <= stats["feed_first_page"].keys()
101
  assert "request_ms" in stats["feed_next_page"] and "total_ms" in stats["feed_tier1"]
102
+
103
+
104
+ async def test_candidate_vectors_and_details_are_fetched_side_by_side(monkeypatch):
105
+ install(monkeypatch, vectors_wait=0.3, details_wait=0.3)
106
+ state = await reader()
107
+ *_, timing = await recs._multi_interest_recommend(READER, state, set(), 60, query_id="q")
108
+ assert timing["cand_vectors_ms"] >= 300 and timing["cand_details_ms"] >= 300
109
+ assert timing["candidate_meta_ms"] < 500 # one wait, not two
110
+
111
+
112
+ async def test_which_fetch_finishes_first_does_not_change_the_feed(monkeypatch):
113
+ install(monkeypatch, vectors_wait=0.15)
114
+ state = await reader()
115
+ vectors_last = await recs._multi_interest_recommend(READER, state, set(), 60, query_id="q")
116
+ install(monkeypatch, details_wait=0.15)
117
+ details_last = await recs._multi_interest_recommend(READER, state, set(), 60, query_id="q")
118
+ assert vectors_last[0] == details_last[0] and vectors_last[2] == details_last[2]