-
Notifications
You must be signed in to change notification settings - Fork 3.8k
fix: HyperLogLogCollector returns zero cardinality when a single element overflows into sparse mode #19678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
asdf2014
wants to merge
3
commits into
apache:master
Choose a base branch
from
asdf2014:fix/hll-sparse-overflow-zero-estimate
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+126
−5
Open
fix: HyperLogLogCollector returns zero cardinality when a single element overflows into sparse mode #19678
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Normalize sparse positions before marking overflow applied
positionis the serialized byte offset, whichtoByteBuffer()writes as the payload index plusgetNumHeaderBytes()(7 for a V1 sketch), whileoverflowPositionis onlyoverflowRegister >>> 1. Consequently this flag can remain false after visiting the overflow bucket's actual sparse byte, or become true for an unrelated entry. For example, if a bucket first receives an in-range value and then an overflow value, a sparse round-trip stores its tuple atoverflowPosition + 7; the new fallback then treats the overflow as a second previously empty register and decrementszeroCountagain, making one populated HLL register estimate approximately 2 instead of 1. Compare positions in the same coordinate system before deciding whether standalone compensation is needed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed. In the sparse buffer
positionis the serialized offset (payload byte index +getNumHeaderBytes()), whereasoverflowPositionisoverflowRegister >>> 1, so the in-loop check never matched the overflow bucket's own byte andoverflowRegisterAppliedwas unreliable. In the in-range-then-overflow case the register was then counted twice — its sparse entry plus the fallback — estimating 2 for a single populated register.Fixed by normalizing
positionto the payload byte index (position - numHeaderBytes) before comparing withoverflowPosition, so the in-loop overflow handling matches the correct entry and the fallback only fires when the overflow bucket has no sparse entry. Added regression tests for the single-register round-trip (now estimates 1, was 2) and for an overflow register sitting alongside other sparse entries.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks—the serialized-position normalization fixes the original double-count for upper-nibble overflow buckets. One parity case remains: for an odd overflow bucket,
lowerNibbleis already decoded to a scalar (for example, 16), so((lowerNibble & 0x0f) == 0)incorrectly marks it as zero.add((short) 5, (byte) 3), thenadd((short) 5, (byte) 16), followed by a sparse round-trip therefore reacheszeroCount == NUM_BUCKETSand estimates 0. Please test decoded values withupperNibble == 0/lowerNibble == 0and add odd-bucket regression coverage.Reviewed 2 of 2 changed files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed. Both checks in that line operate on decoded scalars, so the nibble masks were wrong on both sides:
(lowerNibble & 0x0f) == 0marks decoded 16/32/48 as empty (the odd-bucket case you describe, which estimated 0), and(upperNibble & 0xf0) == 0marks decoded 1-15 as empty, so a populated neighbor register sharing the overflow byte was miscounted as well. Replaced both with direct== 0tests on the decoded values.The same pattern exists in
estimateDense, where the position comparison has always matched the overflow byte. A dense collector holding a single odd-bucket overflow register (for exampleadd((short) 5, (byte) 3)followed byadd((short) 5, (byte) 16), with no serialization round-trip) also estimated 0, independently of this PR's earlier changes. Fixed that occurrence the same way.Added regression tests: odd-bucket sparse round-trip (estimates 1), odd bucket with a populated neighbor in the same byte (estimates 2), and dense even/odd overflow without a round-trip (1 each).