Implementing Dead Letter Queues
1. Understanding DLQ Pattern
| Aspect | Description | Detail |
|---|---|---|
| DLQ | Topic for failed messages | Quarantine |
| Goal | Don't block pipeline | Isolate poison |
| Recovery | Inspect, fix, replay | Manual/auto |
2. Creating DLQ Topic
| Aspect | Description | Detail |
|---|---|---|
| Naming | <topic>.dlq convention | Clear |
| Retention | Longer than source | Time to fix |
| Partitions | Match or fewer | Sufficient |
Example: Create DLQ
kafka-topics.sh --bootstrap-server localhost:9092 --create \
--topic orders.dlq --partitions 3 --replication-factor 3 \
--config retention.ms=1209600000
3. Routing Failed Messages
| Aspect | Description | Detail |
|---|---|---|
| Try/Catch | Catch processing error | Per record |
| Forward | Produce to DLQ | Original payload |
| Commit | Advance source offset | After DLQ ack |
Example: Route to DLQ
try { process(r); }
catch (Exception e) {
dlq.send(new ProducerRecord<>("orders.dlq", r.key(), r.value()));
}
4. Configuring DLQ in Streams
| Aspect | Description | Detail |
|---|---|---|
| Deser Handler | Send bad records to DLQ | Custom |
| Branch | Route invalid downstream | split() |
| Production Handler | Handle output errors | Custom |
Example: Branch invalid records
stream.split()
.branch((k,v) -> isValid(v), Branched.withConsumer(s -> s.to("orders.clean")))
.defaultBranch(Branched.withConsumer(s -> s.to("orders.dlq")));
5. Using DLQ in Connect
| Config | Description | Detail |
|---|---|---|
| errors.tolerance | all = route bad | Don't fail |
| errors.deadletterqueue. topic.name |
DLQ topic | Sink only |
| context.headers | Add error metadata | enable |
Example: Connect DLQ config
{"errors.tolerance":"all",
"errors.deadletterqueue.topic.name":"orders.dlq",
"errors.deadletterqueue.context.headers.enable":"true"}
6. Adding Error Metadata
| Header | Description | Detail |
|---|---|---|
| error.message | Exception text | Diagnose |
| error.class | Exception type | Categorize |
| source.offset | Original location | Trace |
Example: Add error headers
ProducerRecord<String,String> d = new ProducerRecord<>("orders.dlq", r.key(), r.value());
d.headers().add("error.message", e.getMessage().getBytes());
d.headers().add("error.class", e.getClass().getName().getBytes());
dlq.send(d);
7. Monitoring DLQ
| Metric | Description | Detail |
|---|---|---|
| DLQ Rate | Messages/sec to DLQ | Health signal |
| DLQ Size | Backlog count | Lag |
| Alert | Threshold breach | Page |
Example: Watch DLQ lag
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group dlq-monitor
8. Reprocessing DLQ Messages
| Step | Description | Detail |
|---|---|---|
| Consume DLQ | Read failed records | Inspect |
| Fix/Transform | Correct payload | Or code |
| Republish | Back to source topic | Retry |
Example: Replay DLQ
for (var r : dlqConsumer.poll(d)) {
String fixed = repair(r.value());
producer.send(new ProducerRecord<>("orders", r.key(), fixed));
}
9. Setting DLQ Retention
| Aspect | Description | Detail |
|---|---|---|
| Longer | Time to investigate | Days/weeks |
| retention.ms | Per-topic setting | Override |
| Balance | Disk vs recovery window | Tune |
Example: 14-day DLQ retention
kafka-configs.sh --bootstrap-server localhost:9092 --alter \
--entity-type topics --entity-name orders.dlq \
--add-config retention.ms=1209600000
10. Implementing Circuit Breaker
| Aspect | Description | Detail |
|---|---|---|
| Trip | Stop on high failure | Threshold |
| Pause | Hold consumption | consumer.pause() |
| Resume | After recovery probe | Half-open |