Lesson 12 / 25

Failures, Retries and Dead-Letter Topics

Handle poison messages without blocking the partition.

One bad record must not stop the line

A partition is processed in order, so a record that always fails (a poison message: bad data, unexpected schema) can block everything behind it if you retry forever. Separate transient failures (a database timeout, which deserve a few retries with backoff) from permanent ones (malformed data, which retries cannot fix). Send permanent failures to a dead-letter topic (DLT) together with the error reason and original coordinates (topic, partition, offset), commit the offset, and continue. Monitor the DLT size and have a process to inspect and replay its records after fixing the cause. Where ordering matters, retry in place rather than moving the record elsewhere.

Routing a failure to a dead-letter topic (illustrative)

TransientError is retried by your code; anything else is considered permanent and parked with context in orders.dlt.

try:
    handle(msg)
except TransientError:
    raise                                  # let the retry policy handle it
except Exception as exc:                   # permanent: park it, keep going
    producer.produce("orders.dlt", key=msg.key(), value=msg.value(),
                     headers=[("error", str(exc).encode()),
                              ("src", f"{msg.topic()}[{msg.partition()}]@{msg.offset()}".encode())])
commit(msg)

Quick check: Why use a dead-letter topic?

  • To speed up the broker
  • To park permanently failing records so the partition keeps moving
  • To delete all errors silently
  • To increase partitions
Answer

To park permanently failing records so the partition keeps moving — Parked records can be inspected and replayed without blocking healthy traffic.