From 33afc6d7096f3829421273a21ea45d0d0fca0f0a Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:09:05 +0200 Subject: [PATCH 1/2] Docs: warn that negativeAcknowledge redelivery counter resets on dispatcher close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add caution admonitions in three sections of concepts-messaging.md: 1. Negative acknowledgment: the redelivery counter is in-memory only and resets on bundle unload, broker restart, topic unload, or consumer disconnect — making maxRedeliverCount unreliable and the DLQ potentially unreachable. 2. Retry letter topic: clarify that reconsumeLater + enableRetry(true) is the only mechanism that guarantees maxRedeliverCount is honored. 3. Dead letter topic: warn that maxRedeliverCount may not be honored without enableRetry(true). Closes apache/pulsar#25533 Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/concepts-messaging.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/concepts-messaging.md b/docs/concepts-messaging.md index 53f3cf37429d..039392486761 100644 --- a/docs/concepts-messaging.md +++ b/docs/concepts-messaging.md @@ -156,6 +156,16 @@ If batching is enabled, all messages in one batch are redelivered to the consume ::: +:::caution Redelivery counter is not persisted + +With `negativeAcknowledge`, the redelivery counter is kept **only in memory**. It resets to zero whenever the broker closes the subscription dispatcher — which happens on broker restart, bundle unload/rebalance, topic unload, or consumer disconnect. + +As a result, `DeadLetterPolicy.maxRedeliverCount` **may never be reached** and failing messages can be redelivered indefinitely without reaching the [dead letter topic](#dead-letter-topic). + +If your application needs a reliable retry limit, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)` instead — the retry count is persisted as a message property and survives all of the above events. + +::: + ### Acknowledgment timeout :::note @@ -315,7 +325,7 @@ consumer.reconsumeLater(msg, customProperties, 3, TimeUnit.SECONDS); :::note * Currently, retry letter topic is enabled in Shared subscription types. -* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a large number of retries with a configurable retry interval. Because messages in the retry letter topic are persisted to BookKeeper, while messages that need to be retried due to negative acknowledgment are cached on the client side. +* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a reliable retry limit. Messages in the retry letter topic have their retry count persisted as a message property, while the redelivery counter used by `negativeAcknowledge` is kept only in memory and [can be reset unexpectedly](#negative-acknowledgment). This makes `reconsumeLater` with `enableRetry(true)` the only mechanism that guarantees `maxRedeliverCount` is honored and the [dead letter topic](#dead-letter-topic) is eventually reached. ::: @@ -393,6 +403,12 @@ Consumer consumer = pulsarClient.newConsumer(Schema.BYTES) Dead letter topic serves message redelivery, which is triggered by [acknowledgment timeout](#acknowledgment-timeout) or [negative acknowledgment](#negative-acknowledgment) or [retry letter topic](#retry-letter-topic). +:::caution maxRedeliverCount may not be honored with negativeAcknowledge + +Without `enableRetry(true)`, the redelivery counter that drives `maxRedeliverCount` is not persisted and [can be reset unexpectedly](#negative-acknowledgment). To guarantee that failing messages eventually reach the dead letter topic, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)`. + +::: + :::note Currently, dead letter topic is enabled in Shared and Key_Shared subscription types. From 73be0b434a561308c5f676014957aba04b355072 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:26:17 +0200 Subject: [PATCH 2/2] Propagate nack redelivery counter warnings to supported versioned docs Apply the same caution admonitions to version-3.0.x, version-4.0.x, and version-4.2.x via docs-tool.sh apply_last_commit_to_versioned_docs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../version-3.0.x/concepts-messaging.md | 18 +++++++++++++++++- .../version-4.0.x/concepts-messaging.md | 18 +++++++++++++++++- .../version-4.2.x/concepts-messaging.md | 18 +++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/versioned_docs/version-3.0.x/concepts-messaging.md b/versioned_docs/version-3.0.x/concepts-messaging.md index ab4018910244..0dc3661f6ae1 100644 --- a/versioned_docs/version-3.0.x/concepts-messaging.md +++ b/versioned_docs/version-3.0.x/concepts-messaging.md @@ -150,6 +150,16 @@ If batching is enabled, all messages in one batch are redelivered to the consume ::: +:::caution Redelivery counter is not persisted + +With `negativeAcknowledge`, the redelivery counter is kept **only in memory**. It resets to zero whenever the broker closes the subscription dispatcher — which happens on broker restart, bundle unload/rebalance, topic unload, or consumer disconnect. + +As a result, `DeadLetterPolicy.maxRedeliverCount` **may never be reached** and failing messages can be redelivered indefinitely without reaching the [dead letter topic](#dead-letter-topic). + +If your application needs a reliable retry limit, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)` instead — the retry count is persisted as a message property and survives all of the above events. + +::: + ### Acknowledgment timeout :::note @@ -300,7 +310,7 @@ consumer.reconsumeLater(msg, customProperties, 3, TimeUnit.SECONDS); :::note * Currently, retry letter topic is enabled in Shared subscription types. -* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a large number of retries with a configurable retry interval. Because messages in the retry letter topic are persisted to BookKeeper, while messages that need to be retried due to negative acknowledgment are cached on the client side. +* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a reliable retry limit. Messages in the retry letter topic have their retry count persisted as a message property, while the redelivery counter used by `negativeAcknowledge` is kept only in memory and [can be reset unexpectedly](#negative-acknowledgment). This makes `reconsumeLater` with `enableRetry(true)` the only mechanism that guarantees `maxRedeliverCount` is honored and the [dead letter topic](#dead-letter-topic) is eventually reached. ::: @@ -364,6 +374,12 @@ Consumer consumer = pulsarClient.newConsumer(Schema.BYTES) Dead letter topic serves message redelivery, which is triggered by [acknowledgment timeout](#acknowledgment-timeout) or [negative acknowledgment](#negative-acknowledgment) or [retry letter topic](#retry-letter-topic). +:::caution maxRedeliverCount may not be honored with negativeAcknowledge + +Without `enableRetry(true)`, the redelivery counter that drives `maxRedeliverCount` is not persisted and [can be reset unexpectedly](#negative-acknowledgment). To guarantee that failing messages eventually reach the dead letter topic, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)`. + +::: + :::note Currently, dead letter topic is enabled in Shared and Key_Shared subscription types. diff --git a/versioned_docs/version-4.0.x/concepts-messaging.md b/versioned_docs/version-4.0.x/concepts-messaging.md index ec8101ebe187..bcc213ce5998 100644 --- a/versioned_docs/version-4.0.x/concepts-messaging.md +++ b/versioned_docs/version-4.0.x/concepts-messaging.md @@ -156,6 +156,16 @@ If batching is enabled, all messages in one batch are redelivered to the consume ::: +:::caution Redelivery counter is not persisted + +With `negativeAcknowledge`, the redelivery counter is kept **only in memory**. It resets to zero whenever the broker closes the subscription dispatcher — which happens on broker restart, bundle unload/rebalance, topic unload, or consumer disconnect. + +As a result, `DeadLetterPolicy.maxRedeliverCount` **may never be reached** and failing messages can be redelivered indefinitely without reaching the [dead letter topic](#dead-letter-topic). + +If your application needs a reliable retry limit, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)` instead — the retry count is persisted as a message property and survives all of the above events. + +::: + ### Acknowledgment timeout :::note @@ -313,7 +323,7 @@ consumer.reconsumeLater(msg, customProperties, 3, TimeUnit.SECONDS); :::note * Currently, retry letter topic is enabled in Shared subscription types. -* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a large number of retries with a configurable retry interval. Because messages in the retry letter topic are persisted to BookKeeper, while messages that need to be retried due to negative acknowledgment are cached on the client side. +* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a reliable retry limit. Messages in the retry letter topic have their retry count persisted as a message property, while the redelivery counter used by `negativeAcknowledge` is kept only in memory and [can be reset unexpectedly](#negative-acknowledgment). This makes `reconsumeLater` with `enableRetry(true)` the only mechanism that guarantees `maxRedeliverCount` is honored and the [dead letter topic](#dead-letter-topic) is eventually reached. ::: @@ -391,6 +401,12 @@ Consumer consumer = pulsarClient.newConsumer(Schema.BYTES) Dead letter topic serves message redelivery, which is triggered by [acknowledgment timeout](#acknowledgment-timeout) or [negative acknowledgment](#negative-acknowledgment) or [retry letter topic](#retry-letter-topic). +:::caution maxRedeliverCount may not be honored with negativeAcknowledge + +Without `enableRetry(true)`, the redelivery counter that drives `maxRedeliverCount` is not persisted and [can be reset unexpectedly](#negative-acknowledgment). To guarantee that failing messages eventually reach the dead letter topic, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)`. + +::: + :::note Currently, dead letter topic is enabled in Shared and Key_Shared subscription types. diff --git a/versioned_docs/version-4.2.x/concepts-messaging.md b/versioned_docs/version-4.2.x/concepts-messaging.md index 53f3cf37429d..039392486761 100644 --- a/versioned_docs/version-4.2.x/concepts-messaging.md +++ b/versioned_docs/version-4.2.x/concepts-messaging.md @@ -156,6 +156,16 @@ If batching is enabled, all messages in one batch are redelivered to the consume ::: +:::caution Redelivery counter is not persisted + +With `negativeAcknowledge`, the redelivery counter is kept **only in memory**. It resets to zero whenever the broker closes the subscription dispatcher — which happens on broker restart, bundle unload/rebalance, topic unload, or consumer disconnect. + +As a result, `DeadLetterPolicy.maxRedeliverCount` **may never be reached** and failing messages can be redelivered indefinitely without reaching the [dead letter topic](#dead-letter-topic). + +If your application needs a reliable retry limit, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)` instead — the retry count is persisted as a message property and survives all of the above events. + +::: + ### Acknowledgment timeout :::note @@ -315,7 +325,7 @@ consumer.reconsumeLater(msg, customProperties, 3, TimeUnit.SECONDS); :::note * Currently, retry letter topic is enabled in Shared subscription types. -* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a large number of retries with a configurable retry interval. Because messages in the retry letter topic are persisted to BookKeeper, while messages that need to be retried due to negative acknowledgment are cached on the client side. +* Compared with negative acknowledgment, retry letter topic is more suitable for messages that require a reliable retry limit. Messages in the retry letter topic have their retry count persisted as a message property, while the redelivery counter used by `negativeAcknowledge` is kept only in memory and [can be reset unexpectedly](#negative-acknowledgment). This makes `reconsumeLater` with `enableRetry(true)` the only mechanism that guarantees `maxRedeliverCount` is honored and the [dead letter topic](#dead-letter-topic) is eventually reached. ::: @@ -393,6 +403,12 @@ Consumer consumer = pulsarClient.newConsumer(Schema.BYTES) Dead letter topic serves message redelivery, which is triggered by [acknowledgment timeout](#acknowledgment-timeout) or [negative acknowledgment](#negative-acknowledgment) or [retry letter topic](#retry-letter-topic). +:::caution maxRedeliverCount may not be honored with negativeAcknowledge + +Without `enableRetry(true)`, the redelivery counter that drives `maxRedeliverCount` is not persisted and [can be reset unexpectedly](#negative-acknowledgment). To guarantee that failing messages eventually reach the dead letter topic, use [`reconsumeLater`](#retry-letter-topic) with `enableRetry(true)`. + +::: + :::note Currently, dead letter topic is enabled in Shared and Key_Shared subscription types.