Search before reporting
Motivation
PendingAcksMap is maintained per consumer and is on the broker-side consumer ack path. It tracks pending acknowledgements by (ledgerId, entryId) and is touched by dispatch, individual ack, partial ack, redelivery, and mark-delete cleanup.
The current implementation uses nested ordered maps with boxed keys and object values. This keeps the code simple, but it also adds per-entry object overhead, allocation rate, and GC pressure when a consumer has a large pending-ack window.
This issue tracks a series of focused improvements to reduce that overhead without changing consumer ack semantics.
A full draft implementation is available for review and validation:
#26024
Solution
The main insight is that PendingAcksMap needs ordering at the ledger level, but not for every entry within a ledger.
The outer ledger map must stay ordered because removeAllUpTo(markDeletePosition) removes whole ledger buckets by ledger range. Inside one ledger, the common operations are exact lookup, exact remove, remaining-unacked update, and boundary cleanup for entryId <= markDeleteEntryId. These operations do not require the inner map to iterate entries in sorted order.
Redelivery ordering is not inherited from the per-ledger pending-ack map. The redelivery path rebuilds ordered positions separately, so replacing the inner ordered map does not change the redelivery ordering contract.
The split is:
| Part |
PR |
Scope |
| 1 |
#26028 |
Add local primitive Long2LongOpenHashMap |
| 2 |
#26030 |
Pack PendingAcksMap values into long and use primitive accessors in Consumer |
| 3 |
Follow-up |
Use Long2LongOpenHashMap inside PendingAcksMap |
| 4 |
Follow-up |
Add the BitSet prefix index for same-ledger removeAllUpTo cleanup |
The O(1) size() change is already split separately in #26019. The current pending-ack storage PRs do not include the size change.
Alternatives
A few alternatives were considered:
- Keep the current nested
TreeMap layout. This preserves simple prefix cleanup, but keeps the per-entry object and boxing overhead.
- Copy or reimplement a primitive ordered tree map. This would preserve ordered-prefix operations, but the implementation cost and review risk are much higher.
- Add an external primitive collection dependency. This avoids local collection code, but introduces dependency and licensing/review overhead for a narrow broker hot-path use case.
The proposed approach keeps the ordered structure only where the semantics require it, and uses a smaller local primitive structure for the inner hot path.
Anything else?
The draft PR and split PRs include tests for the changed assumptions:
- packed values preserve both
remainingUnacked and stickyKeyHash;
- zero remaining counts are not confused with missing entries;
- negative sticky-key hashes round-trip correctly;
- whole-ledger and boundary-ledger cleanup remain correct;
- entries after the mark-delete position are retained;
- unordered inner storage still preserves
removeAllUpTo behavior;
- BitSet fallback remains correct when the index cannot be used;
- the primitive map is checked with oracle, random, collision, and edge-case tests.
Benchmark and JOL data are included in comments. The benchmark uses datasets tied to real defaults such as maxUnackedMessagesPerConsumer = 50000, maxUnackedMessagesPerSubscription = 200000, and receiverQueueSize = 1000.
This does not change the ack protocol, subscription semantics, cursor mark-delete semantics, or dispatch/redelivery ordering. The goal is to reduce local data-structure overhead in the broker consumer pending-ack path.
Are you willing to submit a PR?
Search before reporting
Motivation
PendingAcksMapis maintained per consumer and is on the broker-side consumer ack path. It tracks pending acknowledgements by(ledgerId, entryId)and is touched by dispatch, individual ack, partial ack, redelivery, and mark-delete cleanup.The current implementation uses nested ordered maps with boxed keys and object values. This keeps the code simple, but it also adds per-entry object overhead, allocation rate, and GC pressure when a consumer has a large pending-ack window.
This issue tracks a series of focused improvements to reduce that overhead without changing consumer ack semantics.
A full draft implementation is available for review and validation:
#26024
Solution
The main insight is that
PendingAcksMapneeds ordering at the ledger level, but not for every entry within a ledger.The outer ledger map must stay ordered because
removeAllUpTo(markDeletePosition)removes whole ledger buckets by ledger range. Inside one ledger, the common operations are exact lookup, exact remove, remaining-unacked update, and boundary cleanup forentryId <= markDeleteEntryId. These operations do not require the inner map to iterate entries in sorted order.Redelivery ordering is not inherited from the per-ledger pending-ack map. The redelivery path rebuilds ordered positions separately, so replacing the inner ordered map does not change the redelivery ordering contract.
The split is:
Long2LongOpenHashMapPendingAcksMapvalues intolongand use primitive accessors inConsumerLong2LongOpenHashMapinsidePendingAcksMapremoveAllUpTocleanupThe O(1)
size()change is already split separately in #26019. The current pending-ack storage PRs do not include the size change.Alternatives
A few alternatives were considered:
TreeMaplayout. This preserves simple prefix cleanup, but keeps the per-entry object and boxing overhead.The proposed approach keeps the ordered structure only where the semantics require it, and uses a smaller local primitive structure for the inner hot path.
Anything else?
The draft PR and split PRs include tests for the changed assumptions:
remainingUnackedandstickyKeyHash;removeAllUpTobehavior;Benchmark and JOL data are included in comments. The benchmark uses datasets tied to real defaults such as
maxUnackedMessagesPerConsumer = 50000,maxUnackedMessagesPerSubscription = 200000, andreceiverQueueSize = 1000.This does not change the ack protocol, subscription semantics, cursor mark-delete semantics, or dispatch/redelivery ordering. The goal is to reduce local data-structure overhead in the broker consumer pending-ack path.
Are you willing to submit a PR?