SQS is often the first AWS service new teams adopt and the last one they think about. That's a mistake: where and how you use SQS shapes the reliability ceiling of your entire system.
The core value of SQS: decoupling
A direct call between two services creates a tight coupling: if the callee is slow, the caller waits; if it's down, the caller fails. An SQS queue breaks that link. The producer writes a message and moves on. The consumer pulls when it's ready. Capacity mismatches, deploys, and transient failures stop cascading.
When SQS is the right tool
- Work offloading. HTTP request completes in 80 ms, the heavy work happens in a worker pulling from SQS.
- Smoothing spiky traffic. Black Friday sends 10× normal traffic to checkout, and SQS absorbs the burst so workers process at a steady rate.
- Retries and failure isolation. Failed messages go to a dead-letter queue, visible and replayable.
- Fan-in aggregation. Many producers, one consumer pool, elastic concurrency.
When SQS is the wrong tool
SQS is a queue, not a log. These needs call for a different service:
- Multiple independent consumers: use SNS → SQS fan-out, or EventBridge.
- Ordered, replayable history: use Kinesis or MSK (Kafka).
- High-throughput event sourcing (millions of events/sec): Kinesis or Kafka.
- Routing based on event content: EventBridge with pattern rules is purpose-built.
Unsure which messaging service fits your architecture? Describe your system: a one-page diagnosis within 48 hours.
Get my diagnosis →Standard vs. FIFO
Standard queues are cheap, nearly unlimited throughput, and at-least-once with best-effort ordering. FIFO queues guarantee exactly-once processing and strict ordering per message group, at 3,000 messages per second with batching (or 300 without).
Reach for FIFO only when you need strict ordering or exactly-once semantics. Most workloads can use Standard with idempotent consumers, which is cheaper and scales further.
Dead-letter queues: non-negotiable
Every production queue should have a DLQ. Without one, a poison-pill message will loop forever, burn CPU, and mask real failures. Our defaults:
- Max receive count: 5 (tuned based on error profile).
- DLQ retention: 14 days, enough to debug and replay.
- CloudWatch alarm on DLQ depth > 0.
- A replay Lambda ready to ship messages back to the main queue after a fix.
Visibility timeout: the silent killer
If a message takes longer than the visibility timeout to process, it becomes visible to other consumers and gets processed twice. Set visibility timeout to 6× your expected p99 processing time, and renew it with ChangeMessageVisibility for long jobs.
// Python consumer renewing visibility during a long task
import boto3, time
sqs = boto3.client('sqs')
def process(msg, queue_url):
receipt = msg['ReceiptHandle']
# ... start long job ...
for _ in range(10):
time.sleep(30)
sqs.change_message_visibility(
QueueUrl=queue_url, ReceiptHandle=receipt, VisibilityTimeout=60)
# ... finish, then delete ...
sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=receipt)
Pairing SQS with Lambda
Lambda has native SQS integration, but the defaults bite. Set maximum batching window and batch size to match your consumer's throughput. Enable partial batch response so one bad message doesn't poison the whole batch. Use ReportBatchItemFailures.
The short list
- Use SQS when you need to decouple, smooth spikes, or isolate failures.
- Use EventBridge, Kinesis, or Kafka when the workload is wrong for a queue.
- Always configure a DLQ. Alarm on depth.
- Size visibility timeout to 6× p99 processing time.
- Use FIFO only when you truly need it.