Back to Error Handling & Dead Letter Topics
Q6Question 6 of 6

Is a dead letter queue enough? Treating a DLQ as an incident-response mechanism

Key Concepts
  • A DLQ only isolates failures -- it does not fix, alert, or process anything by itself.
  • Without monitoring, a DLQ converts a loud failure (stuck partition) into a silent one (growing pile nobody sees).
  • Monitoring must alert on DLQ depth and growth rate, and page the owning team, not just appear on a dashboard.
  • Root-cause classification separates transient failures (replay after recovery) from permanent failures (fix or discard).
  • A written and tested replay/redrive procedure avoids improvising under pressure during an incident.
  • Clear ownership with a service-level agreement for draining the DLQ prevents shared responsibility becoming nobody's responsibility.
  • Treat a DLQ as an incident-response mechanism: alerts, runbooks, triage, ownership, and metrics.

Definition

A dead letter queue only isolates failures; handling them requires monitoring, classification, a replay procedure, and ownership, so treat it as an incident-response mechanism, not a config flag.
  • DLQ isolation -- a dead letter queue stops certain failures from blocking the main processing path, but it does not fix anything, alert anyone, or get the record processed.
  • Monitoring -- alerts on DLQ depth and growth rate that page the owning team; a sudden spike usually signals a dependency outage or bad producer format.
  • Root-cause classification -- sorting DLQ records into transient (replay after recovery), permanent (fix or discard), and poison (producer or schema fix).
  • Replay/redrive procedure -- a written, tested process for moving records back once the fix is deployed, with idempotent consumers and throttling.
  • Ownership -- a specific team responsible for watching, triaging, and clearing the DLQ with a service-level agreement.
  • Silent graveyard -- what a DLQ becomes without monitoring and ownership: failures accumulate unnoticed and business events are silently lost.

At National Bank of India, a dead letter queue is like the problem tray on the clerk's desk. Having a problem tray stops one bad form from freezing the whole line, which is good. But a problem tray is not a solution by itself. If nobody watches it, nobody is paged when it fills up, nobody sorts the forms, and no manager owns clearing it, then the tray quietly overflows and real customer transfers are silently lost. The tray only helps if there is a whole response process around it.

Understanding the Concepts

What does a DLQ actually do?

A DLQ takes a record the consumer could not process and moves it aside so the main partition keeps flowing. That is genuinely valuable. But notice what it does not do: it does not fix anything, it does not tell anyone, and it does not get the record processed. The failure is relocated, not resolved.

JAVA
package com.bank.dlq;

public class WhatDlqDoes {
    public static void main(String[] args) {
        System.out.println("What a DLQ DOES:");
        System.out.println("  + Isolates failing records from the main path");
        System.out.println("  + Prevents a poison pill from blocking the partition");
        System.out.println("  + Preserves the original bytes for later inspection");
        System.out.println();
        System.out.println("What a DLQ does NOT do:");
        System.out.println("  - Does NOT fix the root cause");
        System.out.println("  - Does NOT alert anyone");
        System.out.println("  - Does NOT get the record processed");
        System.out.println("  - Does NOT classify failures");
        System.out.println("  - Does NOT replay records");
    }
}
TEXT
What a DLQ DOES:
  + Isolates failing records from the main path
  + Prevents a poison pill from blocking the partition
  + Preserves the original bytes for later inspection

What a DLQ does NOT do:
  - Does NOT fix the root cause
  - Does NOT alert anyone
  - Does NOT get the record processed
  - Does NOT classify failures
  - Does NOT replay records

Why is a bare DLQ dangerous?

A DLQ with nothing around it converts a loud failure (a stuck partition someone notices) into a silent failure (a growing pile nobody sees). In a bank, this is worse because lost transactions accumulate invisibly.

JAVA
package com.bank.dlq;

public class SilentGraveyard {
    public static void main(String[] args) {
        System.out.println("Without DLQ:");
        System.out.println("  Partition stuck -> consumer lag spikes -> LOUD");
        System.out.println("  Team notices within minutes");
        System.out.println();
        System.out.println("With bare DLQ (no monitoring):");
        System.out.println("  Records go to DLT -> partition flows -> no lag spike");
        System.out.println("  Nobody looks at DLT -> failures pile up -> SILENT");
        System.out.println("  Weeks later: audit finds 5000 unprocessed transfers");
    }
}
TEXT
Without DLQ:
  Partition stuck -> consumer lag spikes -> LOUD
  Team notices within minutes

With bare DLQ (no monitoring):
  Records go to DLT -> partition flows -> no lag spike
  Nobody looks at DLT -> failures pile up -> SILENT
  Weeks later: audit finds 5000 unprocessed transfers

What monitoring do you need?

Track DLQ depth and growth rate, and alert when either crosses a threshold. A sudden spike is a strong incident signal.

JAVA
package com.bank.monitoring;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Gauge;

public class DlqMonitoring {
    // Expose DLT lag as a Prometheus metric
    public static void registerMetric(MeterRegistry registry, long dltLag) {
        Gauge.builder("kafka.dlt.lag", () -> dltLag)
            .tag("topic", "transfers.DLT")
            .description("Dead letter topic lag")
            .register(registry);
    }

    public static void main(String[] args) {
        System.out.println("DLQ monitoring rules:");
        System.out.println("  Alert if DLT depth > 0         (any failure)");
        System.out.println("  Page if DLT growth > 100/min   (dependency down)");
        System.out.println("  Dashboard: DLT depth per topic");
        System.out.println("  Route alert to OWNING team, not a shared channel");
    }
}
TEXT
DLQ monitoring rules:
  Alert if DLT depth > 0         (any failure)
  Page if DLT growth > 100/min   (dependency down)
  Dashboard: DLT depth per topic
  Route alert to OWNING team, not a shared channel

How do you classify root causes?

JAVA
package com.bank.classification;

public class DlqClassification {
    public static void main(String[] args) {
        System.out.println("Root-cause classification:");
        System.out.println("  TRANSIENT : database timeout, service 503");
        System.out.println("    Action  : replay once dependency recovers");
        System.out.println();
        System.out.println("  PERMANENT : validation error, business rule rejection");
        System.out.println("    Action  : fix code or data, then replay or discard");
        System.out.println();
        System.out.println("  POISON    : undeserializable bytes, schema mismatch");
        System.out.println("    Action  : fix producer or schema, discard bad records");
    }
}
TEXT
Root-cause classification:
  TRANSIENT : database timeout, service 503
    Action  : replay once dependency recovers

  PERMANENT : validation error, business rule rejection
    Action  : fix code or data, then replay or discard

  POISON    : undeserializable bytes, schema mismatch
    Action  : fix producer or schema, discard bad records

What does a replay procedure look like?

A written, tested redrive procedure avoids improvising under pressure.

JAVA
package com.bank.runbook;

public class RedriveRunbook {
    public static void main(String[] args) {
        System.out.println("Redrive runbook:");
        System.out.println("  1. Verify fix is deployed and tested");
        System.out.println("  2. Inspect DLT records, classify each");
        System.out.println("  3. Replay transient failures to reprocessing topic");
        System.out.println("  4. Throttle at 50 records/second");
        System.out.println("  5. Monitor consumer success rate during replay");
        System.out.println("  6. Discard permanent failures with audit trail");
        System.out.println("  7. Verify DLT drained, close incident");
    }
}
TEXT
Redrive runbook:
  1. Verify fix is deployed and tested
  2. Inspect DLT records, classify each
  3. Replay transient failures to reprocessing topic
  4. Throttle at 50 records/second
  5. Monitor consumer success rate during replay
  6. Discard permanent failures with audit trail
  7. Verify DLT drained, close incident

Why is ownership critical?

A shared responsibility is nobody's responsibility. Without a named team and a service-level agreement, the DLQ grows indefinitely.

