Repository navigation
fix(auto-routing): resume scheduler run initialization without blocking dispatch - #7723
Conversation
…ng dispatch Dispatch materialized uninitialized runs before it claimed any chunk, and every pass restarted materialization from the run's first lane. A run too large to materialize within one Worker invocation never wrote its scheduler marker, and no pass ever reached the claim step for the other runs. - Claim for initialized runs first. Initialize afterwards under a dedicated lease row so concurrent dispatchers do not materialize the same run, and claim again when a pass completes a run's initialization. - Bound each run's materialization pass to 20 seconds. Every batch commits on its own; the next pass reads each lane's written prefix and resumes after it. The scheduler_runs marker still commits only with the final batch, and a run that stops running stops materializing. - Decide completed-case reuse per inserted chunk through case_results primary-key probes. The previous per-batch UPDATE planned through the (status, run_id) lane index on stats-less SQLite and visited every pending chunk of the run for each batch. - Add a real-SQLite regression test on production migrations: a 100k-chunk run initializes over bounded ticks while another run is claimed on every tick.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (10 files)
Verified: the per-lane written-prefix resume ( Reviewed by deepseek-v4.1-flash · Input: 120K · Output: 50.1K · Cached: 2M Review guidance: REVIEW.md from base branch |
Problem
dispatchSchedulerinitialized uninitialized decider runs before it claimed chunks, andscheduleRunChunksrestarted materialization from the run's first lane on every pass. A run large enough that materialization outlasted one Worker invocation hit the wall-time limit on every tick before it wrote thescheduler_runsmarker. No tick reached the claim step, so every other run stalled too.Each materialization batch also ran a completed-case reuse
UPDATE ... WHERE run_id = ? AND status = 'pending' AND id IN (...). On stats-less SQLite (production migrations, noANALYZE) that plans throughIX_scheduler_chunks_lane (status=? AND run_id=?), so every 50-chunk batch visited every pending chunk of the run.Change
scheduler_dispatch_lock, created on first use, no migration). Completion-driven dispatchers that find it held skip initialization instead of materializing the same run concurrently.scheduleRunChunkscall spends at mostSCHEDULER_INIT_BUDGET_MS(20s) per run. It returns whether the run is initialized. Every batch commits on its own. The next pass readsCOUNT(*)/MAX(chunk)per lane throughIX_scheduler_chunks_runand resumes after each lane's written prefix. A lane whose rows are not a prefix is rewritten from chunk 0 underON CONFLICT DO NOTHING.INSERTcomputescompletedorpendingfromcase_resultsprimary-key probes of that chunk's own cases. The per-batchUPDATEis gone.Why a lane-prefix skip instead of a stored cursor
The resume point comes from the rows themselves. It needs no migration, and it holds even if the entry order changes between passes or two initializers overlap. The cost is one index-backed aggregate per pass over the run's chunks. That is a single read, not a write per already-materialized batch.
Invariants kept
scheduler_runsmarker commits only with the final batch, after every lane is complete. Only marked runs are dispatched. Partially initialized runs are never claimed.runningstops materializing, both within a pass and on later ticks.completedbefore the marker. The existing trigger-guarded test still passes.Tests
src/scheduler-initialization.test.tsdrives the realdispatchScheduleragainst real SQLite with production migrations. Each D1 call advances a simulated clock by 250 ms, so a 50-chunk batch costs about 0.5 s. A tick past the 15-minute wall limit is killed mid-flight.The test fails on the branch base:
steady claims on tick 1: expected 0 to be greater than 0.Also added:
EXPLAIN QUERY PLANassertions: the chunk insert probessqlite_autoindex_case_results_1on all five key columns and touches noscheduler_chunksindex. The lane-progress read usesIX_scheduler_chunks_run.Verification
On a disposable gcloud VM (
services/auto-routing-benchmark):pnpm typecheckpasses.pnpm lint(oxlint) reports 0 warnings and 0 errors.pnpm testpasses: 41 files, 682 tests.pnpm smoke:schedulerpasses.No deploy, queue, or D1 changes are made by this PR.