feat(mem-wal): derive index catch-up from the version a commit read - #8458
Draft
hamersaw wants to merge 2 commits into
Draft
feat(mem-wal): derive index catch-up from the version a commit read#8458hamersaw wants to merge 2 commits into
hamersaw wants to merge 2 commits into
Conversation
An index absent from `index_catchup` reads as fully caught up, so the WAL may retire an SSTable whose rows no index holds. Nothing wrote the field, so every table read that way. Record it at commit time instead. An index covering every fragment that was live at the transaction's read version holds every row compaction had copied in by then, so it is caught up to the progress recorded at that version. That is the only proof available: nothing maps a generation to the fragments its rows landed in, so covering the whole table as the transaction read it is how an index shows it covered those rows. Both halves come from state the commit already has -- the read version's fragments and compaction progress, and the index list being published -- so nothing is asserted by the caller and no new field is transmitted. Deriving it at commit time alone does not work: by then fragments may have been appended and compaction advanced into them, and a scalar watermark cannot stand in for the fragment set, because a rewrite moves rows to higher ids and empties the range it was meant to fence. Every index is named explicitly, at generation 0 where coverage is unproven, since absence would otherwise read as caught up. An index that changes without proving coverage again loses its position; one that is physically unchanged carries it forward, and a position is never lowered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Important This PR touches the Lance format specification. Substantive changes to the format specification — the If this is a meaningful format change:
|
Building a manifest needs to know what the transaction saw, not only what it commits onto. That was threaded through as a MemWAL-specific bundle, which put the system index's layout in the commit path: `commit.rs` had to know the index name and how to decode its details just to hand the result back. Pass the read version's manifest and index list instead, and let `build_manifest` take what it needs from them. The signature is now symmetric with `current_manifest` / `current_indices`, MemWAL parsing sits next to the code that acts on it, and anything else that later needs the read version has it. The coverage rule is unchanged, and still takes the two values it compares so it stays testable without a manifest. Co-Authored-By: Claude Opus 5 (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.
What
An index absent from
index_catchupreads as fully caught up, so the WAL may retire an SSTable whose rows no index holds. Nothing ever wrote the field, so every table reads that way today.This records it at commit time, derived from state the commit already has.
How
An index covering every fragment that was live at the transaction's read version holds every row compaction had copied into the base table by then, so it is caught up to the
compacted_sstablesprogress at that version.That is the only proof available: nothing maps a compaction generation to the fragments its rows landed in, so covering the whole table as the transaction read it is how an index shows it covered those rows. Fragments appended since are a later catch-up gap.
Both halves come from state the commit already holds:
__lance_mem_waldetailsread_versionis already on every transaction andcommit_transactionalready materializes that version, so no claim is passed and no message changes.build_manifestgains one parameter,Option<ReadVersionState>— the read version's manifest and index list, symmetric with the existingcurrent_manifest/current_indices. It isNoneonly where there is no version to read: dataset creation and detached commits. Deriving MemWAL's view from it happens insidebuild_manifest, so the commit path stays generic —commit.rsneeds no knowledge of the system index's name or layout, and anything else that later needs the read version has it.Per logical index:
compacted_sstables@V, capped by what the commit itself records as compacted (so a read version since rolled back cannot retire SSTables no live commit copied in)0Every index is named explicitly, including at
0, because a missing entry reads as "caught up". A position is never lowered, so a commit reading an older version does not withdraw coverage an index already demonstrated.Why not derive it purely at commit time
Two simpler variants were tried and are unsound or ineffective:
max_fragment_idwatermark per generation. Live fragments are not a prefix of the id space. Background compaction rewrites F1, F2 → F3, so{live : id ≤ watermark}shrinks or empties, and an empty required set is a subset of every index — including one holding none of those rows. It fails in the unsafe direction, and with the frag-reuse index deferring remapping (conflict_resolver.rs), it fails on the ordinary path rather than in a rare race.Anchoring to the read version avoids both: the index and the fragment set are captured in the same id-space, and a rewrite moves rows between fragments without destroying them, so the conclusion stays true afterward.
Scope
protos/transaction.protoprotos/table.protofeature_flags.rsOptimizeOptionsConsumers need no new API — read
index_catchupat trim time.Tests
12 unit tests in
dataset::transaction::tests::mem_wal_index_coverage: coverage recorded; capped by the read version; capped by the commit; explicit0when unproven; carry-forward for an unchanged index; dropped for a changed one; never lowering a recorded position; union across delta segments; segment without a fragment bitmap; no-op leaves the system index UUID untouched; never-compacted table records nothing; indices judged independently.cargo test -p lance --lib— 2854 passed, 0 failedcargo clippy -p lance --tests -- -D warnings— cleancargo fmt --all -- --check— cleanThose three were run against the first commit. The follow-up refactor moves types and parameters without touching the coverage rule; it is
cargo check --testsandcargo fmtclean, and relies on CI for the rest.Notes for review
is_index_caught_upreturnstrueunconditionally today because nothing writes the field. Indices that genuinely lag will now report0. That is the point of the change, but it will look like a storage regression the first time it happens.// TODO: this will change with stable row ids.on theRewritearm of the index conflict rules.UpdateMemWalStatepublishes no fragments). This design commits no standaloneUpdateMemWalState, so it does not make that latent bug reachable.🤖 Generated with Claude Code