Dead-letter queue — the escape valve

When a message's delivery count crosses maxReceiveCount, the broker moves it to a sibling DLQ — freeing the main pool and giving ops a quarantine to inspect and redrive once the underlying bug is fixed.

Previously

The badge climbing past a threshold is the broker's only signal that this message is poison rather than transient. So we route it to a sibling channel — quarantine — and let ops decide when (and whether) to redrive it.

Scene 08

Dead-letter queue — the escape valve

  1. Watch
  2. Try it
  3. Predict
  4. Capture
MAIN QUEUEdepth = main eventsdepth = 9poison-00h-00h-10h-20h-30h-40h-50h-60h-70maxReceiveCount= 3DLQquarantine — no workers attacheddepth = 0WORKERS · 4Worker 1Worker 2Worker 3Worker 4redrive DLQ
What to watch for

Same poison cell from the last scene — but now the broker watches the badge. Workers grab it, fail, the count ticks 1, 2, 3. At maxReceiveCount=3 the cell slides down the chute into the DLQ. The main queue keeps going; the workers can finally drain the healthy cells behind it.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Broker.nack(cell, requeue=true)
bump the delivery count; route to DLQ once it crosses the line
def nack(cell, requeue=True):
cell.deliveryCount += 1
if cell.deliveryCount >= maxReceiveCount:
# quarantine: sibling queue, no workers attached
dlq.append(cell)
return
if requeue:
# back to the head, next worker grabs it
mainQueue.pushFront(cell)
# else: drop silently (rare; opt-in)
Broker.redrive()
manual op: drain the DLQ back to the main tail
def redrive():
# ops triggers this AFTER fixing the underlying bug
while not dlq.empty():
cell = dlq.popFront()
cell.deliveryCount = 0 # fresh attempts budget
mainQueue.pushBack(cell)
# DLQ does NOT drain on its own — quarantine is sticky
Broker.config — the symmetric knob
the same threshold expresses two opposite failure modes
# threshold = 1: trigger-happy
# one nack -> DLQ, no retry budget at all
config.maxReceiveCount = 1
# a 200ms DB blip at 1000 msg/s ->
# ~200 healthy messages quarantined per blip
# threshold = 100: slow quarantine
# poison cell loops 99 extra times
config.maxReceiveCount = 100
# workers pinned; healthy traffic starves
# sweet spot in practice: 3..10

Where this sits in Build a Message Queue (RabbitMQ / SQS)

Scene 08 of 14. After N redeliveries, the broker routes the cell to a sibling DLQ; the main pool keeps moving. maxReceiveCount is the knob with two failure modes.

Up next. Delivery count rises only when the consumer actually nacks. But what if the consumer just goes silent — long GC pause, OOM, hung syscall? The broker can't wait forever. It needs a second clock.

All 14 scenes in Build a Message Queue (RabbitMQ / SQS) · Every curriculum

Built with Arqly
Every scene in Build a Message Queue (RabbitMQ / SQS) builds on the one before it.All 14 Build a Message Queue (RabbitMQ / SQS) scenes