Thomas MacKenzie
09/19/2025, 1:00 AMpreciseDispatcherFlowControl?RunLLM
09/19/2025, 1:00 AMpreciseDispatcherFlowControl broker configuration enables more accurate dispatch rate limiting in Apache Pulsar by considering the average number of messages per entry when reading from BookKeeper. This helps prevent over-delivery, especially when batching is enabled, by calculating the number of entries to read as number of messages to read / average messages per entry instead of simply counting entries as messages. Only one of preciseDispatcherFlowControl or dispatchThrottlingOnBatchMessageEnabled should be enabled at a time, as they are mutually exclusive.
For example, with a rate limit of 10 messages/sec and an average of 6 messages per entry, the broker reads 2 entries per second if preciseDispatcherFlowControl is enabled, closely matching the quota. Without it, the broker may read more entries and exceed the quota. See Pulsar Throttling Configurations for details.
Would you like more detail?
Sources:
- Pulsar Throttling Configurations
- Pulsar Throttling Limitations š 0 button š 0 button š” Hint: Mention @runllm in the thread for followups.Thomas MacKenzie
09/19/2025, 1:24 AMpreciseDispatcherFlowControl lead to a local backlog?RunLLM
09/19/2025, 1:24 AMpreciseDispatcherFlowControl can lead to a local backlog if the broker underestimates the number of messages per entry, causing it to read fewer entries than needed to satisfy consumer permits. This may result in messages accumulating locally because the dispatch rate is throttled more precisely and may not keep up with consumer demand if the average is underestimated or not yet initialized, especially on new topics or subscriptions. See Pulsar Throttling Configurations and related PR discussion.
Would you like more detail?
Sources:
- Pulsar Throttling Configurations
- PR #18581 discussion š 0 button š 0 button š” Hint: Mention @runllm in the thread for followups.Thomas MacKenzie
09/19/2025, 1:39 AMpreciseDispatcherFlowControl a dynamic configuration meaning it can be changed without restarting the broker?RunLLM
09/19/2025, 1:39 AMpreciseDispatcherFlowControl is a dynamic configuration, so it can be changed at runtime without restarting the broker. You can update it using the dynamic config update APIs or CLI tools like pulsar-admin brokers update-dynamic-config. See Pulsar Throttling Configurations and Admin API Brokers.
Would you like more detail?
Sources:
- Pulsar Throttling Configurations š 0 button š 0 button š” Hint: Mention @runllm in the thread for followups.Lari Hotari
09/19/2025, 8:44 AMpreciseDispatcherFlowControl and I think it should be Pulsar's default. bumping dispatcherMaxReadBatchSize to 1000 could be useful if Pulsar isn't dispatching fast enough.Lari Hotari
09/19/2025, 8:59 AMThomas MacKenzie
09/19/2025, 2:25 PMpreciseDispatcherFlowControl enabled but I'm seeing a backlog building slowly (production)
Messages are produced by a job hourly, sending messages by batch and we have a dispatch rate (16 partition, rate at 5 / second).
Once it has finished reading the messages over the course of ~45 mins, there is 100+ messages still in the local backlog each time.
I'm going to look at dispatcherMaxReadBatchSize , thanks! Would setting it as a dynamic config work (trying to prevent restarting all the brokers)?
pulsar-admin brokers update-dynamic-config --config dispatcherMaxReadBatchSize --value 1000
Also we are on 4.0.6Thomas MacKenzie
09/20/2025, 1:03 AMlocal backlog in the dashboard) is off. Looking at the pulsar admin command, I see a backlog for a partition-0, but the consumers are idle as if there is nothing else to read from the topics after they are done with the bacth that was just sentLari Hotari
09/20/2025, 8:15 AMLari Hotari
09/20/2025, 8:15 AMLari Hotari
09/20/2025, 8:16 AMThomas MacKenzie
09/20/2025, 8:22 AMThomas MacKenzie
09/20/2025, 8:31 AM__compaction which I understand is related to Pulsar reclaiming space (deleted of acked messages - ledgers)
"__compaction" : {
"msgRateOut" : 0.0,
"msgThroughputOut" : 0.0,
"bytesOutCounter" : 199818,
"msgOutCounter" : 5190,
"msgRateRedeliver" : 0.0,
"messageAckRate" : 0.0,
"chunkedMessageRate" : 0.0,
"msgBacklog" : 716,
"backlogSize" : 5874706,
"earliestMsgPublishTimeInBacklog" : 0,
"msgBacklogNoDelayed" : 716,
"blockedSubscriptionOnUnackedMsgs" : false,
"msgDelayed" : 0,
"msgInReplay" : 0,
"unackedMessages" : 0,
"type" : "Exclusive",
"msgRateExpired" : 0.0,
"totalMsgExpired" : 0,
"lastExpireTimestamp" : 0,
"lastConsumedFlowTimestamp" : 0,
"lastConsumedTimestamp" : 0,
"lastAckedTimestamp" : 0,
"lastMarkDeleteAdvancedTimestamp" : 0,
"consumers" : [ ],
"isDurable" : true,
"isReplicated" : false,
"allowOutOfOrderDelivery" : false,
"consumersAfterMarkDeletePosition" : { },
"drainingHashesCount" : 0,
"drainingHashesClearedTotal" : 0,
"drainingHashesUnackedMessages" : 0,
"nonContiguousDeletedMessagesRanges" : 0,
"nonContiguousDeletedMessagesRangesSerializedSize" : 0,
"delayedMessageIndexSizeInBytes" : 0,
"subscriptionProperties" : { },
"filterProcessedMsgCount" : 0,
"filterAcceptedMsgCount" : 0,
"filterRejectedMsgCount" : 0,
"filterRescheduledMsgCount" : 0,
"dispatchThrottledMsgEventsBySubscriptionLimit" : 0,
"dispatchThrottledBytesEventsBySubscriptionLimit" : 0,
"dispatchThrottledMsgEventsByTopicLimit" : 596,
"dispatchThrottledBytesEventsByTopicLimit" : 0,
"dispatchThrottledMsgEventsByBrokerLimit" : 0,
"dispatchThrottledBytesEventsByBrokerLimit" : 0,
"replicated" : false,
"durable" : true
},
This subscription also has a backlog, but does not seems to go back down. Looking at the compaction status of one of the partitions I can this back
Error in compaction
null
Reason: Error compacting: org.apache.pulsar.common.util.FutureUtil$LowOverheadTimeoutException: Timeout
So I'm wondering if errors from compaction creates a backlog for the __compaction subscription as well. There are no more consumers, I tried to unsubscribe, hoping to clean this back log but I got this error
Stacktrace:
org.apache.pulsar.client.admin.PulsarAdminException$ServerSideErrorException:
--- An unexpected error occurred in the server ---
Message: Timeout
Stacktrace:
org.apache.pulsar.common.util.FutureUtil$LowOverheadTimeoutException: Timeout
at org.apache.pulsar.compaction.PublishingOrderCompactor.phaseOneLoop(...)(Unknown Source)
at org.apache.pulsar.client.admin.internal.BaseResource.getApiException(BaseResource.java:272)
at org.apache.pulsar.client.admin.internal.BaseResource$4.failed(BaseResource.java:237)
at org.glassfish.jersey.client.JerseyInvocation$1.failed(JerseyInvocation.java:898)
at org.glassfish.jersey.client.JerseyInvocation$1.completed(JerseyInvocation.java:879)
at org.glassfish.jersey.client.ClientRuntime.processResponse(ClientRuntime.java:232)
at org.glassfish.jersey.client.ClientRuntime.access$200(ClientRuntime.java:62)
at org.glassfish.jersey.client.ClientRuntime$2.lambda$response$0(ClientRuntime.java:176)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:288)
at org.glassfish.jersey.client.ClientRuntime$2.response(ClientRuntime.java:176)
at org.apache.pulsar.client.admin.internal.http.AsyncHttpConnector.lambda$apply$1(AsyncHttpConnector.java:278)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.complete(Unknown Source)
at org.apache.pulsar.client.admin.internal.http.AsyncHttpConnector.lambda$retryOperation$4(AsyncHttpConnector.java:337)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.cancel(Unknown Source)
at org.apache.pulsar.client.admin.internal.http.AsyncHttpConnector.lambda$executeRequest$10(AsyncHttpConnector.java:406)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.complete(Unknown Source)
at com.spotify.futures.ConcurrencyReducer.lambda$invoke$0(ConcurrencyReducer.java:173)
at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.complete(Unknown Source)
at org.asynchttpclient.netty.NettyResponseFuture.loadContent(NettyResponseFuture.java:222)
at org.asynchttpclient.netty.NettyResponseFuture.done(NettyResponseFuture.java:257)
at org.asynchttpclient.netty.handler.AsyncHttpClientHandler.finishUpdate(AsyncHttpClientHandler.java:241)
at org.asynchttpclient.netty.handler.HttpHandler.handleChunk(HttpHandler.java:113)
at org.asynchttpclient.netty.handler.HttpHandler.handleRead(HttpHandler.java:142)
at org.asynchttpclient.netty.handler.AsyncHttpClientHandler.channelRead(AsyncHttpClientHandler.java:78)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:796)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:732)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:658)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: javax.ws.rs.InternalServerErrorException: HTTP 500 {"reason":"\n --- An unexpected error occurred in the server ---\n\nMessage: Timeout\n\nStacktrace:\n\norg.apache.pulsar.common.util.FutureUtil$LowOverheadTimeoutException: Timeout\n\tat org.apache.pulsar.compaction.PublishingOrderCompactor.phaseOneLoop(...)(Unknown Source)\n"}
at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:977)
at org.glassfish.jersey.client.JerseyInvocation.access$700(JerseyInvocation.java:82)
... 64 moreLari Hotari
09/20/2025, 8:32 AMThomas MacKenzie
09/20/2025, 8:35 AMLari Hotari
09/20/2025, 8:37 AMThomas MacKenzie
09/20/2025, 8:37 AMLari Hotari
09/20/2025, 8:37 AMThomas MacKenzie
09/20/2025, 8:37 AMLari Hotari
09/20/2025, 8:38 AMThomas MacKenzie
09/20/2025, 8:38 AMLari Hotari
09/20/2025, 8:38 AMLari Hotari
09/20/2025, 8:39 AM