UnloadChunk: bulk probe extraction for chunk batches#782
Open
DAlperin wants to merge 3 commits into
Open
Conversation
DAlperin
force-pushed
the
unload-chunk
branch
3 times, most recently
from
July 13, 2026 16:01
63324cc to
90a0811
Compare
antiguru
reviewed
Jul 13, 2026
UnloadChunk is the read surface for chunks whose bodies may not be resident: probe hits are copied into caller-owned staging, and no borrow of chunk contents crosses the trait boundary. Key/val/time/diff opinions stay off the trait (Staging and Probes are opaque); the one comparison the batch driver needs is delegated to the chunk via locate(), answered from resident metadata like bounds() on the cursor path. Staging, for the in-tree families, is ChunkBatchBuilder<Self>: extraction emits its hit runs as resident chunks and the builder settles as it goes, so staged data is graded, coalesced, and -- with a spiller installed -- paged under the same bounded-footprint discipline as any other chunk sequence. Extraction is thereby chunks-in, chunks-out, and its output is consumable by everything chunk-shaped. A run covering a whole resident chunk (and every fetch_into of one) stages as a handle push: a refcount bump, no copy. ChunkBatch gains a provided driver: gallop the chunk list by locate, open only the chunks a probe touches, and re-offer a probe left unconsumed at a chunk's last key to the next chunk (the consume-index protocol); its continuation lands in staging as a legal straddle, the same invariant chunk sequences carry everywhere else. fetch_into is the scan path. VecChunk is the worked implementation. Tests: a random-chain property test against the filter oracle, exhaustive (chunk cut x probe placement) boundary coverage including a key spanning several chunks, and a fetch round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… builder UpdatesBuilder gains meld_keys: meld a key-index range of a borrowed UpdatesView -- the keys with their complete val/time/diff subtrees -- by bulk column-range copies, with the same boundary-stitching semantics as meld (which now delegates to it over the full range). The view may sit over typed columns or directly over wire bytes, so one primitive serves resident and spilled sources. ColChunk's extraction gallops the probe column against the key column and melds maximal runs of consecutive hits into a per-source-chunk carry (one staged chunk per chunk touched); a run covering the whole resident chunk stages the handle instead, no copy. On the paged arm a fetch is scoped to the call and reads a zero-copy view over the loaded bytes (spill::read) -- no trie rebuild, and the OnceCell cache stays unpopulated, unlike the cursor path's fetch-and-cache-forever. locate answers from resident metadata (ChunkMeta when paged). Tests: a filter-oracle property test over random chains for resident and paged chunks (asserting caches stay empty), a straddle fixture, a paged extraction + scan test with spill stats, and a meld_keys property test against filtered form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A read-side section for the UnloadChunk path: both paths consume the probed updates of a columnar ChunkBatch into owned staging -- the straddle cursor by per-probe seeks and owned copies, extraction by one extract_into into chunk staging. Shapes: dense String-keyed probes (the design doc's headline case), sparse (1%) u64 probes, dense u64 probes, and fat 4x256B values; the first two also run against paged batches read cold, where the cursor path decodes-and-caches and extraction views the spilled bytes. At 1M updates on an M-series laptop: dense String probes 201ms cursor vs 1.8ms extraction (the whole-chunk handle fast path; the design doc's 21ms-class admission, cleared with two orders of headroom), sparse probes at 1.2x over cursor parity, fat values staged as handle pushes. Paged cold reads: dense 23x, sparse 4.3x. Co-Authored-By: Claude Fable 5 <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.
UnloadChunkis a second way to read a chunk, alongsideNavigableChunk: given a sorted set of probe keys, copy the matching updates out into caller-owned staging. A cursor navigates a chunk in place and hands out references into it —Cursor::key<'a>(&self, storage: &'a Storage) -> Key<'a>lets the caller hold borrows for the batch's lifetime — so the only sound way to read a paged chunk under it isColChunk's fetch-and-cache-foreverOnceCell, and a probe-heavy consumer progressively re-materializes the trace it probes. Extraction is finished with a chunk's body when the call returns: any fetch a paged implementor needs is scoped inside one method, the cache stays empty, and probing a paged trace does not accrete it back into memory.The trait keeps
Chunk's discipline: key/val/time/diff opinions stay off it (StagingandProbesare opaque associated types), and the one comparison the batch driver needs is delegated to the chunk vialocate, answered from resident metadata likelen. Chunk boundaries are carried as a protocol rather than an index: a chunk consumes every probe strictly below its last key and extracts-but-does-not-consume a probe equal to it, so the driver re-offers that probe to the next chunk and the continuation lands in staging as a legal straddle — the same invariant chunk sequences carry everywhere else. Times are copied verbatim; since-advancement stays with the consumer, exactly as on the cursor path.For the in-tree families,
StagingisChunkBatchBuilder<Self>: extraction emits its hit runs as resident chunks and the builder settles as it goes, so staged data is graded, coalesced, and — when a spiller is installed — paged under the same bounded-footprint discipline as any other chunk sequence. Extraction is thereby chunks-in, chunks-out, its results consumable by everything chunk-shaped (including, sincedoneyields aChunkBatch, the existing cursors — soundly, over storage the consumer owns). A probe run covering a whole resident chunk, and everyfetch_intoof one, stages as a handle push: a refcount bump, no copy.Contents:
trace/chunk: theUnloadChunktrait, a providedChunkBatchdriver (gallop the chunk list bylocate, open only the chunks a probe touches,fetch_intoas the scan path), and theVecChunkimplementation.columnar:UpdatesBuilder::meld_keys, melding a key-index range of a borrowedUpdatesViewby bulk column-range copies (meldnow delegates to it).ColChunk's extraction melds each source chunk's partial hit runs into one staged chunk through it; because it works on views, one primitive serves typed tries and wire bytes, and the paged arm reads a zero-copy view over the loaded bytes (spill::read) for the scope of the call — no trie rebuild, no cache population.meld_keysproperty test against filteredform; paged extraction and scan round-trips with spill stats.chunk_bench: extraction vs the straddle cursor, both consuming into owned staging. At 1M updates: dense String probes 201ms cursor vs 1.8ms extraction (the whole-chunk handle path; the design doc's 21ms-class admission with two orders of headroom), sparse (1%) probes at 1.2x over cursor parity, fat 4x256B values staged as handle pushes. Paged cold reads: dense 23x, sparse 4.3x — where the cursor path must decode-and-cache while extraction views the spilled bytes.This is the counterpart of #781 one layer down. That PR's backend presentations (
present0,present_input) have exactly this contract — sorted probe keys in, an owned restricted run out, nothing borrowed across the boundary — and leave their implementation to the backend;extract_intoandmeld_keysare the machinery a chunked, spillable backend would implement them with. Both sit on the same two seams: tactics as the consumption dispatch point (#773, public since #789–#791), and cursor-less chunks as full trace citizens (#778). Rebased over all of the above.🤖 Generated with Claude Code