compute: page out correction v2 chunks via column_pager#36946
Conversation
PR MaterializeInc#36577 moved correction v2 chunk storage from columnation to columnar (Column<(D, Timestamp, Diff)> with an Align(Vec<u64>) spill form) but did not wire the pager. Route chunks through mz_timely_util::column_pager so cold chunks spill out-of-core, sharing the process-wide TieredPolicy budget and swap/file backend already configured in compute_state. Chunks are born paged (RefCell<Option<PagedColumn>>) and materialize lazily through a OnceCell<Column> on first read, so index() still hands out Ref<'_> borrows. Cursors release their Rc<Chunk>s as they advance, so only the chunks under active merge fronts stay resident -- a merge streams an out-of-core working set rather than materializing it whole. ChainBuilder re-pages every minted chunk, keeping resting chains paged; try_unwrap only reuses never-materialized (still-paged) chunks. Cached len/first_time/ last_time keep bookkeeping and boundary time checks from paging chunks in. updates_before stays a non-destructive read (its updates remain in the buffer for persist feedback to cancel) but now collects the before-upper prefix into an owned Vec and returns it: Chunk is !Sync, so a borrowing iterator can no longer be Send across the writer's await. When the global pager is disabled (the default) chunks stay resident and behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f3da77d to
a5556e6
Compare
|
Reviewer note on
Why this is bounded rather than a leak:
Where it does cost resident memory:
Subtle accounting gap: a chunk paged in via Empirically this stays bounded: the swap/file/lz4 study (on Possible follow-ups if a profile ever shows it matters:
|
Motivation
CorrectionV2(#36577) stores its chunks as columnarColumns but keeps them resident.This wires those chunks through
mz_timely_util::column_pagerso cold chunks can spill out-of-core, the same seam the columnar merge batcher already uses.Part of CLU-65 (pager) / swap-cooperative data shapes.
Description
Each
Chunkis born paged (aPagedColumnfromglobal_pager()) and materializes lazily through aOnceLockon first read, so cursors keep handing out borrows unchanged.A chunk is released as the cursor advances past it, so only the chunks under an active merge front are resident — a merge streams an out-of-core working set instead of materializing it whole.
Cached
first_time/last_timelet the chain invariant andsplit_at_timeroute a resting chunk by its boundary times without paging it in.When the global pager is disabled (the default) chunks stay resident and behavior is unchanged; benchmarking confirms the abstraction is free when it does not spill.
Relationship to #36898
This is rebased on top of #36898 (bucketed
CorrectionV2), now merged.The two are structurally orthogonal — this works at the chunk level (chunk storage), #36898 at the chain/bucket level — and
OnceLock+MutexkeepChunkSync, so #36898's slice-proportional read path (emitted.iter()) is untouched.Verification
Covered by the existing in-module equivalence tests (V1 vs V2) plus added swap-backend round-trip tests; the full swap/file/lz4 benchmark study lives on
correction-v2-pager-eval(doc/developer/design/20260609_pager_swap_findings.md).🤖 Generated with Claude Code