top of page

Poison Messages

  • Writer: Pradeep P
    Pradeep P
  • 2 days ago
  • 3 min read

Layer 2 · Post 12 of 14

Layer 2 — Communication · Post 96 of 119

A payload that can never succeed will retry forever unless you isolate it (DLQ, skip, poison-pill handling).

What you'll learn

  • What makes a message "poison" versus "the dependency is down"

  • Redelivery limits, dead-letter queues, and why infinite retry is an outage

  • How this interacts with at-least-once delivery and head-of-line blocking

The idea in one minute

A poison message (poison pill) is a record the consumer cannot process successfully, no matter how often it retries: bad JSON, unknown schema version, a user id that violates a new constraint, a payload that always throws.

At-least-once delivery will keep handing it over. Each attempt may crash the worker, roll back, nack, and — in a queue that is FIFO or a single-threaded consumer — block everything behind it.

[ poison ][ good ][ good ][ good ]
     ^
     consumer dies / nacks / retries
     good messages wait  (head-of-line)

This is different from "Postgres was down for 30 seconds." That should retry. Poison is the message itself is the problem.

Why it matters

Queues exist to absorb spikes (Post 19). They also absorb permanent failures unless you bound retries. A one-line schema bug can stall a whole payment pipeline at 3 a.m.

Interviews: they want a dead-letter queue (DLQ) and an operator path, not "we retry with exponential backoff forever" (Post 28). Backoff is for transient errors.

How it works

Classify errors.

  • Transient: 503, timeout, lock timeout. Retry with backoff. Do not count these as poison immediately.

  • Permanent: 400-from-your-own-parser, ValidationError, NULL in a NOT NULL column after a deploy. Do not retry without changing the payload or the code.

Bound redeliveries. SQS maxReceiveCount, Kafka consumer plus your own retry topic, RabbitMQ dead-letter after N nacks. After N, move the message off the hot path.

DLQ (dead-letter queue). A separate queue/topic for failed payloads. Alert on DLQ depth. Tooling to replay after you ship a fix, or to drop after you confirm junk.

Poison-pill in Kafka. A bad record in a partition blocks that partition's consumer if you throw before committing the offset. Catch, log, skip or write to a DLQ topic, then commit. Skipping is a product decision (loss vs stall).

Do not DLQ on "I don't know." A bug that throws on every message will fill the DLQ with your entire backlog. Circuit-break the consumer; fix the code; replay.

Idempotency still applies. Replay from DLQ will duplicate side effects if you did not key them.

A simple example

Order worker expects {"orderId": "abc", "cents": 1999}. A client ships {"order_id": "abc", "cents": "19.99"}. Parser throws. SQS redelivers until the queue's retention. Visibility timeout fires, another worker dies, pager storms.

Fix: after 5 receives, SQS moves it to orders-dlq. Dashboard pages once. Engineer maps the field, ships a more tolerant parser, replays that one message. The live queue kept draining good orders.

If the consumer was single-threaded FIFO and you retried without a receive limit, no later order in that group would ship. That is poison plus head-of-line (next post).

Common mistakes

DLQ with no alarm. Silent graveyard. Customers wait forever.

Treating all exceptions as poison. Database failover looks like poison for 10 seconds. Use error types.

Infinite in-memory retry in the client while the broker thinks you are fine. You still hold the message; you just burn CPU.

Schema registry "I'll just skip unknowns" without a DLQ. You dropped money. Log and park.

How this shows up in real systems

  • SQS redrive policy → DLQ. The textbook setup.

  • Kafka: ErrorHandlingDeserializer, retry topics (Spring), or manual skip; Confluent / MSK patterns.

  • Pub/Sub dead-letter topics, EventBridge DLQ, Azure poison queues.

  • Saga / workflow engines: a failed step parked for humans — same idea with a UI.

Recap

  • Poison = this payload will not succeed; retrying it forever is an outage.

  • Bound retries, DLQ, alert, replay after fix.

  • Next: even a slow (not poison) first item can stall the line — head-of-line blocking.

Layer 2 · Post 12 of 14

Comments


About Me

DSC_7604.jpg

Hi, I am a software engineer from Bangalore, India. Love spending time on gaming and photography. This website is where I will ocassionally throw what comes to my mind. Hope it is useful or at least entertaining to you. :)

 

  • Instagram
  • Facebook
  • Twitter
  • LinkedIn
  • YouTube
  • 500px

© 2023 by Going Places. Proudly created with Wix.com

bottom of page