feat(verify): stream suspects to disk via an external-sort sink (#156)#165
Merged
Conversation
Verify accumulated every suspect in a list before writing, so peak RSS scaled with the finding count — ~280K orbit outliers on the full corpus would all sit in RAM at once, breaking the constant-memory invariant. Add SuspectSink: an external merge-sort (spill each full chunk to a temp file, k-way-merge on write) plus a running Counter/hard/total tally, so peak memory is one chunk regardless of suspect count. run_verify and run_orbit_pass now stream into the sink instead of building lists. Output is byte-identical to the old list writer by construction: the serialization is extracted into one shared primitive (_suspect_line) used by both paths, summary.json stays sort_keys=True, summary.md sorts its tally, and heapq.merge's key-tie ordering (earlier run first, then the in-memory tail) reproduces the stable add-order that sorted(key=_sort_key) gave the list path. A new byte-equivalence test locks this against the retained list renderers across the empty, no-spill, and multi-spill paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CHANGELOG gains a Changed entry; ARCHITECTURE's verify/report row now describes the external-sort SuspectSink alongside the list renderers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stream verify suspects to disk via an external-sort sink (#156)
verifyaccumulated every suspect in a list before writing, so peak RSS scaled with the finding count — the last part of the verify path whose footprint grew with the corpus. Issue #156 measured this at ~4 GB under high suspect count.The change
SuspectSink(inverify/report.py) replaces the in-memory list with an external merge-sort — each full chunk is sorted and spilled to a temp file,writek-way-merges the runs, and a runningCounter/hard/totaltally feeds the summary.run_verifyandrun_orbit_passstream into it instead of building lists (orbit outliers were the main offender). Peak memory is now one chunk regardless of suspect count. Mirrors the existinggrouping.ExternalSorter.Byte-identity preserved by construction
suspects.jsonl/summary.{json,md}bytes are unchanged:_suspect_line) used by both the sink and the retained list renderers;summary.jsonstayssort_keys=True,summary.mdsorts its tally, so both are input-order-independent;heapq.merge's key-tie ordering (earlier run first, then the in-memory tail) reproduces the exact stable add-order thatsorted(key=_sort_key)gave the list path.tests/test_verify_stream.pylocks this:TestByteEquivalenceasserts the streamed output equals the list renderers across the empty / no-spill / multi-spill paths (including a tie-key pair), andTestConstantMemoryforces 10 spills atchunk_size=10and asserts the buffer never exceeds one chunk.Validation
ruff check+ruff format --checkclean.verify --orbitover the full cleaned corpus (232,281,894 records / ~30 GB) held peak RSS flat at 181 MB (dedup, already streamed, 144 MB). 88,041 suspects found, byte-deterministic output. (This run's suspect count sat under the 200K chunk, so the in-memory tail path was exercised; the spill/merge path is covered by the unit tests above — a--allorbit run would exercise spills on real data.)Closes #156.
🤖 Generated with Claude Code