Skip to content

fix: deduplicate stale BTree entries during optimize with stable row IDs#6041

Open
wkalt wants to merge 2 commits intolance-format:mainfrom
wkalt:fix/btree-optimize-sort-order
Open

fix: deduplicate stale BTree entries during optimize with stable row IDs#6041
wkalt wants to merge 2 commits intolance-format:mainfrom
wkalt:fix/btree-optimize-sort-order

Conversation

@wkalt
Copy link
Contributor

@wkalt wkalt commented Feb 27, 2026

When stable row IDs are used, the BTree index stores stable row IDs (not physical addresses) in its _rowid column. During optimize, the old entries for updated rows would survive fragment-based filtering because stable row IDs don't encode fragment IDs in their upper 32 bits. Both old (stale) and new entries for the same row ID ended up in the merged index, causing FlatIndex::try_new to fail with "RowAddrTreeMap:: from_sorted_iter called with non-sorted input" due to duplicate IDs.

Fix: before merging old and new data in combine_old_new(), collect the new data's row IDs and filter old entries that have matching IDs. This removes stale entries for rows that have been updated.

When stable row IDs are used, the BTree index stores stable row IDs
(not physical addresses) in its _rowid column. During optimize, the old
entries for updated rows would survive fragment-based filtering because
stable row IDs don't encode fragment IDs in their upper 32 bits. Both
old (stale) and new entries for the same row ID ended up in the merged
index, causing FlatIndex::try_new to fail with "RowAddrTreeMap::
from_sorted_iter called with non-sorted input" due to duplicate IDs.

Fix: before merging old and new data in combine_old_new(), collect the
new data's row IDs and filter old entries that have matching IDs. This
removes stale entries for rows that have been updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added the bug Something isn't working label Feb 27, 2026
@wkalt
Copy link
Contributor Author

wkalt commented Feb 27, 2026

This PR was generated automatically from the output of some generative testing

@github-actions
Copy link
Contributor

Review

Solid correctness fix for a real bug. The approach — collect new row IDs, then filter stale entries from the old stream — is straightforward and correct.

P1: Consider RoaringTreemap instead of HashSet<u64>

The codebase convention (per CLAUDE.md) is to prefer RoaringBitmap over HashSet<u32> for memory efficiency. For u64 keys, the equivalent is RoaringTreemap, which is already used in 13+ files across the repo. During a large optimize where many rows were updated, the HashSet<u64> could use significantly more memory than a RoaringTreemap. Since roaring is already a dependency here, the change would be minimal:

use roaring::RoaringTreemap;

let new_row_ids: RoaringTreemap = new_batches
    .iter()
    .flat_map(|batch| { ... })
    .collect();

No other issues found — the test coverage is good and the fix is well-targeted.

@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 92.50000% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-index/src/scalar/btree.rs 92.50% 0 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

Replace HashSet<u64> with RoaringTreemap for row ID deduplication
per repo coding standards (prefer roaring structures over hash sets
for row ID collections).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
// new fragment. Without this dedup step, both the old (stale) and new
// entries would survive, causing duplicate row IDs in the merged index.
let new_schema = new_data.schema();
let new_batches: Vec<RecordBatch> = new_data.try_collect().await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will collect all our new data, seems bad.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants