[fix][broker] Fix bucket delayed delivery state consistency issues - #26251
[fix][broker] Fix bucket delayed delivery state consistency issues#26251nodece wants to merge 3 commits into
Conversation
80937e9 to
8cda10a
Compare
|
|
|
When a selected bucket still has its current segment loaded in For example, assume a source bucket's current segment contains If We need to keep the current segment recoverable without adding a second live queue entry, and exclude entries that have already been dispatched. |
8cda10a to
d52e5fb
Compare
|
@void-ptr974 This PR body has been updated. |
void-ptr974
left a comment
There was a problem hiding this comment.
Thanks for the update. The main correctness issues are addressed. LGTM.
c0d4097 to
2b61d42
Compare
Update createMergeableBucket to use BucketContext instead of individual MutableBucket fields that no longer exist after the Bucket base class removal.
Motivation
Fix permanent message loss during bucket snapshot merge, and fix counter / delivery correctness issues that emerge once the data-loss fix is applied.
Merge permanently loses segment 1 data.
getRemainSnapshotSegmentloads fromcurrentSegmentEntryId + 1(segment 2+), skipping segment 1. After merge, old snapshots are deleted from BookKeeper. On restart, recovery rebuilds an incomplete bitMap — messages in segment 1 can never be delivered.Loading segment 1 during merge creates duplicate queue entries. Segment 1 messages are already in
sharedBucketPriorityQueuefrom the initial seal; merge pushes them in again. The existing pop path ignoresremoveIndexBit's return value, so both pops deliver and decrement the counter.Expired re-add leaks counter.
addMessagewithdeliverAt <= cutoffTimereturnsfalsewithout removing the existing bit. The bit stays and the counter remains inflated.Modifications
Fix merge to load all segments from segment 1
getAllSnapshotSegmentsnow loadssegment 1 … lastSegmentEntryIdinstead ofcurrentSegmentEntryId+1 … lastSegmentEntryId. The merged BookKeeper snapshot preserves complete bucket state.Extract
BucketDelayedMessageIndexas the single runtime truth sourceThe runtime dedup state is consolidated into
BucketDelayedMessageIndexwhich owns the bitmap and counter together. The counter is an invariant of the bitmap cardinality, not something callers must manually keep in sync.ImmutableBucket.delayedIndexBitMapis now a frozen snapshot used only for BookKeeper writes, merge composition (OR of source buckets), and recovery. It is no longer consulted at runtime for dedup or counter.Pop path now checks the return value:
First pop of a position delivers and decrements; subsequent pops are silently skipped.
Fix expired re-add cleanup
addMessagewithdeliverAt <= cutoffTimenow callsremoveIndexBitbefore returningfalse.Recovery restores into runtime index
index.restore(bucket.getDelayedIndexBitMap())populates the runtime index so all recovered messages participate in delivery dedup.Trim is index-aware
deleteBucketSnapshotiterates the trimmed bucket's frozen bitMap and callsindex.untrackper bit. Only bits still in the runtime index decrement the counter.Remove
Bucketbase classImmutableBucket's inheriteddelayedIndexBitMapserved a dual role (runtime dedup + BK snapshot) that made these bugs hard to reason about.ImmutableBucketandMutableBucketare now independent classes sharing aBucketContextrecord.Semantics
Pulsar's existing at-least-once delivery semantics are preserved. After merge + restart, messages that were dispatched but whose acknowledgement was not persisted before restart may be replayed. Actual delivery goes through the cursor's
markDeletedPositioncheck — already-acked messages are filtered by the dispatcher. The only window for re-delivery is the same one that exists for non-delayed messages.