Skip to content

perf: reduce bitmap index build to 1 bitmap in RAM - #6371

Merged
wkalt merged 1 commit into
lance-format:mainfrom
wkalt:task/improve-bitmap-build-efficiency
Apr 1, 2026
Merged

perf: reduce bitmap index build to 1 bitmap in RAM#6371
wkalt merged 1 commit into
lance-format:mainfrom
wkalt:task/improve-bitmap-build-efficiency

Conversation

@wkalt

@wkalt wkalt commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Bitmap index build and update previously materialized every unique value's bitmap in a HashMap before writing to disk. For high-cardinality columns (e.g. URLs with millions of unique values across thousands of fragments) this caused OOM during both initial build and append.

Replace the materialize-then-write pipeline with a streaming merge that processes one value at a time:

  • Request sorted input via TrainingOrdering::Values so the scanner delivers (value, row_id) pairs in value order
  • Stream through the sorted data, accumulating row_ids for the current value and flushing each bitmap to disk as soon as the value changes
  • During append, merge-join the sorted new data with the old index's BTreeMap keys, loading each old bitmap on demand and dropping it after write

@wkalt

wkalt commented Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

before and after for 10M unique md5 hashes

bitmap_comparison

@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.14851% with 30 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance-index/src/scalar/bitmap.rs 83.33% 7 Missing and 23 partials ⚠️

📢 Thoughts on this report? Let us know!

@wjones127 wjones127 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice work on this!

There are a few unrelated changes to clean up, but after that it's good to merge.

Comment thread rust/lance-index/src/scalar/bitmap.rs Outdated
let keys_array =
ScalarValue::iter_to_array(self.keys.drain(..).collect::<Vec<_>>().into_iter())
.unwrap();
let mut binary_builder = BinaryBuilder::new();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: you could presize this builder:

Suggested change
let mut binary_builder = BinaryBuilder::new();
let total_size = self.serialized.iter().map(|b| b.len()).sum();
let mut binary_builder = BinaryBuilder::with_capacity(self.serialized.len(), total_size);

Although it might be just as efficient to do:

let bitmaps_array: BinaryArray = self.serialized.iter().collect();

vec![
Arc::new(StringArray::from(object_ids)),
Arc::new(StringArray::from(object_types.to_vec())),
Arc::new(StringArray::from(object_types.clone())),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: these changes in namespace code seem unrelated and unnecessary

Comment thread rust/lance-index/benches/inverted.rs Outdated
DocType::Text,
))
})
.map(|pair| Arc::new(Tokens::new(pair.to_vec(), DocType::Text)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: this seems unrelated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hmm, I wonder if my lint fix triggered on this. Will clean this up thanks.

@wkalt
wkalt force-pushed the task/improve-bitmap-build-efficiency branch from 2ba0b97 to 746ed60 Compare April 1, 2026 17:47
…s) to O(1 bitmap)

Bitmap index build and update previously materialized every unique
value's bitmap in a HashMap before writing to disk. For high-cardinality
columns (e.g. URLs with millions of unique values across thousands of
fragments) this caused OOM during both initial build and append.

Replace the materialize-then-write pipeline with a streaming merge that
processes one value at a time:

- Request sorted input via TrainingOrdering::Values so the scanner
  delivers (value, row_id) pairs in value order
- Stream through the sorted data, accumulating row_ids for the current
  value and flushing each bitmap to disk as soon as the value changes
- During append, merge-join the sorted new data with the old index's
  BTreeMap keys, loading each old bitmap on demand and dropping it
  after write

Peak memory drops from O(unique_values * avg_bitmap_size) to
O(largest_single_bitmap). On a local benchmark this reduced RSS from
8.6 GB to 3.3 GB and wall time from 20s to 8.5s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wkalt
wkalt force-pushed the task/improve-bitmap-build-efficiency branch from 746ed60 to b1a7c3f Compare April 1, 2026 18:38
@wkalt
wkalt merged commit 175a492 into lance-format:main Apr 1, 2026
29 checks passed
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.

2 participants