Skip to content

perf: batch fragment-reuse index remap into a single rebuild + commit#7303

Closed
xuanyu-z wants to merge 2 commits into
lance-format:mainfrom
xuanyu-z:xuanyuzhan/opt-batch-frag-reuse-remap
Closed

perf: batch fragment-reuse index remap into a single rebuild + commit#7303
xuanyu-z wants to merge 2 commits into
lance-format:mainfrom
xuanyu-z:xuanyuzhan/opt-batch-frag-reuse-remap

Conversation

@xuanyu-z

Copy link
Copy Markdown
Contributor

🟡 DRAFT, stacked on #7286. This branch is based on #7286, so the diff shows that fix's commit plus the one new perf: commit here — review the perf: batch fragment-reuse index remap… commit. I'll rebase to drop the #7286 commit once it merges.

Summary

remap_index (the post-deferred-compaction catch-up that physically rewrites an index's stored row addresses) applied the fragment-reuse index one version at a time — rebuilding the index file and committing once per reuse version. An index touched by K deferred compactions paid K full index rebuilds + K commits, even though the final state is identical to applying all K at once. This is worst exactly when it matters most: when the reuse index has accumulated many versions before remap runs.

Change

Compose the whole chain and rebuild once:

  • Row addresses: FragReuseIndex::remap_row_id already chains every version and passes through addresses a version doesn't touch, so the union of all versions' map keys mapped through it yields a single baseline → final address map. One index::remap_index call applies it.
  • Coverage (fragment bitmap): composed in a single pass over all versions, with the same all-or-nothing / straddle-error semantics as before (chaining is automatic — a version inserts its new fragments, which the next version sees as its old ones). The data_predates_version check is evaluated against the fixed baseline, since there are no longer any intermediate commits.
  • One CreateIndex commit instead of one per version.

Semantically equivalent to the previous per-version loop (applying the composed map to a stored address gives the same result as applying each version's map in sequence), just without the intermediate rebuilds/commits.

Why this is safe

  • Fragment ids are never reused, so composing the union of version keys can't collide.
  • The straddle invariant (fix: reject Rewrite vs CreateIndex when FRI groups straddle bitmap #6610) is preserved — still errors on a partially-indexed rewrite group.
  • All existing remap / frag_reuse / compaction tests pass unchanged, including test_remap_index_after_compaction (a 3-version chain) which now completes in one commit.

Test

test_remap_index_batches_multiple_reuse_versions: accumulates ≥2 reuse versions (delete-prefix + deferred compaction in a loop), then asserts a single remap_column_index advances the dataset version by exactly 1 (one commit, not one per version) and that the reuse index trims to zero afterward.

Note

This optimizes the cost per catch-up; it's complementary to running remap regularly (which keeps the version count — and thus K — small in the first place).

…emapped

`remap_column_index` decided whether to remap an index's data from the
presence of the old fragments in its fragment bitmap. But `load_indices`
coverage-remaps the bitmap onto the new fragments in memory, and a later
commit can persist that cleaned bitmap to disk before the index data is
remapped -- for example while remapping a sibling index on the same table.

Once the bitmap is clean, the data remap was silently skipped: the index
kept serving stale row addresses (corrected only by reuse-index auto-remap
at load), and its dataset_version never advanced, so the fragment reuse
index could never be trimmed and grew without bound.

Detect the already-coverage-remapped case (the group's old fragments are
gone from the bitmap but its new fragments are present, and the index data
still predates the reuse version) and remap the data in that case too.

Adds a regression test exercising two indexes on one table.
`remap_index` applied the fragment-reuse index one version at a time,
rebuilding the index file and committing once per version. An index touched
by K deferred compactions therefore paid K full index rebuilds and K commits.

Compose instead: `remap_row_id` already chains every reuse version (passing
through addresses a version does not touch), so the union of all versions'
keys gives a single baseline->final row-address map; the fragment bitmap is
composed in one pass with the same all-or-nothing/straddle semantics. The
index is then rebuilt and committed exactly once.

Adds a test asserting a single commit for a multi-version reuse chain.
@xuanyu-z xuanyu-z force-pushed the xuanyuzhan/opt-batch-frag-reuse-remap branch from f54ea8a to d203514 Compare June 16, 2026 23:48
@xuanyu-z

Copy link
Copy Markdown
Contributor Author

Superseded by a stacked PR based on the #7286 fix branch so the diff shows only the batching commit: xuanyu-z#1 . Will reopen against upstream main once #7286 merges.

@xuanyu-z xuanyu-z closed this Jun 16, 2026
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.54726% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/optimize/remapping.rs 75.94% 16 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant