Add a running XOR key digest to upsert snapshot metadata - #19525
Closed
KKcorps wants to merge 5 commits into
Closed
Conversation
Each partition keeps 256 XOR buckets over the live primary-key map. Every entry that is not a tombstone contributes hash64(storedKey, comparisonValue). Segment and docId are not hashed, so a replace that lands the same rows leaves the digest unchanged. The on-heap manager updates the digest at every map write and removal, including the metadataTTL sweep, and the snapshot capture freezes it under a seqlock and publishes it in the report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016sih7cgUT7mAFHtTYQq1mk
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #19525 +/- ##
============================================
+ Coverage 67.74% 67.78% +0.03%
- Complexity 1424 1450 +26
============================================
Files 3489 3496 +7
Lines 224672 225482 +810
Branches 35468 35633 +165
============================================
+ Hits 152210 152835 +625
- Misses 60445 60547 +102
- Partials 12017 12100 +83
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:
|
Contributor
Author
|
Closing. The digest is being built in the StarTree RocksDB upsert manager instead, where the write sites, value format and persistence live. This branch stays as an on-heap reference. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Stacked on #19515. Only the last commit is new. The diff view includes #19515 until that PR merges.
TL;DR
Two replicas of an upsert partition can hold the same segments, the same valid-doc counts, and still
disagree on which row is current or on its comparison value. This PR adds a running XOR digest of the
live primary-key map to the snapshot report from #19515. Two replicas at the same stream boundary hold
the same digest exactly when they hold the same set of
(primary key, comparison value)entries. It isopt-in behind the existing
enableSnapshotMetadataflag and costs one hash and two XORs per map write.The problem
The saved-bitmap fingerprints in #19515 compare bytes on disk. They need every segment to have a saved
file, they say nothing about comparison values, and a mismatch still needs a logical bitmap compare to
confirm. The count-based consensus checks miss the same-count-different-rows shape entirely.
flowchart LR subgraph Before["❌ Today"] A[replica A: key k at cv 120] --> C[equal counts, equal bitmaps] B[replica B: key k at cv 100] --> C C --> D[no signal] end subgraph After["✅ With this PR"] E[replica A: XOR includes h k,120] --> G[digests differ at the same boundary] F[replica B: XOR includes h k,100] --> G G --> H[bucket names 1/256th of the key space] endThe approach
longbuckets. Bucket = top byte ofhash64(storedKey).hash64(storedKey, comparisonValue)to its bucket.An update XORs the old contribution out and the new one in. A removal XORs out. A delete record XORs
the old contribution out and adds nothing.
digest unchanged.
The snapshot capture freezes the buckets at start and end and publishes them only if both reads were
outside any window and nothing changed in between.
keyDigestblock: algorithm, total, base64 buckets, entry count, stable flag.Key components
UpsertKeyDigestUpsertSnapshotMetadata.KeyDigestUpsertSnapshotDiagnosticsBasePartitionUpsertMetadataManagerConcurrentMapPartitionUpsertMetadataManagerRecordLocationcarries the tombstone flagdocs/upsert-snapshot-metadata.mdFlow
A consuming record
sequenceDiagram participant C as Consumer thread participant M as ConcurrentMapPartitionUpsertMetadataManager participant K as ConcurrentHashMap participant D as UpsertKeyDigest C->>M: addRecord(segment, recordInfo) M->>K: compute(storedKey) alt new key K-->>M: no entry M->>D: add(storedKey, cv, isDelete) else newer comparison value K-->>M: current location M->>D: update(storedKey, oldCv, oldIsDelete, cv, isDelete) else out of order K-->>M: current location Note over M,D: no map change, no digest change endA snapshot capture
sequenceDiagram participant C as Consumer thread participant B as BasePartitionUpsertMetadataManager participant S as UpsertSnapshotDiagnostics participant D as UpsertKeyDigest participant H as Helix thread C->>B: takeSnapshot(consumingSegment, startOffset) B->>S: begin S->>D: freeze (epoch, depth, copy, depth, epoch) B->>B: write bitmap files opt segment add / replace / remove H->>D: beginUnstable H->>D: add / update / remove per key H->>D: endUnstable end B->>S: finish S->>D: freeze alt both marks stable and equal S-->>B: keyDigest with total and buckets else S-->>B: keyDigest with stable=false, total and buckets null endWhat the digest ignores on purpose
removes tombstones, so it never touches the digest. A swallowed delete still shows: one replica removed
the old contribution, the other still holds it.
them would flag every reload.
with the same
consumingSegmentNameandstartOffset, taken at the commit boundary.Sweeps
The on-heap manager applies the metadataTTL sweep to the digest. Its sweep runs at the commit boundary
on every replica, and its map is rebuilt from segments on restart, so the digest must describe the live
map. One capture after a restart on a metadataTTL table can differ until the first sweep runs. A manager
whose sweep runs on a timer must keep the sweep off the digest and persist the buckets instead. That is
the RocksDB companion, not this PR.
Configuration
No new keys. The digest exists only when
upsertConfig.metadataManagerConfigs.enableSnapshotMetadatais
true, same as the rest of the report. When it is off, every hook is one null check.Performance considerations
finalizer, one atomic XOR on a
long, oneLongAdderbump. ForhashFunction=NONEthe key hash callsPrimaryKey.asBytes(), which allocates once per write. Hashed key functions use the stored 16 bytes.RecordLocationdoes not grow. The tombstone flag rides in the sign bit of the docId.Compatibility notes
UpsertSnapshotMetadata.FORMAT_VERSIONmoves from 3 to 4. Version 3 sidecars read as unavailable.Version 3 never shipped.
RecordLocationgains a four-argument constructor andisDeleteRecord(). The three-argumentconstructor stays and means "not a delete".
UpsertSnapshotDiagnosticstakes the digest in its package-private constructor.PartitionUpsertMetadataManagerorTableUpsertMetadataManager.Validation
UpsertKeyDigestTest: order independence, add/remove/update algebra, tombstones contribute zero,distinct hashes across comparison-value types, seqlock stability, report gating.
ConcurrentMapPartitionUpsertMetadataManagerTest: two replicas fed the same records agree; a missedupdate and a swallowed delete each break agreement until applied; an out-of-order record and a segment
replace leave the digest unchanged; segment removal removes the owned key on both; a fresh manager fed
only the surviving rows lands on the same digest; the metadataTTL sweep removes swept keys from the
digest.
BasePartitionUpsertMetadataManagerTest: the report carries a stable zero digest, and a segmentoperation in flight publishes
stable=falsewith null total and buckets.UpsertSnapshotMetadataStoreTest: round trip, version 3 rejected.TablesResourceTestpasses with the new report shape (19 tests).pinot-segment-localupsert pass. Spotless and Checkstyle pass.Release notes
Adds an opt-in per-partition XOR digest of upsert primary keys and comparison values to the snapshot
metadata report.
Related: #19515, #19499. Documentation:
docs/upsert-snapshot-metadata.md.🤖 Generated with Claude Code
https://claude.ai/code/session_016sih7cgUT7mAFHtTYQq1mk