Skip to content

test(maintenance): pin pipeline-decode auto-engagement on both rebuild routes - #3496

Merged
Sinity merged 1 commit into
masterfrom
perf/pipeline-parse-behind-apply
Jul 31, 2026
Merged

test(maintenance): pin pipeline-decode auto-engagement on both rebuild routes#3496
Sinity merged 1 commit into
masterfrom
perf/pipeline-parse-behind-apply

Conversation

@Sinity

@Sinity Sinity commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

polylogue-2cuv asked for a bounded-memory producer/consumer pipeline overlapping rebuild parse decode with the writer's apply work, on both the daemon bulk-rebuild route and the offline CLI route. Re-verification against current master found this already shipped by PR #3478 (_ReplaySpillPrefetcher, "Lever A"), with lineage-order preservation from PR #3485 landed the same day. This PR does not add pipeline plumbing (none is missing) — it adds one regression test proving the pipeline auto-engages from the production entry point both routes share, plus an honest before/after measurement in the PR description below.

Problem

The bead's core measured claim, from a real 4h22m rebuild receipt: parse_s (census 1202s + spill_load 2830s) + apply_s (8601s) == total (12633s) exactly — zero overlap between decode and the single SQLite writer. spill_load (2830s, 22% of the pass) was documented as strictly SERIAL pickle.loads/reparse work interleaved with writer apply work. The bead asked for a producer/consumer queue so census+spill hide behind apply, on both daemon/bulk_rebuild.py and maintenance/rebuild_index.py.

Solution

