Skip to content

Dead Letter Queue

The Dead Letter Queue is the controlled failure boundary in ChokaQ. A job enters DLQ when the runtime decides that normal processing should stop and an operator or developer needs to inspect the failure.

DLQ lifecycle

One-Minute Explanation

DLQ is not a discard path. It is an inspection and recovery table for work that could not safely finish through the normal worker path.

In ChokaQ, failed jobs leave JobsHot and move to JobsDLQ with their payload, type key, queue, attempts, worker information, failure reason, and error details. The Deck can then show the row, let an operator inspect the payload and stack trace, and optionally resurrect the job back into Hot.

When Jobs Enter DLQ

PathTriggerWhy DLQ is safer than retry
Fatal failureSmart Worker classifies an exception as non-retryableRepeating code or payload failures increases queue lag without changing the root cause
Max retries exceededTransient retries are exhaustedThe dependency or handler did not recover in time
Zombie processing jobHeartbeat expires while job is ProcessingSide effects may have partially completed
Manual operator actionAdmin cancels or isolates failed workHuman decision is required before retry

Storage Role

DLQ is the third pillar in the Hot, Archive, DLQ model:

TablePurpose
JobsHotPending, fetched, and processing work
JobsArchiveTerminal success, cancellation, and retained completion history
JobsDLQTerminal failure requiring inspection or controlled resurrection

Moving a job to DLQ is a terminal state transition. The job is removed from Hot so workers will not keep claiming it, and it becomes visible to The Deck as failed work.

Failure Reasons

DLQ rows should explain why normal processing stopped. The important distinction is not only "failed" but "what kind of failure happened?"

Reason familyTypical sourceOperator response
FatalArgumentException, JsonException, ChokaQFatalExceptionFix code or payload before resurrection
Retries exhaustedTimeout, temporary downstream failure, HTTP failureCheck dependency recovery, then bulk resurrect if safe
ZombieWorker crash, host eviction, heartbeat lossVerify idempotency and downstream state before resurrection
UnknownUnexpected runtime/storage failureInspect logs and stack trace before touching the job

The Deck Visibility

The Deck should make DLQ work clear and repeatable:

  • queue and job type;
  • failure reason;
  • attempt count;
  • last worker id;
  • failed timestamp;
  • payload and tags;
  • exception summary and stack trace;
  • safe actions: inspect, edit, resurrect, purge.

The operator's first question should be "why is this job here?" The second question should be "is it safe to run again?"

Resurrection Boundary

Resurrection moves a DLQ row back to JobsHot as Pending work. That is a deliberate boundary: ChokaQ does not process rows directly from DLQ.

That decision keeps one execution path:

  1. Hot row is fetched.
  2. Worker starts processing.
  3. Heartbeat tracks liveness.
  4. Handler runs through middleware.
  5. Result goes to Archive, retry, or DLQ.

If resurrection bypassed Hot, every lifecycle rule would need a second version.

Production Tuning

Monitor DLQ by rate, age, and reason:

SignalMeaning
DLQ rate risingNew deployment, dependency outage, or bad input stream
Old DLQ rowsOperators are not closing failed work
One job type dominatesHandler-specific bug or downstream contract break
Zombie reason dominatesWorker stability, heartbeat, or host shutdown issue
Fatal reason dominatesPayload validation or code correctness issue

DLQ growth is usually not solved by adding workers. More workers can process bad jobs faster, but they cannot make bad payloads or broken handlers succeed.

Architecture Decision

ChokaQ treats DLQ as a first-class lifecycle state because failed background work needs more than a log line. A database row gives operators enough context to investigate, preserves payload evidence, and provides a controlled recovery path.

The alternative is to discard failed work after logging or to retry forever. Discarding loses business operations. Retrying forever hides real defects and creates retry storms. DLQ is the middle path: stop automatic execution, preserve evidence, and require a deliberate next action.

The trade-off is operational responsibility. A DLQ only helps if teams review it and have clear ownership for resurrection, purge, or bug fixes.

Additional Questions

Why is DLQ separate from Archive?
Archive is retained completion history. DLQ is unresolved failed work that may need correction and resurrection. Mixing them weakens both read models.

Why not automatically resurrect max-retry jobs later?
Because exhausting retries means the normal recovery policy already failed. Automatic resurrection without new evidence can restart the same failure loop.

What makes DLQ safe for enterprise operations?
Clear failure reasons, immutable context unless explicitly edited, audited operator actions, and a resurrection path that returns jobs to the normal Hot lifecycle.

Next: review Retry And DLQ for the retry path and Edit + Resurrect for operator recovery.

Apache 2.0 Licensed