Skip to content

perf(probe): build the Library Probe payload off the event loop (#626) - #654

Merged
coaxk merged 1 commit into
mainfrom
perf/626-probe-library-off-loop
Nov 1, 2026
Merged

coaxk merged 1 commit into
mainfrom
perf/626-probe-library-off-loop

Conversation

@coaxk

@coaxk coaxk commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Closes #626.

What the measurement showed

Step 1 of #626, measured with a synthetic media_probe (2 audio + 3 subtitle streams per row), Python 3.12, --cpus 2 (the maintainer instance's limit), loop_lag at its default 0.5 s threshold:

Coverage refresh (build_coverage, 3 builds per size)

rows all_entries() in the build longest loop gap loop_lag warnings
6,375 106-183 ms 16-71 ms 0
40,000 991-1,240 ms 142-298 ms 0

The hydration already runs in asyncio.to_thread, and CPython hands the GIL back every 5 ms, so the loop is slowed, not frozen. The issue's acceptance (no loop_lag warning from a coverage refresh at 40k) already holds on main, so no caching, lazy hydration or chunked decode is added there: each would add memory bounds or invalidation logic to remove a gap that is under the threshold.

GET /api/probe/library (the Library Probe tab) called all_entries() directly on the event loop:

rows loop blocked per call loop_lag warnings
6,375 107-183 ms 0
40,000 1,038-1,484 ms 3 of 3 calls before, 0 of 3 after

The warning's stack named routers/probe.py library -> probe_store.py all_entries.

Change

  • The route's body (hydration plus per-row classification and filtering) moves into _library_payload and runs in asyncio.to_thread. Response shape and every filter are unchanged.

Tests

  • tests/test_probe_library_off_loop_626.py: all_entries() and the per-row classification must never run on the loop. Fails on main; a mutant that moves only the hydration off-loop fails the classification assert.
  • Same file pins limit vs total_cached, only_without_eng (partial English counts as English), combined filter_text + sub_kind, and the empty store. These had no test before; they pass on main and after.
  • tests/test_library_probe.py unchanged and green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ShzWm3urD55AUk5yrSgXsi

GET /api/probe/library hydrated every media_probe row on the loop and
blocked it for 1.1-1.5 s per call at 40k rows (loop_lag warned on 3 of 3
calls; 0 of 3 after). The coverage refresh already hydrates in a worker
thread and stays under the 0.5 s threshold at 40k, so it is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ShzWm3urD55AUk5yrSgXsi
@coaxk
coaxk merged commit a9d9ff3 into main Nov 1, 2026
19 checks passed
@coaxk
coaxk deleted the perf/626-probe-library-off-loop branch November 1, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Coverage refresh hydrates the whole media_probe table under the GIL (same pattern as #621)

1 participant