Finding: already done. Both routes call the identical chain:

  • Offline: maintenance/rebuild_index.pyrebuild_index_from_source_sync
  • Daemon: daemon/bulk_rebuild.py::run_daemon_bulk_rebuild_passrebuild_index_from_source_sync (same function, scheduled through the daemon's write coordinator)

Both funnel into maintenance/replay.py::rebuild_index_from_sourcesources/revision_backfill.py::backfill_historical_revision_evidence, which since PR #3478 (66515459c/c6e275bca, "pipeline replay decode off the writer thread") runs a background _ReplaySpillPrefetcher thread that decodes upcoming replay cohorts' parsed sessions while the writer applies the current cohort, auto-engaging via pipeline_decode=None whenever parallel_threads_effective() (free-threaded build) and the pass has >= _PIPELINE_DECODE_MIN_COHORTS (8) cohorts. PR #3485 (bdffadc2d, same day) added _lineage_aware_replay_order, which both the prefetcher and the writer loop consume from the identical ordered_logical_keys sequence — lineage ordering survives pipelining by construction.

Since pipeline_decode auto-resolution lives inside the shared function, there is no separate per-route knob either the offline or daemon caller could have forgotten to wire — both inherit it for free.

What this PR adds: tests/unit/maintenance/test_rebuild_parse_apply_split.py::test_rebuild_index_from_source_sync_auto_engages_pipelined_decode — drives the real rebuild_index_from_source_sync entry point (not the lower-level backfill_historical_revision_evidence the existing unit tests already call directly) with a corpus of _PIPELINE_DECODE_MIN_COHORTS + 4 independent raws and RAM spill tiers shrunk to force every for_raw decode through the prefetcher-or-inline fork, then asserts stage_timings_s["spill_prefetch.consumed"] > 0. Anti-vacuity: dropping the pipeline_decode parameter anywhere in the rebuild_index_from_source_sync → ... → backfill_historical_revision_evidence threading chain makes this assertion fail with 0, not a wrong number.

What this PR does NOT add: no new pipeline plumbing. The remaining fully-serial stage is the up-front census classification pass — it structurally precedes the replay loop (cohort membership must be resolved before cohorts can be ordered/classified), and _ReplaySpillPrefetcher neither touches nor could touch it without a materially larger redesign (overlapping census-page N+1 with replay-page N's apply, across page boundaries). Left as a residual finding, not implemented here — see the honest measurement below for its current relative weight.

Before/after measurement

Ad hoc synthetic-corpus script (not committed; used tests/infra/rebuild_cost_model.py's Stratum/build_stratum_sample_corpus machinery against backfill_historical_revision_evidence directly, comparing pipeline_decode=False — the exact pre-#3478 serial path — against pipeline_decode=None/auto, the current production default), 160-raw corpus, ~900KB payloads, 20% chain fraction, 10% ambiguous fraction:

metric before (serial) after (auto pipeline)
spill_load 3.988s 0.063s
spill_prefetch.decode_concurrent (hidden behind apply) n/a 2.901s
fraction of original spill_load now overlapped 72.7%

Wall-clock deltas from this run are not reported as a speedup number: the host was at load average ~19-22 on 24 cores during measurement (multiple concurrent agent lanes per repo convention), which visibly perturbed unrelated stages (e.g. census, which pipeline_decode never touches, moved 5.04s → 7.89s between the two runs) — the wall-clock signal was too noisy to trust in isolation. The spill_load/decode_concurrent stage-timing shift is the reliable signal because it is a structural property of which code path ran, not a wall-clock race against host load.

Verification

python -m devtools test tests/unit/maintenance/test_rebuild_parse_apply_split.py
  -> 5 passed (includes the new test)
python -m devtools test tests/unit/sources/test_revision_backfill.py -k pipelined_decode
  -> 3 passed (pre-existing outcome-parity proofs for pipeline_decode, confirmed still green:
     test_pipelined_decode_matches_serial_archive_state[reparse-fallback-lane],
     test_pipelined_decode_matches_serial_archive_state[sqlite-spill-lane],
     test_pipelined_decode_respects_batched_replay_commits)
python -m devtools test tests/benchmarks/test_rebuild_cost_model.py -k "not full_population"
  -> 4 passed
python -m devtools verify --quick -> exit 0 (ran again on push via pre-push hook, exit 0)

Not run: tests/benchmarks/test_rebuild_cost_model.py::test_full_population_projection (opt-in, ~40 real rebuild passes, minutes) and the full non-integration suite (devtools verify --all) — this PR's surface is a single new test in an already-green file plus a PR-body-only measurement, not new production plumbing.

AC matrix (against polylogue-2cuv)

AC status
Bounded-memory producer/consumer pipeline overlapping parse decode with apply Already satisfied_ReplaySpillPrefetcher (PR #3478), pre-existing
Wired on both daemon bulk-rebuild route and offline route Already satisfied — both share rebuild_index_from_source_sync; this PR adds the regression test proving it from that shared entry point
Preserve lineage-aware ordering from #3485 Already satisfied — prefetcher consumes the same ordered_logical_keys the writer loop does
Outcome parity proven by a differential test (accepted_raw_ids/adoption identical) Already satisfied — pre-existing test_pipelined_decode_matches_serial_archive_state (byte-identical RevisionBackfillResult + full index content manifest, both RAM-miss decode lanes)
Honest before/after measurement on the rebuild-cost harness fixture Satisfied in this PR body (see above); wall-clock deltas explicitly caveated as unreliable under current host load rather than reported as a speedup claim
Census-stage overlap (the remaining ~9-16% pre-#3478 serial bucket not touched by Lever A) Not implemented / out of scope — would require overlapping census across page boundaries with the prior page's apply, a materially larger redesign than what this bead's "spill_load 2830s SERIAL" evidence targeted

Ref polylogue-2cuv

…d routes

Problem: polylogue-2cuv reported parse_s + apply_s == total_s exactly (zero
overlap) on the real full rebuild, with spill_load (2830s SERIAL
pickle.loads/reparse on the writer thread) as 22% of a 12633s pass. The bead
asked for a bounded-memory producer/consumer pipeline overlapping parse
decode with apply on both the daemon bulk-rebuild route and the offline
route.

Re-verification against current master found the structural fix already
shipped: PR #3478 added `_ReplaySpillPrefetcher` (Lever A), a background
thread that decodes upcoming replay cohorts' parsed sessions while the
single writer applies the current cohort, wired into
`backfill_historical_revision_evidence` via the `pipeline_decode` parameter
(auto-engages under `parallel_threads_effective()` once a pass has >=8
cohorts). PR #3485 (`_lineage_aware_replay_order`, landed same day) is
consumed by both the prefetcher and the writer loop from the identical
`ordered_logical_keys` sequence, so lineage ordering survives pipelining.
Both `polylogue/maintenance/rebuild_index.py` (offline CLI, `promote=True`)
and `polylogue/daemon/bulk_rebuild.py::run_daemon_bulk_rebuild_pass` (daemon
route) call the SAME `rebuild_index_from_source_sync` ->
`maintenance/replay.py::rebuild_index_from_source` ->
`backfill_historical_revision_evidence` chain, so the pipeline auto-engages
identically on both -- there is no separate per-route wiring to add.

Direct measurement (ad hoc synthetic corpus, not committed --
`spill_load` before=3.988s -> after=0.063s, `spill_prefetch.decode_concurrent`
(hidden behind apply)=2.901s, 72.7% of the corpus's spill_load moved off the
writer's critical path) confirms the mechanism is live, not merely present
in source. Existing tests already pin outcome parity: `test_pipelined_
decode_matches_serial_archive_state` in
`tests/unit/sources/test_revision_backfill.py` asserts byte-identical
`RevisionBackfillResult` and full index content manifest between
`pipeline_decode=False` and `pipeline_decode=True` runs over both the
sqlite-spill and reparse-fallback decode lanes.

What changed: added one new test proving the pipeline auto-engages from the
PRODUCTION entry point both routes share (`rebuild_index_from_source_sync`),
not only from the lower-level `backfill_historical_revision_evidence` calls
the existing unit tests already exercised directly. Anti-vacuity: the test
shrinks the spill's RAM cache tiers to force every `for_raw` to miss RAM,
sizes the corpus at `_PIPELINE_DECODE_MIN_COHORTS + 4` independent raws (each
its own logical cohort), and asserts `spill_prefetch.consumed > 0` in the
receipt's `stage_timings_s` -- a parameter dropped anywhere in the
`rebuild_index_from_source_sync` -> ... -> `backfill_historical_revision_
evidence` threading chain makes this assertion fail with 0, not a wrong
number.

What was NOT changed: no new pipeline plumbing -- #3478 already built it.
The remaining fully-serial stage is the up-front `census` classification
pass (still ahead of the replay loop by construction: cohort membership must
be known before the replay loop can order/classify cohorts), which
`_ReplaySpillPrefetcher` does not and structurally cannot touch -- pipelining
THAT would mean overlapping census-page N+1 with replay-page N's apply, a
materially larger redesign than the spill_load producer/consumer pipeline
this bead asked for. Left as a residual finding, not implemented here.

Verification:
  python -m devtools test tests/unit/maintenance/test_rebuild_parse_apply_split.py
    -> 5 passed
  python -m devtools test tests/unit/sources/test_revision_backfill.py -k pipelined_decode
    -> 3 passed (pre-existing equivalence proofs, confirmed still green)
  python -m devtools test tests/benchmarks/test_rebuild_cost_model.py -k "not full_population"
    -> 4 passed
  python -m devtools verify --quick -> exit 0

Ref polylogue-2cuv

Co-Authored-By: Claude <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a168a503-3a05-414c-8996-b1cd4ab0b506

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab9dcf and 4ffc848.

📒 Files selected for processing (1)
  • tests/unit/maintenance/test_rebuild_parse_apply_split.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity merged commit 99449ab into master Jul 31, 2026
3 checks passed
@Sinity
Sinity deleted the perf/pipeline-parse-behind-apply branch July 31, 2026 22:44
Sinity added a commit that referenced this pull request Jul 31, 2026
…, 37t.1, uegw, wofr (PRs #3488-#3496)

Co-Authored-By: Claude <noreply@anthropic.com>
Sinity added a commit that referenced this pull request Jul 31, 2026
Rebased perf/sharded-from-empty-rebuild onto origin/master (merged since
branch point: #3494 deadline_check, #3496 pipeline-decode pin, #3497
conversational-evidence gate, #3498 lineage fixture fix). Conflicts
resolved in rebuild_index.py by keeping both #3494's mid-replay deadline
checkpointing (non-sharded path) and this PR's shard dispatch, with
shard_count>1 + pass_deadline_seconds explicitly rejected (validator and
CLI) since the sharded path has no deadline_check seam yet.

Ref polylogue-pzxm

Co-Authored-By: Claude <noreply@anthropic.com>
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.

1 participant