Skip to content

Commit 7795eec

Browse files
committed
Address comment
1 parent aa7e718 commit 7795eec

5 files changed

Lines changed: 346 additions & 21 deletions

File tree

pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/InMemoryDelayedDeliveryTracker.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public class InMemoryDelayedDeliveryTracker extends AbstractDelayedDeliveryTrack
6666
// Count of delayed messages in the tracker.
6767
private final AtomicLong delayedMessagesCount = new AtomicLong(0);
6868

69+
// Cached memory usage of the delayed message bitmaps, maintained via delta on each mutation.
70+
private final AtomicLong memoryUsage = new AtomicLong(0);
71+
6972
InMemoryDelayedDeliveryTracker(AbstractPersistentDispatcherMultipleConsumers dispatcher, Timer timer,
7073
long tickTimeMillis,
7174
boolean isDelayedDeliveryDeliverAtTimeStrict,
@@ -142,7 +145,9 @@ public boolean addMessage(long ledgerId, long entryId, long deliverAt) {
142145
boolean isNew = !bitmap.contains(entryId);
143146

144147
if (isNew) {
148+
long oldSize = bitmap.getLongSizeInBytes();
145149
bitmap.add(entryId);
150+
memoryUsage.addAndGet(bitmap.getLongSizeInBytes() - oldSize);
146151
delayedMessagesCount.incrementAndGet();
147152
}
148153

@@ -205,13 +210,16 @@ public NavigableSet<Position> getScheduledMessages(int maxMessages) {
205210
});
206211
n -= cardinality;
207212
delayedMessagesCount.addAndGet(-cardinality);
213+
memoryUsage.addAndGet(-entryIds.getLongSizeInBytes());
208214
ledgerIdToDelete.add(ledgerId);
209215
} else {
216+
long oldSize = entryIds.getLongSizeInBytes();
210217
long[] entryIdsArray = entryIds.toArray();
211218
for (int i = 0; i < n; i++) {
212219
positions.add(PositionFactory.create(ledgerId, entryIdsArray[i]));
213220
entryIds.removeLong(entryIdsArray[i]);
214221
}
222+
memoryUsage.addAndGet(entryIds.getLongSizeInBytes() - oldSize);
215223
delayedMessagesCount.addAndGet(-n);
216224
n = 0;
217225
}
@@ -248,6 +256,7 @@ public NavigableSet<Position> getScheduledMessages(int maxMessages) {
248256
public CompletableFuture<Void> clear() {
249257
this.delayedMessageMap.clear();
250258
this.delayedMessagesCount.set(0);
259+
this.memoryUsage.set(0);
251260
return CompletableFuture.completedFuture(null);
252261
}
253262

@@ -264,9 +273,7 @@ public long getNumberOfDelayedMessages() {
264273
*/
265274
@Override
266275
public long getBufferMemoryUsage() {
267-
return delayedMessageMap.values().stream().mapToLong(
268-
ledgerMap -> ledgerMap.values().stream().mapToLong(
269-
Roaring64Bitmap::getLongSizeInBytes).sum()).sum();
276+
return memoryUsage.get();
270277
}
271278

272279
@Override

pulsar-broker/src/main/java/org/apache/pulsar/broker/delayed/bucket/BucketDelayedDeliveryTracker.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.common.annotations.VisibleForTesting;
2525
import com.google.common.collect.Range;
2626
import com.google.common.collect.RangeMap;
27-
import com.google.common.collect.TreeRangeMap;
2827
import io.github.merlimat.slog.Logger;
2928
import io.netty.util.Timeout;
3029
import io.netty.util.Timer;
@@ -107,7 +106,7 @@ public static record SnapshotKey(long ledgerId, long entryId) {}
107106

108107
@Getter
109108
@VisibleForTesting
110-
private final RangeMap<Long, ImmutableBucket> immutableBuckets;
109+
private final ImmutableBucketIndex immutableBuckets;
111110

112111
private final ConcurrentHashMap<SnapshotKey, ImmutableBucket> snapshotSegmentLastIndexMap;
113112

@@ -154,7 +153,7 @@ public BucketDelayedDeliveryTracker(DelayedDeliveryContext context,
154153
this.maxIndexesPerBucketSnapshotSegment = maxIndexesPerBucketSnapshotSegment;
155154
this.maxNumBuckets = maxNumBuckets;
156155
this.sharedBucketPriorityQueue = new TripleLongPriorityQueue();
157-
this.immutableBuckets = TreeRangeMap.create();
156+
this.immutableBuckets = new ImmutableBucketIndex();
158157
this.snapshotSegmentLastIndexMap = new ConcurrentHashMap<>();
159158
this.lastMutableBucket =
160159
new MutableBucket(context.getName(), context.getCursor(), FutureUtil.Sequencer.create(),
@@ -241,7 +240,7 @@ private synchronized long recoverBucketSnapshot() throws RecoverDelayedDeliveryT
241240
for (Map.Entry<Range<Long>, ImmutableBucket> mapEntry : toBeDeletedBucketMap.entrySet()) {
242241
Range<Long> key = mapEntry.getKey();
243242
ImmutableBucket immutableBucket = mapEntry.getValue();
244-
immutableBucketMap.remove(key);
243+
immutableBuckets.remove(key);
245244
// delete asynchronously without waiting for completion
246245
immutableBucket.asyncDeleteBucketSnapshot(stats);
247246
}
@@ -251,6 +250,8 @@ private synchronized long recoverBucketSnapshot() throws RecoverDelayedDeliveryT
251250
numberDelayedMessages.add(bucket.numberBucketDelayedMessages);
252251
});
253252

253+
immutableBuckets.recomputeCounters();
254+
254255
log.info()
255256
.attr("buckets", immutableBucketMap.size())
256257
.attr("numberDelayedMessages", numberDelayedMessages.longValue())
@@ -371,7 +372,7 @@ private void afterCreateImmutableBucket(Pair<ImmutableBucket, DelayedIndex> immu
371372
});
372373

373374
immutableBucket.setCurrentSegmentEntryId(immutableBucket.lastSegmentEntryId);
374-
immutableBuckets.asMapOfRanges().remove(
375+
immutableBuckets.remove(
375376
Range.closed(immutableBucket.startLedgerId, immutableBucket.endLedgerId));
376377
snapshotSegmentLastIndexMap.remove(
377378
new SnapshotKey(lastDelayedIndex.getLedgerId(), lastDelayedIndex.getEntryId()));
@@ -409,7 +410,7 @@ public synchronized boolean addMessage(long ledgerId, long entryId, long deliver
409410
afterCreateImmutableBucket(immutableBucketDelayedIndexPair, createStartTime);
410411
lastMutableBucket.resetLastMutableBucketRange();
411412

412-
if (maxNumBuckets > 0 && immutableBuckets.asMapOfRanges().size() > maxNumBuckets) {
413+
if (maxNumBuckets > 0 && immutableBuckets.count() > maxNumBuckets) {
413414
asyncMergeBucketSnapshot();
414415
}
415416
}
@@ -507,7 +508,7 @@ private synchronized CompletableFuture<Void> asyncMergeBucketSnapshot() {
507508
} else {
508509
log.info()
509510
.attr("bucketKeys", bucketsStr)
510-
.attr("bucketNum", immutableBuckets.asMapOfRanges().size())
511+
.attr("bucketNum", immutableBuckets.count())
511512
.log("Merge bucket snapshot finish");
512513

513514
stats.recordSuccessEvent(BucketDelayedMessageIndexStats.Type.merge,
@@ -577,8 +578,7 @@ private synchronized CompletableFuture<Void> asyncMergeBucketSnapshot(List<Immut
577578
});
578579

579580
for (ImmutableBucket bucket : buckets) {
580-
immutableBuckets.asMapOfRanges()
581-
.remove(Range.closed(bucket.startLedgerId, bucket.endLedgerId));
581+
immutableBuckets.remove(Range.closed(bucket.startLedgerId, bucket.endLedgerId));
582582
}
583583
}
584584
});
@@ -644,7 +644,7 @@ public synchronized NavigableSet<Position> getScheduledMessages(int maxMessages)
644644
SnapshotKey snapshotKey = new SnapshotKey(ledgerId, entryId);
645645

646646
ImmutableBucket bucket = snapshotSegmentLastIndexMap.get(snapshotKey);
647-
if (bucket != null && immutableBuckets.asMapOfRanges().containsValue(bucket)) {
647+
if (bucket != null && immutableBuckets.containsValue(bucket)) {
648648
// All message of current snapshot segment are scheduled, try load next snapshot segment
649649
if (bucket.merging) {
650650
log.info()
@@ -674,8 +674,7 @@ public synchronized NavigableSet<Position> getScheduledMessages(int maxMessages)
674674
synchronized (BucketDelayedDeliveryTracker.this) {
675675
this.snapshotSegmentLastIndexMap.remove(snapshotKey);
676676
if (CollectionUtils.isEmpty(indexList)) {
677-
immutableBuckets.asMapOfRanges()
678-
.remove(Range.closed(bucket.startLedgerId, bucket.endLedgerId));
677+
immutableBuckets.remove(Range.closed(bucket.startLedgerId, bucket.endLedgerId));
679678
bucket.asyncDeleteBucketSnapshot(stats);
680679
return;
681680
}
@@ -807,13 +806,9 @@ public synchronized boolean containsMessage(long ledgerId, long entryId) {
807806
}
808807

809808
public Map<String, TopicMetricBean> genTopicMetricMap() {
810-
stats.recordNumOfBuckets(immutableBuckets.asMapOfRanges().size() + 1);
809+
stats.recordNumOfBuckets((int) (immutableBuckets.count() + 1));
811810
stats.recordDelayedMessageIndexLoaded(this.sharedBucketPriorityQueue.size() + this.lastMutableBucket.size());
812-
MutableLong totalSnapshotLength = new MutableLong();
813-
immutableBuckets.asMapOfRanges().values().forEach(immutableBucket -> {
814-
totalSnapshotLength.add(immutableBucket.getSnapshotLength());
815-
});
816-
stats.recordBucketSnapshotSizeBytes(totalSnapshotLength.longValue());
811+
stats.recordBucketSnapshotSizeBytes(immutableBuckets.totalSnapshotLength());
817812
return stats.genTopicMetricMap();
818813
}
819814
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.pulsar.broker.delayed.bucket;
20+
21+
import com.google.common.collect.Range;
22+
import com.google.common.collect.RangeMap;
23+
import com.google.common.collect.TreeRangeMap;
24+
import java.util.Map;
25+
import java.util.concurrent.atomic.AtomicLong;
26+
27+
/**
28+
* Index of {@link ImmutableBucket}s keyed by ledger-ID {@link Range}.
29+
*
30+
* <p>The underlying {@link TreeRangeMap} is not thread-safe. All mutations must be
31+
* performed under an external lock (for example {@code synchronized(tracker)}).
32+
*
33+
* <p>To support lock-free stats reads, this class maintains cached counters for:
34+
* <ul>
35+
* <li>number of buckets ({@link #count()})</li>
36+
* <li>total snapshot length ({@link #totalSnapshotLength()})</li>
37+
* </ul>
38+
*
39+
* <p>Note that {@link TreeRangeMap#put} automatically removes any existing entries
40+
* whose ranges overlap with the inserted range. Because these removals are implicit,
41+
* counter updates cannot be derived solely from the caller's operation. Therefore
42+
* {@link #put(Range, ImmutableBucket)} and {@link #recomputeCounters()} rebuild the
43+
* counters from the actual map state.
44+
*/
45+
class ImmutableBucketIndex {
46+
47+
private final TreeRangeMap<Long, ImmutableBucket> map = TreeRangeMap.create();
48+
49+
private final AtomicLong count = new AtomicLong();
50+
private final AtomicLong totalSnapshotLength = new AtomicLong();
51+
52+
/**
53+
* Adds a bucket to the index.
54+
*
55+
* <p>If the supplied range overlaps existing entries, {@link TreeRangeMap}
56+
* removes them automatically. Counters are therefore recomputed from the
57+
* resulting map state.
58+
*/
59+
void put(Range<Long> range, ImmutableBucket bucket) {
60+
map.put(range, bucket);
61+
recomputeCounters();
62+
}
63+
64+
/**
65+
* Removes the bucket associated with the given range.
66+
*
67+
* <p>The supplied range is expected to exactly match an existing entry.
68+
*/
69+
void remove(Range<Long> range) {
70+
ImmutableBucket removed = map.asMapOfRanges().remove(range);
71+
if (removed != null) {
72+
count.decrementAndGet();
73+
totalSnapshotLength.addAndGet(-removed.getSnapshotLength());
74+
}
75+
}
76+
77+
long count() {
78+
return count.get();
79+
}
80+
81+
long totalSnapshotLength() {
82+
return totalSnapshotLength.get();
83+
}
84+
85+
ImmutableBucket get(long key) {
86+
return map.get(key);
87+
}
88+
89+
RangeMap<Long, ImmutableBucket> subRangeMap(Range<Long> range) {
90+
return map.subRangeMap(range);
91+
}
92+
93+
Map<Range<Long>, ImmutableBucket> asMapOfRanges() {
94+
return map.asMapOfRanges();
95+
}
96+
97+
boolean containsValue(ImmutableBucket bucket) {
98+
return map.asMapOfRanges().containsValue(bucket);
99+
}
100+
101+
/**
102+
* Rebuilds cached counters from the current map state.
103+
*
104+
* <p>This method is useful after bulk updates or whenever the map state is
105+
* considered the source of truth.
106+
*/
107+
void recomputeCounters() {
108+
Map<Range<Long>, ImmutableBucket> ranges = map.asMapOfRanges();
109+
110+
count.set(ranges.size());
111+
112+
long snapshotLength = 0;
113+
for (ImmutableBucket bucket : ranges.values()) {
114+
snapshotLength += bucket.getSnapshotLength();
115+
}
116+
totalSnapshotLength.set(snapshotLength);
117+
}
118+
}

pulsar-broker/src/test/java/org/apache/pulsar/broker/delayed/bucket/DelayedIndexQueueTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,29 @@ public void tripleLongPriorityDelayedIndexQueueTest() {
152152

153153
Assert.assertEquals(10, count);
154154
}
155+
156+
@Test
157+
public void testTripleLongPriorityDelayedIndexQueueDrainTo() {
158+
@Cleanup
159+
TripleLongPriorityQueue queue = new TripleLongPriorityQueue();
160+
queue.add(30, 3, 3);
161+
queue.add(10, 1, 1);
162+
queue.add(20, 2, 2);
163+
queue.add(10, 1, 2);
164+
165+
TripleLongPriorityDelayedIndexQueue delayedIndexQueue =
166+
TripleLongPriorityDelayedIndexQueue.wrap(queue);
167+
168+
List<long[]> result = new ArrayList<>();
169+
delayedIndexQueue.drainTo((ts, lid, eid) -> result.add(new long[]{ts, lid, eid}));
170+
171+
Assert.assertTrue(delayedIndexQueue.isEmpty());
172+
Assert.assertEquals(result.size(), 4);
173+
174+
// Verify sorted order: (10,1,1) < (10,1,2) < (20,2,2) < (30,3,3)
175+
Assert.assertEquals(result.get(0), new long[]{10, 1, 1});
176+
Assert.assertEquals(result.get(1), new long[]{10, 1, 2});
177+
Assert.assertEquals(result.get(2), new long[]{20, 2, 2});
178+
Assert.assertEquals(result.get(3), new long[]{30, 3, 3});
179+
}
155180
}

0 commit comments

Comments
 (0)