Service spotlight: Amazon SQS queue depth, DLQs, and visibility
SQS is old, boring, and one of the most reliable services AWS runs — which is exactly why it’s easy to wire up carelessly and not notice until a queue backs up in production. The service itself rarely fails; the mistakes are almost always in how visibility timeouts, DLQs, and consumer scaling are configured around it.
The core model, briefly
A producer sends a message; a consumer polls, receives it, processes it, and explicitly deletes it. Between receive and delete, the message is invisible to other consumers for the visibility timeout duration — not deleted, just hidden, so a second consumer doesn’t pick up the same message while the first is still working it. If the consumer crashes, times out, or never calls delete, the message reappears after the timeout expires and gets redelivered. That reappear-on-failure behavior is the entire reliability model, and most SQS production issues trace back to a mismatch somewhere in that loop.
Mistake 1: visibility timeout shorter than processing time
If your consumer takes 90 seconds to process a message but the queue’s
visibility timeout is set to the default 30 seconds, the message becomes
visible again while it’s still being processed — a second consumer
picks it up, and now you’re processing the same message twice concurrently.
For anything non-idempotent (charging a card, sending an email, decrementing
inventory), that’s a correctness bug, not just wasted compute. Set the
visibility timeout to comfortably exceed your maximum expected
processing time, not the average — or use ChangeMessageVisibility to
extend it dynamically from inside a long-running handler if processing time
varies widely.
Mistake 2: no DLQ, or a DLQ nobody watches
A dead-letter queue catches messages that fail processing repeatedly (by
maxReceiveCount) instead of retrying forever and blocking the queue behind
one poison message. Skipping a DLQ means a single malformed message can
loop indefinitely, burning consumer capacity on retries that will never
succeed. But the more common failure is having a DLQ and never alarming on
it — messages quietly pile up, nobody notices for weeks, and by the time
someone checks, the redrive window (or business relevance of the messages)
has passed. A DLQ without a CloudWatch alarm on ApproximateNumberOfMessages
Visible is a DLQ that isn’t doing its job.
Mistake 3: treating queue depth as the only signal
ApproximateNumberOfMessagesVisible tells you how many messages are
waiting, but on its own it doesn’t tell you whether that’s a processing
outage or just a traffic spike your consumers will burn down in ten
minutes. Pair it with ApproximateAgeOfOldestMessage — a queue that’s deep
but where the oldest message is only 90 seconds old is healthy and
scaling; a queue where the oldest message is 40 minutes old means something
downstream is actually stuck, regardless of current depth. Age-of-oldest is
usually the better alarm trigger for “something is broken” versus
“we’re busy.”
Standard vs FIFO
Standard queues are at-least-once delivery with best-effort ordering and effectively unlimited throughput — the right default for most workloads, and your consumer logic needs to be idempotent regardless (duplicate delivery is a normal, expected occurrence, not an edge case). FIFO queues add exactly-once processing and strict ordering **within a message group**, at the cost of a throughput ceiling (3,000 messages/sec with batching, per API action) and higher per-request cost. Reach for FIFO only when ordering is a genuine correctness requirement (e.g., applying account balance changes in sequence) — defaulting to FIFO “to be safe” trades away throughput headroom for a guarantee most workloads don’t actually need.
A practical tip
Set a redrive policy that moves messages back from the DLQ to the source queue (SQS supports this natively now, no custom script needed) as part of your incident-response runbook, not as something you improvise during an outage. Deciding in advance how many retries a message deserves before landing in the DLQ, and what “redrive after the bug is fixed” actually looks like operationally, is a five-minute conversation before go-live and a much worse one at 2am with a few thousand stuck messages.
Join the discussion
Comments for this post live on social — reply to the thread.
Related posts
Cloud roundup: macOS Screen Sharing bug now under attack
A patched macOS Screen Sharing flaw is being exploited to plant crypto miners, a Windows Defender bypass has no fix yet, and EC2 gets built-in app health checks.
Cutting NAT gateway costs with VPC endpoints that actually help
How gateway and interface VPC endpoints replace NAT gateway traffic for AWS API calls, what they cost instead, and which traffic still has to go through NAT.
Cloud roundup: S3 finally names the policy that denied you
AWS S3 access-denied errors now name the exact policy ARN, Client VPN gets a scriptable CLI, and OpenAI ships authorized offensive-security models on Bedrock.