Skip to main content

Poison Message Handling

Poison Message Handling detects messages that repeatedly crash or block a consumer and quarantines them — usually to a dead-letter queue — so one bad message cannot stall the whole pipeline. It hinges on bounded retries, delivery-count tracking, and idempotency for safe recovery.

Type
Resilience
When to Use
Repeated Consumer Failure, Queue Blocking, Malformed Events, At Least Once Delivery

A poison message is one that a consumer cannot process successfully no matter how many times it tries — it throws an exception, fails validation, or crashes the consumer on every delivery. In an at-least-once system the broker redelivers unacknowledged messages, so a poison message gets redelivered endlessly, blocking the queue (in FIFO/ordered systems) or burning CPU in a retry loop. Poison Message Handling is the discipline of detecting such messages and getting them out of the processing path.

How It Works

The core technique is to count attempts and act on a threshold. Each redelivery increments a counter (a broker-maintained delivery count, a header the consumer stamps, or a tracking store). When the count exceeds a limit, the consumer stops retrying the message and instead:

  • Dead-letters it — moves it to a dead-letter queue for later inspection (the most common resolution).
  • Quarantines or logs it — persists the payload and error somewhere durable, then acknowledges so the queue unblocks.
  • Skips it — acknowledges and drops, acceptable only when loss is tolerable.

Crucially, handling must guard against partial processing: if a message does some work before failing, idempotency or transactional consumption is needed so dead-lettering does not leave half-applied side effects. Some consumers also wrap processing in a circuit-breaker-like check to avoid a poison message taking down throughput.

When to Use It

Poison message handling is essential for every durable, at-least-once queue or event stream: SQS, RabbitMQ, Kafka, Azure Service Bus, Pub/Sub. Ordered queues need it most, since a single stuck message halts everything behind it. It is the detection half of the pattern whose destination is usually a dead-letter queue.

Trade-offs

The key tension is transient vs. poison: too few retries dead-letter recoverable messages; too many keep a truly poison message blocking the queue for a long time. Distinguishing the two reliably is hard, so most systems use a generous-but-bounded retry count plus DLQ monitoring. Tracking delivery counts adds state, and improper acknowledgment ordering can drop healthy messages. Quarantined messages need a human or automated remediation path, or they are simply lost.

Related Patterns

Poison message handling routes to a dead-letter-queue, builds on bounded retry-with-backoff, needs idempotency-key for safe partial-processing recovery, and may use circuit-breaker-style protection to preserve throughput.

Example

Consumer logic with a delivery-count check:

if message.deliveryCount > 5:
  deadLetter(message, reason=lastError)
  ack(message)              // unblock the queue
else:
  try: process(message); ack(message)
  catch e: nack(message)    // redeliver, count increments

The queue keeps moving even when a malformed event would otherwise jam it.