Skip to content

feat: avoid deserialization in bitmap_contains/min/max/has_any/has_all - #20210

Open
harry-hao wants to merge 9 commits into
databendlabs:mainfrom
harry-hao:bitmap_avoid_deser
Open

feat: avoid deserialization in bitmap_contains/min/max/has_any/has_all#20210
harry-hao wants to merge 9 commits into
databendlabs:mainfrom
harry-hao:bitmap_avoid_deser

Conversation

@harry-hao

@harry-hao harry-hao commented Jul 27, 2026

Copy link
Copy Markdown

I hereby agree to the terms of the CLA available at: https://docs.databend.com/dev/policies/cla/

Background

Issue [#19019](#19019) identified deserialization as the bottleneck for BITMAP_INTERSECT. A series of PRs addressed this progressively:

These PRs laid the foundation for computing on serialized bytes, follow-up work left as a good first issue tracked under [#19101](#19101). This PR picks up that work.

Summary

Implement zero-deserialization fast paths for five scalar bitmap functions on the HybridBitmap::Large variant (bitmap_contains, bitmap_min, bitmap_max, bitmap_has_any, bitmap_has_all). These functions now compute results by reading RoaringFormatSpec serialized bytes directly, avoiding full deserialization.

Zero-deserialization operations

  • bitmap_contains: intersects a single-element RoaringTreemap with the serialized buffer
  • bitmap_min / bitmap_max: reads the first/last value from the first/last container
  • bitmap_has_any / bitmap_has_all: container-level merge-join between two serialized bitmaps if both are large, if one side is large then deserialize and iterate the smaller side, then probe the larger side, fallback to deserialize both side as last sort.

Fast-path

Before entering the costly intersection path, several checks reject impossible results at zero cost:

  • Empty-bitmap: e.g. bitmap_has_any(empty, _) => false
  • Range overlap: e.g. bitmap_has_any(lhs, rhs) => false if lhs.max < rhs.min || lhs.min > rhs.max
  • Range covering: e.g. bitmap_has_all(lhs, rhs) => false if lhs.min > rhs.min || lhs.max < rhs.max
  • Cardinality: e.g. bitmap_has_all(lhs, rhs) => false if lhs.len < rhs.len
  • Container-level cardinality: same as above but per container
  • Bitmap-container full-superset shortcut: e.g. if lhs is a bitmap container (cardinality = 65536), it trivially contains any rhs container.

Supporting infrastructure

  • BitmapStats: a struct holding {len, min, max} computed once from serialized bytes, reused by bitmap_has_any/bitmap_has_all for range and cardinality rejection

Tests & benchmarks

  • 6 unit tests in bitmap.rs: test_bitmap_contains, test_bitmap_min, test_bitmap_max, test_bitmap_has_any, test_bitmap_stats, test_bitmap_has_all -- covering HybridLarge, HybridSmall, Legacy, and empty-bitmap cases.
  • 16 divan benchmarks for the optimized functions in the bitmap_scalar group: bitmap_contains (large + small), bitmap_min (large + small), bitmap_max (large + small), bitmap_has_any (5 variants including disjoint), bitmap_has_all (5 variants including disjoint).

Performance

Benchmarks collected from bench (cargo bench -p databend-common-functions --bench bench -- bitmap_scalar), taking median values for compare:

Function Baseline Median PR Median Faster
bitmap_contains_large 13.7 us 2.0 us 7x
bitmap_min_large 11.4 us 0.55 us 20x
bitmap_max_large 11.5 us 0.53 us 22x
bitmap_has_all_large_large 40.5 us 10.4 us 4x
bitmap_has_any_large_large 27.5 us 2.7 us 10x

Remaining work

Tests

  • Unit Test
  • Logic Test
  • Benchmark Test
  • No Test - Explain why

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Breaking Change (fix or feature that could cause existing functionality not to work as expected)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

This change is Reviewable

@harry-hao harry-hao changed the title perf: avoid deserialization in bitmap_contains/min/max/has_any/has_all feat: avoid deserialization in bitmap_contains/min/max/has_any/has_all Jul 27, 2026
@github-actions github-actions Bot added the pr-feature this PR introduces a new feature to the codebase label Jul 27, 2026
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2aab60db4a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap/reader.rs Outdated
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 2aab60d to 02454bf Compare July 27, 2026 13:49
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 02454bf569

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@harry-hao
harry-hao marked this pull request as ready for review July 27, 2026 13:57

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02454bf569

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap.rs
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 02454bf to cd08039 Compare July 27, 2026 15:00
Added a `bitmap_scalar` bench group covering:
- bitmap_contains
- bitmap_count
- bitmap_min
- bitmap_max
- bitmap_and
- bitmap_has_any
- bitmap_has_all
For large hybrid bitmaps, bitmap_contains now reads the serialized
buffer directly via binary search on container descriptions instead
of deserializing the entire bitmap into memory.

This reduces bitmap_contains_large from ~12.1µs to ~2.1µs (6x speedup).
For large hybrid bitmaps, bitmap_min now reads the minimum value
directly from the serialized buffer (first element of the first
container) instead of deserializing the entire roaring treemap.

This reduces bitmap_min_large from ~10.3µs to ~562ns (18x speedup).
For large hybrid bitmaps, bitmap_max now reads the maximum value
directly from the serialized buffer (last element of the last
container) instead of deserializing the entire roaring treemap.

This reduces bitmap_max_large from ~10.7µs to ~531ns (20x speedup).
For large hybrid bitmaps, bitmap_has_any now checks intersection
directly on the serialized buffers using container-level binary search
instead of deserializing both bitmaps and computing full intersection.

This reduces bitmap_has_any_large_large from ~26µs to ~2.5µs (10x),
and disjoint cases from ~20µs to ~963ns (21x).
For large hybrid bitmaps, bitmap_has_all now checks superset
relationships directly on serialized buffers using container-level
comparison instead of deserializing both bitmaps.

This reduces bitmap_has_all_large_large from ~39µs to ~11µs (3.5x),
and disjoint cases from ~20µs to ~932ns (22x).
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 9bafe89 to 4ba28b9 Compare July 28, 2026 02:07
@harry-hao

Copy link
Copy Markdown
Author

Hi @forsaken628, could you review this PR? It adds zero-deserialization fast paths for bitmap_contains/min/max/has_any/has_all, operating directly on serialized HybridLarge buffers via a TreemapReader instead of full deserialize + method call. The else branch (HybridSmall + Legacy) still uses deserialize_bitmap fallback. I'd appreciate your perspective on the reader.rs zero-deserialization approach and the bitmap.rs dispatch design.

@b41sh
b41sh requested a review from KKould July 28, 2026 02:28
@KKould
KKould requested a review from forsaken628 July 28, 2026 02:35
@forsaken628

Copy link
Copy Markdown
Collaborator

I do not object to reimplementing these operations instead of relying on roaring-rs, but the current implementation is too fragmented. Although TreemapReader and BitmapReader already exist, container-level behavior is still spread across many free functions operating on raw byte slices. A dedicated zero-copy serialized container type should encapsulate parsing, validation, representation dispatch, and operations such as contains, min, max, intersection, and superset checks.

The repeated treemap/container merge traversal should also be centralized, with operation-specific logic supplied through a visitor or handler. ab59ebef (RoaringBitmap/roaring-rs@ab59ebe) provides a useful reference for separating traversal from operation handlers. It is designed around roaring-rs’s materialized containers, so Databend does not need to copy it directly; since both operands here are serialized buffers, we can design a simpler zero-copy visitor around our own serialized container abstraction.

The current tests are also insufficient for maintaining an independent implementation of Roaring semantics. There are some fixed differential checks for has_any and has_all, but equivalent comparisons against RoaringTreemap should cover all newly implemented operations. Property-based tests are also needed, with structured coverage of array/bitmap combinations, multiple prefix buckets, empty inputs, and cardinality boundaries such as 4095/4096/4097.

@harry-hao

Copy link
Copy Markdown
Author

I do not object to reimplementing these operations instead of relying on roaring-rs, but the current implementation is too fragmented. Although TreemapReader and BitmapReader already exist, container-level behavior is still spread across many free functions operating on raw byte slices. A dedicated zero-copy serialized container type should encapsulate parsing, validation, representation dispatch, and operations such as contains, min, max, intersection, and superset checks.

The repeated treemap/container merge traversal should also be centralized, with operation-specific logic supplied through a visitor or handler. ab59ebef (RoaringBitmap/roaring-rs@ab59ebe) provides a useful reference for separating traversal from operation handlers. It is designed around roaring-rs’s materialized containers, so Databend does not need to copy it directly; since both operands here are serialized buffers, we can design a simpler zero-copy visitor around our own serialized container abstraction.

The current tests are also insufficient for maintaining an independent implementation of Roaring semantics. There are some fixed differential checks for has_any and has_all, but equivalent comparisons against RoaringTreemap should cover all newly implemented operations. Property-based tests are also needed, with structured coverage of array/bitmap combinations, multiple prefix buckets, empty inputs, and cardinality boundaries such as 4095/4096/4097.

Thank you for the detailed review. I fully agree with your feedback:

  1. Current implementation is indeed too fragmented. A dedicated zero-copy serialized container type should make it more maintainable.
  2. Centralizing the repeated merge traversal logic through visitor pattern is a good idea.
  3. Tests comparing against RoaringTreemap and property-based tests will be added.

I'll work on refactoring the implementation and expanding the test coverage accordingly.

@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 6369790 to 4ae69c5 Compare July 30, 2026 10:05
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ae69c5513

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap.rs Outdated
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 4651515 to 2cf4a7e Compare July 30, 2026 10:51
@harry-hao

Copy link
Copy Markdown
Author

That was a make lint error. Resolved.

@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2cf4a7e834

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap.rs Outdated
Comment thread src/common/io/src/bitmap.rs
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70fd795f4c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap.rs
Comment thread src/common/io/src/bitmap.rs
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a42ce85e70

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap/reader.rs Outdated
Comment thread src/common/io/src/bitmap.rs
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from a42ce85 to da16bd4 Compare July 30, 2026 15:58
@harry-hao

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da16bd4ccf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/common/io/src/bitmap/reader.rs Outdated
Comment on lines +55 to +56
let header = BitmapReader::decode(probe)?;
probe = &probe[header.buf.len()..];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize treemap prefixes before fast reads

If a legacy/HybridLarge payload contains out-of-order or duplicate treemap bucket prefixes, this loop only decodes each bucket and preserves the raw order, while the old deserialize_bitmap path normalized prefixes through RoaringTreemap's BTreeMap semantics. For example, a payload with two prefix-0 buckets {1} then {2} used to behave as {2}, but the new bitmap_contains returns from the first bucket and reports that 1 is present; min/max and the set predicates have analogous mismatches. Please reject or normalize non-canonical prefix order before taking the zero-copy fast paths.

Useful? React with 👍 / 👎.

@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from da16bd4 to 4c47c52 Compare July 30, 2026 16:32
Refactor the 5 bitmap operation functions to improve
maintainability:

- bitmap_contains
- bitmap_min
- bitmap_max
- bitmap_has_any
- bitmap_has_all

Key changes:

- ContainerReader: read and operate on a roaring container
- SmallReader: same as ContainerReader but for HybridBitmap::Small
- ContainerVisitor: merge traversal of two serialized treemaps
- ContainerHandler: trait abstract container handle logic, used by ContainerVisitor
- HasAnyHandler/HasAllHandler: impl ContainerHandler for bitmap_has_any/all
- Tests restructured to compare against RoaringTreemap as ground truth, sharing fixtures across all tests, covering combinations of: format, container type, set relationship, and key
  alignment.
This commit introduced [proptests](https://docs.rs/proptest) for newly introduced:

- bitmap_contains
- bitmap_min
- bitmap_max
- bitmap_has_any
- bitmap_has_all

Each compares the result against RoaringTreemap as ground truth.

Strategy design inspired by roaring as:

- Store (random bits)
- Container (50% Array, 50% Bitmap)
- Bitmap (0 to 16 containers)
- Tree (0 to 16 Bitmaps)
- Serialization (80% Hybrid, 10% Legacy, 10% Empty)
… Small

Previously, when bitmap_has_all got a Small lhs and a Large rhs, it
simply return false, since a set with fewer cardinality can't has_all
another one with higher cardinality. But due to we don't always demote
Large to Small after cardinality reducing operations, that's not true
anymore.

As a defense, we removed this branch, and the doubting rhs will fall
into branch below, where rhs cardinality is actually checked before
returning false.
@harry-hao
harry-hao force-pushed the bitmap_avoid_deser branch from 4c47c52 to 29bbf63 Compare July 31, 2026 03:07
@harry-hao

Copy link
Copy Markdown
Author

I pushed three patches, with the following changes:

  1. ContainerReader and SmallReader: encapsulates container/HybridSmall operations
  2. Visitor pattern: now the merge traversal logic is centralized with bitmap logics decoupled
  3. Structured unit tests: all tests share same fixtures comparing against Roaring(Bit|Tree)map, with combinations of: format, prefix, container key, container type, lhs/rhs relationships, boundary and emptiness
  4. Property-based tests: with arbitrary tree generating strategy and verifying against RoaringTreemap, caught 2 bugs, no new one emerges in last ~2000 test runs(64000 random cases for each function)

These should address all three points from the last review.

Regarding the later codex comments: codex wants the fast paths behave exactly same like deserialize_bitmap on corrupted input. For example, this, codex complains bitmap_contains({0:{1}, 0:{2}}, 1) behave differently -- 0:{1} means prefix 0 bucket with value 1, deserialize_bitmap will normalize it into {0:{2}} and return false, new path will take the first and return true. Another example, another, codex complains bitmap_has_any(corrupted, empty) will error in deserialize_bitmap path(because it deserialize both at first), with new path return false (without deserialize and check rhs).

After some thought, I‘d prefer to not to address those. Because they are all corruption related: (1) the bottom line of fast paths should be: correctness, safety and we have introduced random walking proptests for that; (2) even if we deserialize the whole buffer like deserialize_bitmap did, we could not catch all corruption. The corruption should be considered, but not in this pr; (3) this issue is mainly about speedup, and it should not be slowed down by adding extra checks for corruption.

So I think it's ready for another review.

@forsaken628 would you like to take another look?

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

Labels

pr-feature this PR introduces a new feature to the codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants