Queues & Background Processing in Depth · Background Job Processing
Retries and Dead-Letter Queues
Retry transient failures, park permanent ones.
Classify failures: transient (timeouts, 503, deadlocks) → retry with exponential backoff and jitter; permanent (validation errors, missing data, bugs) → don't retry endlessly. After max attempts, move the message to a dead-letter queue (DLQ) with error details, alert, and provide tooling to inspect, fix and redrive (replay) messages. DLQs are an operational queue, not a trash can.
DLQ mechanisms
| Broker | DLQ mechanism |
|---|---|
| SQS | Redrive policy (maxReceiveCount) → DLQ; redrive back to source |
| RabbitMQ | Dead-letter exchange (x-dead-letter-exchange) on reject/expiry |
| Kafka | Application-level DLT topic (e.g., Spring DeadLetterPublishingRecoverer) |
| BullMQ / Celery | Failed job state / custom DLQ queue |
{
"deadLetterTargetArn": "arn:aws:sqs:eu-west-1:123456789012:orders-dlq",
"maxReceiveCount": "5"
}
def handle(msg):
try:
process(msg)
except (Timeout, ServiceUnavailable):
raise # transient → broker retries with backoff
except (ValidationError, NotFound) as e:
dead_letter(msg, reason=str(e)) # permanent → park immediately
ack(msg)
Common pitfalls
- Infinite retries of permanent failures.
- DLQs without alerts or owners.
- Redriving before fixing the cause.
Key points
- Transient vs permanent
- Max attempts → DLQ
- Alert, inspect, redrive
Further reading: SQS: Dead-letter queues
Practice questions
Why shouldn't validation errors be retried?
Answer
They will fail the same way every time, wasting resources and delaying other messages; send them to the DLQ immediately.
What's needed to safely redrive DLQ messages?
Answer
A fix for the underlying cause, idempotent handlers (messages may have been partially processed), and controlled-rate replay with monitoring.
Open in the app to mark lessons complete, take notes and track your progress.