You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by pe...@apache.org on 2026/10/16 16:39:52 UTC
(pulsar) branch master updated: [fix][broker] Fix inflated rate stats of newly created consumers (#26928)
This is an automated email from the ASF dual-hosted git repository.
codelipenghui pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push:
new 7e7c916854ec [fix][broker] Fix inflated rate stats of newly created consumers (#26928)
7e7c916854ec is described below
commit 7e7c916854ec434c437054774a4a256b58a947fe
Author: Penghui Li <pe...@apache.org>
AuthorDate: Fri Oct 16 09:39:44 2026 -0700
[fix][broker] Fix inflated rate stats of newly created consumers (#26928)
---
.../org/apache/pulsar/broker/service/Consumer.java | 14 +++++---
.../pulsar/broker/stats/ConsumerStatsTest.java | 41 ++++++++++++++++++++++
2 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/Consumer.java b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/Consumer.java
index ac6b352386b6..a75aaaaa6e22 100644
--- a/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/Consumer.java
+++ b/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/Consumer.java
@@ -109,6 +109,7 @@ public class Consumer {
private volatile long firstConsumedFlowTimestamp;
private volatile long lastConsumedFlowTimestamp;
private Rate chunkedMessageRate;
+ private long lastRatesUpdateNanos = System.nanoTime();
// Represents how many messages we can safely send to the consumer without
// overflowing its receiving queue. The consumer will use Flow commands to
@@ -1179,10 +1180,15 @@ public class Consumer {
}
public void updateRates() {
- msgOut.calculateRate();
- chunkedMessageRate.calculateRate();
- msgRedeliver.calculateRate();
- messageAckRate.calculateRate();
+ long now = System.nanoTime();
+ // at least 1s, so the burst a new consumer receives while filling its receiver queue
+ // isn't extrapolated into a huge per-second rate
+ double periodInSeconds = Math.max((now - lastRatesUpdateNanos) / 1e9, 1);
+ lastRatesUpdateNanos = now;
+ msgOut.calculateRate(periodInSeconds);
+ chunkedMessageRate.calculateRate(periodInSeconds);
+ msgRedeliver.calculateRate(periodInSeconds);
+ messageAckRate.calculateRate(periodInSeconds);
stats.msgRateOut = msgOut.getRate();
stats.msgThroughputOut = msgOut.getValueRate();
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/stats/ConsumerStatsTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/stats/ConsumerStatsTest.java
index efbe792a90b7..6b1313a9827a 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/stats/ConsumerStatsTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/stats/ConsumerStatsTest.java
@@ -113,6 +113,9 @@ public class ConsumerStatsTest extends ProducerConsumerBase {
// wait for shutdown of the broker, this prevents flakiness which could be caused by metrics being
// unregistered asynchronously. This impacts the execution of the next test method if this would be happening.
conf.setBrokerShutdownTimeoutMs(5000L);
+ // only the tests update the rates, so the periodic stats task can't reset the rates they check
+ conf.setStatsUpdateInitialDelayInSecs(100000000);
+ conf.setStatsUpdateFrequencyInSecs(100000000);
return conf;
}
@@ -435,6 +438,44 @@ public class ConsumerStatsTest extends ProducerConsumerBase {
}
}
+ @Test
+ public void testNewConsumerRateIsNotInflatedByItsInitialBurst() throws Exception {
+ final String topicName = newUniqueName("persistent://my-property/my-ns/new-consumer-rate");
+ final int messages = 100;
+ @Cleanup
+ Producer<byte[]> producer = pulsarClient.newProducer()
+ .topic(topicName)
+ .enableBatching(false)
+ .create();
+ for (int i = 0; i < messages; i++) {
+ producer.send(new byte[1024]);
+ }
+ admin.topics().createSubscription(topicName, "sub", MessageId.earliest);
+ // reload the topic, so that it is as young as the consumer
+ admin.topics().unload(topicName);
+
+ // like the compaction reader, a new consumer receives a burst as soon as it sends its flow permits
+ long consumerCreationLowerBound = System.nanoTime();
+ @Cleanup
+ Consumer<byte[]> consumer = pulsarClient.newConsumer()
+ .topic(topicName)
+ .subscriptionName("sub")
+ .subscribe();
+ org.apache.pulsar.broker.service.Consumer brokerConsumer = pulsar.getBrokerService()
+ .getTopicReference(topicName).orElseThrow().getSubscription("sub").getConsumers().get(0);
+ Awaitility.await().until(() -> brokerConsumer.getMsgOutCounter() == messages);
+ pulsar.getBrokerService().updateRates();
+ double maxPeriodSeconds = Math.max((System.nanoTime() - consumerCreationLowerBound) / 1e9, 1);
+
+ // a per-second rate can't exceed what the consumer received during the period
+ long bytes = brokerConsumer.getBytesOutCounter();
+ ConsumerStatsImpl stats = brokerConsumer.getStats();
+ assertThat(stats.msgRateOut).as("msgRateOut")
+ .isBetween(messages / maxPeriodSeconds, (double) messages);
+ assertThat(stats.msgThroughputOut).as("msgThroughputOut")
+ .isBetween(bytes / maxPeriodSeconds, (double) bytes);
+ }
+
@Test
public void testAvgMessagesPerEntry() throws Exception {
conf.setAllowOverrideEntryFilters(true);