Consumers & Consumer Groups
Dead Letter Queues and Error Handling in Consumers
Retries, backoffs, and routing poison pills.
Introduction
When a consumer encounters a malformed or corrupt message (a "poison pill"), processing fails. A Dead Letter Queue (DLQ) is a dedicated topic where consumers route failed messages, allowing the pipeline to continue reading subsequent records without locking.
Why It Matters
Without error routing, a single malformed message will stall a consumer partition indefinitely. If the consumer fails to parse a message, retrying in a loop blocks the thread, building up lag across the entire system.
Real-World Analogy
Think of a customs inspection line. If an inspector finds a suspicious package with incorrect labeling, they don't halt the entire shipping lane and block all trucks behind it. Instead, they move the suspicious package to a side holding room (Dead Letter Queue) for investigation, letting the main line resume.
How It Works
The DLQ routing flow operates as follows:
- Consume: The consumer retrieves a batch of records.
- Error Detection: A record fails to deserialize or parse (e.g. invalid JSON schema).
- Publish to DLQ: The consumer catches the exception and publishes the malformed message payload along with header errors to a dedicated DLQ topic.
- Commit and Resume: The consumer commits the offset of the failed record, resuming the poll loop for the next message.
Internal Architecture
The DLQ is implemented as a standard Kafka topic. Error handlers write to this topic asynchronously. Senders include header metadata (e.g., x-exception-message, x-original-topic, x-original-partition) to aid debugging.
Visual Explanation
Below is a visual layout of error routing in a consumer parse loop:
Main Topic ──> [Consumer Parse Loop] ──(Success)──> Process Node
│
(Failure)
│
▼
[Publish to DLQ Topic] ──> Admin/Alerting Analysis
Practical Example
Below is a Java snippet demonstrating how to catch parse errors and route failed records to a DLQ topic:
Common Mistakes
- Routing to DLQ without committing the original offset: If you send the bad message to the DLQ but fail to commit the offset, the consumer will pull the exact same message in the next poll, getting stuck in an infinite retry loop.
- Using a blocking write for DLQ publishing without timeouts: Blocking on the DLQ producer write without safety bounds can cause the consumer to hang if the DLQ topic partition broker goes offline.
Quick Quiz
Q1: What is a "poison pill" message?
A malformed or corrupt record that continuously fails processing, preventing the consumer thread from progressing.
Q2: Why should you include error messages in record headers when routing to a DLQ?
To let SRE and developers inspect why the message failed parsing without needing to dig through application logs.
Scenario-Based Challenge
The Challenge: Handling transient database failures
Senders write logs. The consumer writes them to a SQL database. The database goes offline for 1 minute. Should you route these records to a DLQ?
Solution Tip: No. DLQs are for permanent code/data errors. For transient database connectivity failures, implement retries with backoffs, or pause consumer consumption using consumer.pause() to wait.
Debugging Exercise
Malformed records are piling up in your DLQ topic, but SRE reports they cannot find which consumer node routed them or what exception occurred.
The Fix: The DLQ producer code is routing raw keys and values without attaching metadata headers. Add headers detailing the source hostname, original topic-partition, and stack trace bytes.
Interview Questions
1. What is the difference between retry queues and dead letter queues?
Retry queues are for transient network/resource failures (retried later). DLQs are for permanent parsing or logic failures that require manual code reviews.
2. How do you avoid processing ordering issues when using a DLQ?
DLQ routing breaks strict partition order because subsequent events are processed while the failed event is held. If strict order is required, you must block the partition and alert, rather than routing to a DLQ.
Production Considerations
Define alerting thresholds on the DLQ topic log sizes. Set up automated scripts to re-inject messages after code deployments.