Skip to content

feat(table): stable partition transition on the rewrite commit path - #8978

Draft
LuQQiu wants to merge 1 commit into
lance-format:lu/friv2from
LuQQiu:lu/friv2-transition
Draft

feat(table): stable partition transition on the rewrite commit path#8978
LuQQiu wants to merge 1 commit into
lance-format:lu/friv2from
LuQQiu:lu/friv2-transition

Conversation

@LuQQiu

@LuQQiu LuQQiu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Stack

PR
2 👉 #8978 — transition + commit path
1 #8972 — row map format (base of this 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::Rewrite gains an in-memory stable_partition field following the frag_reuse_index precedent exactly: it is dropped when the transaction serializes and None when one is read back. Other writers' conflict decisions only need the fragment sets in groups, 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,
  • the row map file reference ({indices dir}/{row_map_id}/row_map.lance).

Inline below 200KB, spilled to an external details.binpb above 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_bitmap documents 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_rewrite checks the rewrite against the counts the row map file itself carries — no extra IO:

  • every destination's physical rows equal its label total,
  • the row map covers exactly the sources' physical rows,
  • labeled (live) rows equal the sources' live rows (physical − deleted).

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

  • Old readers: the entry is an unrecognized system index name — carried through manifests losslessly, never opened, queries degrade to scans for rows whose only index coverage was through retired sources. Verified in the end-to-end test.
  • Old writers: the transaction file contains a plain Rewrite; nothing new to drop or misread.

Tests

  • End-to-end reordered rewrite through 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.
  • Conservation rejections: destination row count vs label total, destination count vs the row map's m, source rows vs row map rows.
  • ordered_rewrite_groups: partitioning, no-op without a stable partition, and the mixed-group error.
  • Details proto round trip.

cargo test -p lance-table (382) and the new -p lance tests pass; clippy clean for the new code.

Follow-ups (separate PRs)

  1. Read integration: coverage derivation and decode-time translation through the existing RowIdRemapper seams; row map file lifecycle (cleanup integration).
  2. Conflict handling: deletion-vector fold on rebase via the sweep translator; combining disjoint concurrent rewrites instead of rejecting them.

🤖 Generated with Claude Code

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>
@github-actions github-actions Bot added A-index Vector index, linalg, tokenizer A-format On-disk format: protos and format spec docs format-change A change to the format spec, which requires a vote. Remove if minor (e.g. fixing typo). enhancement New feature or request labels Sep 3, 2026
@LuQQiu
LuQQiu changed the base branch from main to lu/friv2 September 3, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-format On-disk format: protos and format spec docs A-index Vector index, linalg, tokenizer enhancement New feature or request format-change A change to the format spec, which requires a vote. Remove if minor (e.g. fixing typo).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant