SproutStackSproutStack home

Amazon SQS in Depth

9 min readBeginner-friendlyFree to read

Before this, it helps to know: Queues & Pub/Sub, Idempotency & Exactly-Once Illusion.

Read a little, play a little. No scary maths, and no rush.

Share:

What SQS is, in one minute

Amazon SQS is a fully managed queue: no brokers to run, patch or scale. Producers send messages, consumers poll for them, process them and delete them. You pay per request, and a standard queue scales to very high throughput without any configuration.

The model is simple, but three things shape every design: messages are delivered at least once, a received message is only hidden (not removed) until you delete it, and there is no replay once a message is deleted.

Standard or FIFO?

Standard FIFO
Ordering best effort strict, within each message group
Delivery at least once (duplicates possible) exactly-once processing, with deduplication over a 5-minute window
Throughput nearly unlimited 300 API calls per second per action, or 3,000 messages with batches of 10; high-throughput mode goes much higher
Name anything must end in .fifo

Standard is the default choice. FIFO is for when order matters per entity (one customer's ledger, one order's status changes) or duplicates are very costly.

A message's life

sendreceivesuccessredriveProducerSendMessageSQS queuevisible messagesWorkerReceiveMessageDeletedDeleteMessageDead-letter queueafter maxReceiveCount
A received message is hidden for the visibility timeout. Deleted in time, it is gone. Not deleted, it reappears. After maxReceiveCount receives, the redrive policy moves it to a dead-letter queue.

The settings that matter

Setting Default Range or limit
Visibility timeout 30 seconds up to 12 hours
Message retention 4 days 1 minute to 14 days
Receive wait time (long polling) 0 (short polling) up to 20 seconds
Delivery delay 0 up to 15 minutes
Maximum message size 1 MiB larger payloads go to S3, with a pointer in the message
Batch size 1 up to 10 messages per send, receive or delete
In-flight messages (standard) about 120,000 received but not deleted

The visibility timeout

When a consumer receives a message, SQS hides it from everyone else for the visibility timeout. The consumer must delete it before the timeout ends. If it does not (it crashed, or it is just slow), the message becomes visible again and another consumer gets it.

So the timeout is a bet on how long processing takes:

  • Too short: slow messages are processed twice, sometimes by several workers at once.
  • Too long: when a worker crashes, its message sits hidden until the timeout ends, delaying the retry.

For work of varying length, set a reasonable timeout and extend it while working with ChangeMessageVisibility (a "heartbeat"). Either way, duplicates can happen, so consumers must be idempotent.

Short and long polling

By default, ReceiveMessage uses short polling: it checks a subset of SQS's servers and answers at once, so it can return nothing even when messages exist. Long polling (WaitTimeSeconds up to 20) checks all servers and waits until a message arrives or the time runs out. It means fewer empty responses, fewer requests and a lower bill. There is rarely a reason not to use it.

FIFO: message groups and deduplication

  • MessageGroupId defines the unit of ordering. Messages in one group are delivered strictly in order, and SQS will not hand out a group's next message while an earlier one is in flight. Different groups are processed in parallel. So parallelism equals the number of active groups: key by customer or order, never by one constant.
  • Deduplication: a message with the same MessageDeduplicationId (or the same body, with content-based deduplication) sent within 5 minutes of the first is accepted but not delivered again. That covers producer retries, not consumer duplicates hours later.
  • Head-of-line blocking: a message that keeps failing holds up its whole group until it is deleted or moved to a dead-letter queue.

Dead-letter queues

A redrive policy on the source queue names a DLQ and a maxReceiveCount. Once a message has been received that many times without being deleted, SQS moves it to the DLQ instead of offering it again. Without a DLQ, a poison message is retried until retention expires, wasting work all along.

Two details:

  • A FIFO queue's DLQ must also be FIFO, and moving a message out of its group breaks that group's order, which you should accept knowingly.
  • For standard queues, a message keeps its original enqueue time in the DLQ, so retention there counts from when it was first sent. Give the DLQ a longer retention than the source. (FIFO queues reset the timestamp on move.)

