feat(table): stable partition transition on the rewrite commit path - #8978
Draft
LuQQiu wants to merge 1 commit into
Draft
feat(table): stable partition transition on the rewrite commit path#8978LuQQiu wants to merge 1 commit into
LuQQiu wants to merge 1 commit into
Conversation
A reordered rewrite commits through the ordinary Rewrite operation with a new in-memory `stable_partition` field, following the `frag_reuse_index` precedent exactly: nothing new enters the transaction file (other writers' conflict decisions only need the fragment sets in `groups`), and the payload travels as a manifest system index entry. - New system index `__lance_stable_partition` (lance-table system_index/stable_partition.rs + StablePartitionIndexDetails proto): an accumulating ledger of transitions, each recording the ordered source digests (the row map's addressing base), the ordered destination ids (a row map label indexes this list) and the row map file reference. Inline below 200KB, external `details.binpb` above, mirroring the fragment reuse entry. The entry's fragment bitmap is the union of transition source ids: provenance, deliberately keeping retired fragments. - Commit path: groups covered by the rewrite's reordered sources skip fragment-bitmap recalculation entirely, for every index kind - the bitmaps keep the retired source ids instead of following the rewrite, which is what makes reads degrade to correct scans rather than serve stale addresses. A group straddling reordered and order-preserving sources is rejected. Coverage-based index retention now exempts all system indices, not just the fragment reuse entry. - `build_stable_partition_rewrite` validates conservation against the row map's own counts before any transaction exists: per-destination physical rows equal label totals, the row map covers exactly the sources' physical rows, and labeled rows equal the sources' live rows. - Concurrent reordered rewrites cannot see each other through the transaction file (the field is not serialized), so the commit carries the entry version it appended onto and manifest build rejects a mismatch, forcing a rebuild-and-retry instead of silently dropping the concurrent transition. - Tests: an end-to-end reordered rewrite through apply_commit (entry installed and round-trips, scalar index bitmap keeps the retired sources, filtered queries degrade to correct scans, stale-base commit rejected), conservation rejections, group partitioning and the mixed group error, details proto round trip. 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.
Stack
What
Part 2 of reordered-rewrite support, stacked on #8972 (the first four commits are that PR; review the last commit here). This wires the row map format into the commit path: a reordered rewrite now commits through the ordinary
Operation::Rewrite, carrying a new stable partition transition that lands in the manifest as its own system index entry.Design
The transaction carries nothing new on disk
Operation::Rewritegains an in-memorystable_partitionfield following thefrag_reuse_indexprecedent exactly: it is dropped when the transaction serializes andNonewhen one is read back. Other writers' conflict decisions only need the fragment sets ingroups, which are exact either way — so a reader or writer that predates this feature sees a perfectly ordinary rewrite transaction, not an unknown field.The manifest carries a new system index entry
__lance_stable_partition(separate from__lance_frag_reuse, whose proto is untouched) accumulates one transition per reordered rewrite:sources— ordered fragment digests; their physical row counts are the row map's addressing base (prefix sums position each fragment's rows in the concatenated label sequence),destinations— ordered fragment ids; a row map label is an index into this list,{indices dir}/{row_map_id}/row_map.lance).Inline below 200KB, spilled to an external
details.binpbabove it, mirroring the fragment reuse entry. The entry's fragment bitmap is the union of all transition source ids — provenance, deliberately keeping retired fragments (IndexMetadata::fragment_bitmapdocuments that ids may no longer exist).What the commit path does differently for reordered groups
For an ordinary (compaction) group, manifest build swaps index fragment bitmaps from old to new fragments — legal only because row order is preserved. A reordered group redistributes rows, so its groups skip bitmap maintenance entirely, for every index kind: the bitmaps keep the retired source ids. That single choice is what makes existing readers degrade instead of lie — a bitmap of retired ids intersects the live fragments as empty, so the index is skipped for those rows and queries fall back to correct scans. A group straddling reordered and order-preserving sources is rejected.
Coverage-based index retention now exempts all system indices (previously only the fragment reuse entry), since their bitmaps are provenance and an empty intersection with live fragments does not mean the entry is obsolete.
Conservation is validated before the transaction exists
build_stable_partition_rewritechecks the rewrite against the counts the row map file itself carries — no extra IO:Concurrent reordered rewrites
Because the transaction field is never serialized, one reordered rewrite cannot detect another through the transaction file. Instead the commit records which entry version it appended onto (
base_entry_version), and manifest build rejects the splice when the manifest's entry no longer matches — the alternative would silently drop the concurrent transition. The rejected caller rebuilds against the latest entry and retries.Compatibility
Rewrite; nothing new to drop or misread.Tests
apply_commit: entry installed and round-trips (sources, destinations, row map ref), the scalar index bitmap keeps the retired source ids instead of following the rewrite, a filtered query on the now-uncovered column still answers correctly (degrade path), and a second rewrite built against a stale entry version is rejected with the concurrent-rewrite error.m, source rows vs row map rows.ordered_rewrite_groups: partitioning, no-op without a stable partition, and the mixed-group error.cargo test -p lance-table(382) and the new-p lancetests pass; clippy clean for the new code.Follow-ups (separate PRs)
RowIdRemapperseams; row map file lifecycle (cleanup integration).🤖 Generated with Claude Code