JAVA
package com.bank.ownership;

public class DlqOwnership {
    public static void main(String[] args) {
        System.out.println("DLQ ownership model:");
        System.out.println("  Topic: transfers.DLT");
        System.out.println("  Owner: Settlement team");
        System.out.println("  SLA  : DLT drained within 4 hours of alert");
        System.out.println("  Escalation: if not drained in 4h, page lead");
    }
}
TEXT
DLQ ownership model:
  Topic: transfers.DLT
  Owner: Settlement team
  SLA  : DLT drained within 4 hours of alert
  Escalation: if not drained in 4h, page lead

What are common mistakes?

JAVA
package com.bank.mistake;

public class DlqMistakes {
    public static void main(String[] args) {
        // WRONG: enable DLT and walk away
        System.out.println("WRONG: DLT enabled, no monitoring, no owner");
        System.out.println("  Result: silent graveyard of lost transactions");
        System.out.println();

        // CORRECT: DLT + monitoring + classification + ownership
        System.out.println("CORRECT: DLT as incident-response mechanism");
        System.out.println("  Alerts, runbooks, triage, ownership, metrics");
    }
}
TEXT
WRONG: DLT enabled, no monitoring, no owner
  Result: silent graveyard of lost transactions

CORRECT: DLT as incident-response mechanism
  Alerts, runbooks, triage, ownership, metrics

What should a senior engineer know?

When records land in the DLQ, an incident has effectively occurred even if nothing crashed, because business events are not being processed. Treating the DLQ as a config option gives a false sense of safety. Treating it as incident response means alerts, runbooks, triage, ownership, and metrics, exactly like any other production incident path.

Visual Overview

DIAGRAM
  DLQ as Incident-Response Mechanism

  ┌──────────────────────────────────────────┐
  │ Main Consumer                             │
  │  Record fails -> bounded retries -> DLT  │
  └─────────────────────┬────────────────────┘
                        │
                        ▼
  ┌──────────────────────────────────────────┐
  │ DLT: transfers.DLT                       │
  │ (isolation only -- not a solution)       │
  └─────────────────────┬────────────────────┘
                        │
          ┌─────────────┼─────────────┐
          ▼             ▼             ▼
  ┌────────────┐ ┌────────────┐ ┌────────────┐
  │ MONITORING │ │ CLASSIFY   │ │ OWNERSHIP  │
  │ Alert on   │ │ Transient  │ │ Named team │
  │ depth +    │ │ Permanent  │ │ SLA for    │
  │ growth     │ │ Poison     │ │ draining   │
  └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
        │              │              │
        └──────────────┼──────────────┘
                       ▼
              ┌────────────────┐
              │ REDRIVE        │
              │ Inspect        │
              │ Replay/Discard │
              │ Verify         │
              └────────────────┘

Key Takeaway

A dead letter queue is not enough by itself. It does exactly one thing: moves a failing record aside so the main partition keeps flowing. That is valuable, but it does not fix anything, alert anyone, or get the record processed. A bare DLQ converts a loud failure (a stuck partition someone notices) into a silent one (a growing pile nobody sees), which in a bank is worse because lost transactions accumulate invisibly.

To genuinely handle failures, wrap the DLQ in four things: monitoring that alerts on depth and growth rate, root-cause classification using error metadata, a written and tested replay procedure, and clear ownership with a service-level agreement. When records land in the DLQ, an incident has effectively occurred even if nothing crashed.

At National Bank of India, an audit found thousands of unsettled transfers sitting in the DLT for months because there was no alert and no owner. Pankaj rebuilt around it: the DLT lag is now a metric that pages the settlement team, a classifier buckets records, an ownership registry routes each bucket, and a redrive runbook drives the fix-replay-verify cycle.

Remember one sentence: a DLQ only buys you isolation, and isolation without monitoring, classification, replay, and ownership is just a slower, quieter way to lose data.

---

Share
Use ← → arrow keys to navigate