When the bug is fixed, DLQ redrive sends messages back to the source queue for another try.

Patterns you will use

  • Fan-out: SQS has no routing or topics. Publish to SNS (or EventBridge) and subscribe one SQS queue per consumer, so each service gets its own copy and its own pace.
  • Lambda consumers: Lambda polls the queue for you and calls your function with batches. Set the queue's visibility timeout to at least six times the function timeout, and enable partial batch responses (ReportBatchItemFailures), so one bad message does not send the whole batch back.
  • Large payloads: store the body in S3 and send a reference; extended client libraries do this for you.
  • Delayed work: per-message delays up to 15 minutes; longer schedules belong in a scheduler.
Going deeper: cost and throughput tuning

You pay per request, and a request can carry up to 10 messages. Batching sends, receives and deletes, plus long polling, can cut request counts by close to ten times. For FIFO, throughput limits are per API action, so batching also multiplies throughput: 300 calls a second becomes 3,000 messages. High-throughput FIFO mode raises the limits further (the exact figure varies by region) and works best when traffic is spread across many message groups.

Watch ApproximateNumberOfMessagesVisible (backlog) and ApproximateAgeOfOldestMessage (how long the worst one has waited). Scaling consumers on age rather than count catches "few messages, all stuck" as well as "many messages."

How it fails

  • Duplicate processing: visibility timeout shorter than processing time.
  • Silent expiry: messages older than the retention period are deleted, processed or not.
  • Poison messages: retried until retention ends when there is no DLQ.
  • Blocked FIFO groups: one failing message holds its group; one hot group caps throughput.
  • Too many in flight: past about 120,000 received-but-undeleted messages on a standard queue, receives fail with OverLimit until some are deleted.

SQS or something else?

Choose SQS for decoupled work queues on AWS with no infrastructure to run. Choose Kafka (or Kinesis) when consumers must replay history or many consumers need the same stream. Choose RabbitMQ for rich routing and protocol flexibility outside a single cloud. SQS plus SNS covers a large share of everyday event-driven designs on AWS.

What to say in a design review

  • "A standard queue with long polling, a visibility timeout of twice our p99 processing time, and a heartbeat for slow jobs."
  • "A DLQ after 5 receives, with longer retention than the source and an alarm on its depth."
  • "Order updates use FIFO with the order ID as the message group, so each order is in order and different orders run in parallel."
  • "Consumers are idempotent, because even FIFO's deduplication only covers 5 minutes on the send side."

Remember this

  • At least once by default; a received message is only hidden until you delete it.
  • The visibility timeout (default 30 seconds) must exceed processing time, or work runs twice.
  • Use long polling (WaitTimeSeconds up to 20).
  • FIFO orders per message group; parallelism equals the number of active groups.
  • Always set a DLQ, with longer retention than the source.

Check your understanding

5 questions · first-try answers earn the most XP

1. A worker takes about 50 seconds to process each SQS message. The queue uses the default visibility timeout. What will the team notice?
2. A FIFO queue uses one MessageGroupId for all orders. Throughput is far below what the team needs, even with 20 consumers. Why?
3. Consumers poll an empty-looking queue with default settings and often get empty responses even when messages exist. Which change helps most?
4. A standard queue keeps messages 4 days. Its DLQ also keeps them 4 days. A message fails for 3 days, then moves to the DLQ. How long will it stay there?
5. A Lambda function processes SQS batches of 10. One bad message makes the whole batch fail, so 9 good messages are retried again and again. What fixes this?

My notes

Saved in this browser. Highlight a line above and save it, or write it in your own words.

Nothing saved yet. Your highlights will live here.

References

Finished reading?

Ticking it here also ticks the chapter in the sidebar, the section count and your streak — it is all one number.

Try a design drill

Related chapters

Spotted a mistake or want a topic covered? Report an issue

📬 An occasional email when new lessons land

New lessons and showdowns, no more than a few times a month. No spam, unsubscribe anytime. We store your email only to send the newsletter.