[UUID 4b] Bloom filter support for the logical UUID type - #19182
Conversation
BloomFilterSegmentPruner could not prune UUID columns: it probed the bloom filter with _comparableValue.toString(), which for a UUID is a ByteArray, so the probe used ByteArray's identity-ish toString and never matched a real entry. That is not a correctness bug -- a bloom miss only ever costs a non-pruned segment -- but it made the pruner useless for UUID predicates. The fix matches BloomFilterCreator.add(Object, int), which is the contract the reader has to mirror: it renders a UUID as its canonical dashed string. The rendering is resolved once, at pruner construction, rather than per mightBeContained() call. Deliberately scoped to UUID. BIG_DECIMAL has the same class of divergence (DataType#toString uses toPlainString(), the creator uses value.toString(), so they disagree on trailing zeros) but fixing that changes pruning for existing tables and belongs in its own change. Split out of apache#18872. Depends only on UuidUtils (apache#18869), already on master.
Adds UUID handling to the predicate evaluators, so =, !=, IN, NOT IN and range predicates work against a UUID column on both the raw and the dictionary path. UUID follows the pattern TIMESTAMP already uses: a logical type whose stored type does the work. The literal is parsed to its 16-byte stored form once, when the evaluator is built, and from there the existing BYTES evaluators apply -- no per-value conversion in the scan loop. The dictionary path needs no UUID branch: Dictionary#getStoredValue returns hex for a UUID column and indexOf(String) hex-decodes, so the existing String-keyed lookup is already correct. PredicateUtils renders the literal to that hex form for those String-typed lookup APIs. Split into apache#19181 (CAST), apache#19182 (bloom filter pruning) and apache#19183 (transform functions); this PR is now just the predicate evaluators.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #19182 +/- ##
============================================
+ Coverage 66.62% 66.65% +0.03%
Complexity 1423 1423
============================================
Files 3443 3443
Lines 218577 218626 +49
Branches 34792 34792
============================================
+ Hits 145624 145730 +106
+ Misses 61218 61171 -47
+ Partials 11735 11725 -10
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Those forms are accepted by UuidUtils.toBytes but must hash to the canonical key, which is why the probe goes literal -> stored bytes -> canonical string rather than hashing the raw literal. Verified non-vacuous: hashing _value directly fails exactly these assertions and no others. Also uses the UuidUtils.toString(ByteArray) overload instead of unwrapping.
Adds UUID handling to the predicate evaluators, so =, !=, IN, NOT IN and range predicates work against a UUID column on both the raw and the dictionary path. UUID follows the pattern TIMESTAMP already uses: a logical type whose stored type does the work. The literal is parsed to its 16-byte stored form once, when the evaluator is built, and from there the existing BYTES evaluators apply -- no per-value conversion in the scan loop. The dictionary path needs no UUID branch: Dictionary#getStoredValue returns hex for a UUID column and indexOf(String) hex-decodes, so the existing String-keyed lookup is already correct. PredicateUtils renders the literal to that hex form for those String-typed lookup APIs. Split into apache#19181 (CAST), apache#19182 (bloom filter pruning) and apache#19183 (transform functions); this PR is now just the predicate evaluators.
There was a problem hiding this comment.
Pull request overview
Enables BloomFilterSegmentPruner to correctly prune segments for logical UUID columns by hashing the same canonical UUID string representation that BloomFilterCreator writes into the bloom filter, and adds unit coverage to prevent future reader/writer rendering divergence.
Changes:
- Update bloom-filter probe key rendering to canonical UUID string for
UUID-typed predicates (mirrorsBloomFilterCreator#add(Object, int)contract). - Add UUID-specific pruning tests, including mixed-case and dashless literal normalization.
- Add a round-trip test that builds bloom filters through
BloomFilterCreatorto validate reader/writer consistency across multiple data types (includingBIG_DECIMALscientific notation).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ValueBasedSegmentPruner.java | Fix bloom-filter probe key generation for UUID by rendering to canonical UUID string before hashing (memoized per value/type). |
| pinot-core/src/test/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPrunerTest.java | Add UUID pruning tests and a creator-round-trip regression test to ensure bloom filter key rendering stays consistent. |
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Where is the logic of adding UUID into bloom filter? What value do we add?
Use the BYTES stored type's lowercase hex key for UUID Bloom creation and pruning. Cover segment generation and reload across dictionary/raw and SV/MV paths with real generated segments.
Exercise UUID Bloom filtering through a real custom-cluster query using both dashless hex and canonical CAST literals. Verify present values are not falsely pruned and an absent in-range value is pruned by the Bloom filter.
|
Addressed the remaining review question about what is inserted into the Bloom filter:
The new |
Add UUID transform coverage and stored-byte handling for CASE and IN. - CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID). - IN parses all-literal UUID lists to the 16-byte stored form so dashed and dashless spellings compare by value. - Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation. Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
Add UUID transform regression coverage and stored-byte handling for CASE. - CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID).\n- IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings.\n- Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation. Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
Add UUID transform regression coverage and stored-byte handling for CASE. - CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID). - IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings. - Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation. Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
Add UUID transform regression coverage and stored-byte handling for CASE. - CASE validates bare UUID branches as fixed-width hex and initializes the bytes result buffer for null-aware evaluation; canonical dashed values require CAST(... AS UUID). - IN already uses the BYTES stored path; cover uppercase hex, explicit UUID casts, and rejection of bare canonical strings. - Binary comparisons already dispatch through BYTES stored types; add dictionary/raw regression coverage and improve diagnostics and comparator documentation. Split out of apache#18872. Prerequisite UUID changes apache#19181 and apache#19182 are now on master.
What
Completes Bloom-filter support for logical UUID columns across segment creation, segment reload/index preprocessing, and segment pruning.
Representation
UUID values are stored as fixed-width BYTES. Bloom filters use the existing lowercase dashless hexadecimal representation:
No UUID-specific Bloom key rendering is needed.
Changes
Testing
About this PR
Repurposed from pruning-only to the UUID Bloom-filter story.
Part of #18140.