CoursesAWS Solutions Architect AssociateDecoupling: SQS, SNS & EventBridge

Decoupling: SQS, SNS & EventBridge

Queues, pub/sub, and event-driven design.

Advanced30 min · lesson 12 of 15

Watch the pass in a busy restaurant. The waiter doesn't hand your order to a cook and stand there waiting for the plate. They clip a ticket to the rail and walk straight back out to the dining room. Cooks pull tickets at their own pace. When a tour bus empties into the car park, the rail gets long and the kitchen keeps its rhythm anyway. That rail is a queue, and it is the most useful resilience trick on AWS. It turns *everything must work right now* into *everything will be handled shortly*.

Now put names on the parts. The waiter is a producer, meaning any component that hands off work. The cook is a consumer, the component that does the work. The ticket is a message, a small piece of text describing one unit of work. Two components are tightly coupled when the producer calls the consumer directly and waits for an answer: a slow consumer stalls the producer, a dead one breaks it, and a rush hits both at the same moment. They are loosely coupled when something holds the work in between, so each side can crash, scale, and be redeployed on its own schedule. AWS gives you three managed ways to do that, and the exam expects you to tell them apart on sight: SQS (Simple Queue Service, for queues), SNS (Simple Notification Service, for publish and subscribe), and EventBridge (for routing events).

SQS: the rail that holds the tickets

Amazon SQS (Simple Queue Service) is that rail, run for you. Producers call SendMessage. SQS writes the message down in more than one Availability Zone (separate data centre campuses inside one AWS region), so losing a building doesn't lose your orders. Consumers ask for work with ReceiveMessage, and nothing is ever pushed at them. That pull model is the whole reason a queue can grow to a million messages without anything toppling over: the backlog sits in SQS, not in your application's memory. Messages stick around for 4 days by default, and you can set that anywhere from 60 seconds to 14 days, so a consumer that dies at 2am is an inconvenience rather than lost revenue. Queues come in two shapes. Standard queues take nearly unlimited throughput, deliver *at least once*, and make a best effort at keeping order. FIFO queues (first in, first out) hold strict order inside each message group and process each message exactly once, and you pay for that with a ceiling: 300 messages a second, 3,000 if you batch, tens of thousands in high-throughput mode. Build a real queue starting with its safety net, a dead-letter queue (DLQ). That is a second queue that catches messages which keep failing, so one poisoned message can't loop forever and drown your workers.

create a queue with a dead-letter safety net
# 1. The DLQ first — it's just another queue
aws sqs create-queue --queue-name orders-dlq
# {
# "QueueUrl": "https://sqs.eu-west-1.amazonaws.com/123456789012/orders-dlq"
# }
# 2. Grab its ARN for the redrive policy
aws sqs get-queue-attributes \
--queue-url https://sqs.eu-west-1.amazonaws.com/123456789012/orders-dlq \
--attribute-names QueueArn
# "QueueArn": "arn:aws:sqs:eu-west-1:123456789012:orders-dlq"
# 3. Work queue: 20s long polling, 90s visibility, DLQ after 5 failed receives
aws sqs create-queue --queue-name orders --attributes '{
"ReceiveMessageWaitTimeSeconds": "20",
"VisibilityTimeout": "90",
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:eu-west-1:123456789012:orders-dlq\",\"maxReceiveCount\":\"5\"}"
}'

Two of those settings carry real weight, and the exam pokes at both. ReceiveMessageWaitTimeSeconds: 20 switches on long polling. Without it, an empty queue answers your poll instantly with nothing, and you get billed for the round trip anyway. With it, SQS holds the line open for up to 20 seconds and answers the moment work shows up. Fewer API calls, smaller bill, quicker pickup. The second setting, VisibilityTimeout, exists because *receiving a message does not delete it*. Borrowing a library book works the same way: the book leaves the shelf, nobody else can take it, and there is a due date. When a consumer receives a message, SQS hides it from every other consumer for the length of the timeout (30 seconds by default, 12 hours at most). The consumer does the work, then explicitly deletes the message using the ReceiptHandle that came back with it. Crash before that delete and the clock runs out, the message pops back into view, and someone else picks it up. That quiet retry is the entire fault-tolerance story, and it is also why standard queues promise *at least once* and never *exactly once*.

the message lifecycle: send, receive, delete
QUEUE=https://sqs.eu-west-1.amazonaws.com/123456789012/orders
aws sqs send-message --queue-url $QUEUE \
--message-body '{"orderId":"1042","total":149.99}'
# {
# "MD5OfMessageBody": "8ef4d2a9c1b7e6f3...",
# "MessageId": "c3b9e0f1-6f2a-4c8e-9a51-1d2f3e4a5b6c"
# }
aws sqs receive-message --queue-url $QUEUE \
--wait-time-seconds 20 --max-number-of-messages 10
# {
# "Messages": [{
# "MessageId": "c3b9e0f1-6f2a-4c8e-9a51-1d2f3e4a5b6c",
# "ReceiptHandle": "AQEBzL5q8XoGm3...",
# "Body": "{\"orderId\":\"1042\",\"total\":149.99}"
# }]
# }
# Work done -> delete, or the message comes back in 90 seconds
aws sqs delete-message --queue-url $QUEUE \
--receipt-handle "AQEBzL5q8XoGm3..."
# (no output = success)
The visibility timeout is a deadline, not a padlock
Run past the visibility timeout and the message becomes visible again while your consumer is still working on it. A *second* consumer grabs it, and now the customer gets two shipments. Size the timeout against your worst case, not your average (when Lambda reads the queue, AWS suggests at least 6× the function timeout). And make every consumer idempotent, meaning it can handle the same message twice with the second run changing nothing. On standard queues, duplicates are a *when*, not an *if*. Pair a tight timeout with a low maxReceiveCount and perfectly healthy messages start piling into your DLQ while they are still being processed.

SNS: one announcement, everyone who cares hears it

A tannoy announcement in a warehouse goes out once and everyone on the floor hears it at the same time. The person holding the microphone has no idea who is listening, and that is exactly the point. Amazon SNS (Simple Notification Service) works that way, which flips the direction of SQS. It is publish/subscribe, and it *pushes*. A publisher sends one message to a topic, which is a named channel, and SNS hands a copy to every subscriber on it: SQS queues, Lambda functions, HTTPS endpoints, email, SMS, mobile push, or Amazon Data Firehose (the service that used to be called Kinesis Data Firehose). Adding a new listener needs zero changes to the publisher. SNS stores nothing for later pickup, which is why the standard pattern is fan-out: the topic drops a copy into several SQS queues, one per consuming service, and each queue absorbs its own backlog. One trap catches everybody once. SNS can only deliver into a queue whose access policy names sns.amazonaws.com and allows sends from that topic's ARN (Amazon Resource Name, the unique ID AWS gives every resource). A missing queue policy is the number one reason a fan-out silently delivers nothing at all. Filter policies cut the noise: a subscription with a filter receives only the messages whose attributes match, so the refunds service isn't woken up by every order that gets placed.

fan out one event to filtered subscribers
aws sns create-topic --name order-events
# { "TopicArn": "arn:aws:sns:eu-west-1:123456789012:order-events" }
# Subscribe a queue; raw delivery skips the SNS JSON envelope
aws sns subscribe \
--topic-arn arn:aws:sns:eu-west-1:123456789012:order-events \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-west-1:123456789012:refunds \
--attributes '{"RawMessageDelivery":"true"}'
# { "SubscriptionArn": "arn:aws:sns:eu-west-1:123456789012:order-events:7f3c9a1e-..." }
# Only refund events reach this queue
aws sns set-subscription-attributes \
--subscription-arn arn:aws:sns:eu-west-1:123456789012:order-events:7f3c9a1e-... \
--attribute-name FilterPolicy \
--attribute-value '{"eventType":["refund"]}'
aws sns publish \
--topic-arn arn:aws:sns:eu-west-1:123456789012:order-events \
--message '{"orderId":"1042"}' \
--message-attributes '{"eventType":{"DataType":"String","StringValue":"refund"}}'
# { "MessageId": "b5e0a3d2-91c4-4f7e-8a2b-0c1d2e3f4a5b" }

EventBridge: reading the letter, not the envelope

A sorting office that opens the letter and routes it on what is written inside can do things one that only reads the label never could. Amazon EventBridge is that sorting office: a serverless event bus, which is SNS with a rules engine bolted on the front. An event is a JSON document carrying a source, a detail-type, and a detail payload. Rules match on the *content* of that document, the fields inside it rather than a flat list of attributes stuck to the outside, and they send matches to targets: Lambda, Step Functions, SQS, a bus in another AWS account, and twenty-odd other services. Three things separate it from a nicer SNS. First, the default bus already carries the events AWS emits about itself (*an EC2 instance changed state*, *someone signed in to the console*), and SaaS vendors such as Datadog and Stripe can push their own events into a partner event bus you attach to your account. Either way you react to a vendor without writing a poller that hammers their API every minute. Second, the matching is rich: numeric ranges, prefixes, anything-but. That makes 'orders over $100 go to fraud review' a line of routing config instead of an if statement buried in consumer code. Third, archive and replay records events so you can push them back through the bus later, which saves your week when a buggy consumer silently binned seven days of traffic. The price: EventBridge usually adds more delivery latency than SNS, and its PutEvents API is capped by regional quotas, while SNS standard topics take nearly unlimited throughput and deliver in near real time.

content-based routing with a rule
aws events put-rule --name big-orders \
--event-pattern '{
"source": ["com.shop.orders"],
"detail-type": ["OrderPlaced"],
"detail": {"total": [{"numeric": [">", 100]}]}
}'
# { "RuleArn": "arn:aws:events:eu-west-1:123456789012:rule/big-orders" }
aws events put-targets --rule big-orders \
--targets '[{"Id":"fraud-check","Arn":"arn:aws:sqs:eu-west-1:123456789012:fraud-check"}]'
# { "FailedEntryCount": 0, "FailedEntries": [] }
# NB: as with SNS, delivery only works once the queue's access policy
# lets events.amazonaws.com send from this rule's ARN
# Emit a custom event and let the rule route it
aws events put-events --entries '[{
"Source": "com.shop.orders",
"DetailType": "OrderPlaced",
"Detail": "{\"orderId\":\"1042\",\"total\":149.99}"
}]'
# { "FailedEntryCount": 0, "Entries": [{ "EventId": "5f9a3b7c-2e41-..." }] }
Choosing a decoupling service (the exam's favorite question)
Match the scenario to the service
The tells the exam hinges on
One producer, one consumer, work buffered and done once
SQS
Pull-based queue: the consumer polls, works, then deletes
One event, many consumers that each need a copy
SNS
Push-based pub/sub, usually fanning out into SQS queues
Route on event content, react to AWS or SaaS events, cross-account, or replay
EventBridge
Event bus with a content-matching rules engine
Strict ordering AND no duplicates
SQS / SNS FIFO
Ordered per message group, processed exactly once
Trap: real-time streaming analytics, or several consumers re-reading the same ordered records, points at Kinesis Data Streams instead of any of these. SQS deletes a message once it is consumed; Kinesis keeps the stream, so each consumer reads at its own cursor.

Telling them apart in an exam question

Scenario questions turn on a short list of tells. One producer, one consumer, work buffered and done once → SQS. One event, several consumers that each need their own copy → SNS, usually fanning into SQS. Routing on what the event contains, reacting to AWS or SaaS events, crossing account boundaries, or replaying history → EventBridge. Strict ordering *and* no duplicates → the FIFO version of SQS or SNS. And when the question mentions real-time streaming analytics, or several consumers re-reading the same ordered records, the answer is Kinesis Data Streams and none of these three: SQS deletes a message once it is consumed, while Kinesis keeps the stream so every consumer reads at its own cursor. Track the direction of travel too. SQS consumers *pull*. SNS and EventBridge *push*. That pull model is why queue depth, the ApproximateNumberOfMessagesVisible metric, is the standard scaling signal for a fleet of workers, as you saw in the Auto Scaling lesson.

Before it leaves the lab: security, scale, cost

Three levers matter in production. Security: new SQS queues arrive with SSE-SQS (server-side encryption at rest, using a key AWS manages) already switched on, but compliance workloads usually want SSE-KMS, which uses a customer-managed key in AWS Key Management Service so *you* own the key policy and the rotation schedule. Write resource policies that grant the least access that still works: only the topic may SendMessage to the fan-out queues, only the worker role may ReceiveMessage. Add VPC endpoints, private doors into AWS services from your Virtual Private Cloud, so queue traffic never touches the public internet. Scale: memorize 256 KB per message, still the answer the exam wants, though AWS raised the real maximum to 1 MiB in August 2025. The classic workaround for anything bigger is to park the payload in S3 and queue a pointer to it. Remember roughly 120,000 in-flight messages on a standard queue, plus the FIFO throughput ceiling from earlier. Cost: SQS bills per request, around $0.40 per million once you leave the free tier, so long polling saves money as well as latency by wiping out most of your empty ReceiveMessage calls. Custom EventBridge events run about $1 per million. The cheapest architecture tends to be the calmest one: fewer synchronous calls, fewer retry storms pounding a service that is already struggling.

Notice how much of this came back to permissions. A queue policy that is too generous lets anybody inject work into your pipeline, and an unencrypted topic hands over every payload it carries. Every arrow you draw between two decoupled components is also a door somebody can try. Resource policies, customer-managed keys, private endpoints: those are three instances of one discipline, and the next lesson, Security best practices, applies that discipline across your whole AWS account.

So: SQS holds work until a worker is free. SNS copies one event to everybody subscribed. EventBridge reads the event and decides where it goes, including into other accounts and out to SaaS vendors. Pick by the relationship you actually have. One worker draining a backlog is a different problem from ten teams all reacting to "OrderPlaced."

Try this

Spin up a throwaway queue, put one message in, pull it back out, then delete the queue. Do this in a lab account, never production.

terminal
Q=$(aws sqs create-queue --queue-name saa-lab-q-$RANDOM --query QueueUrl --output text)
echo "Queue=$Q"
aws sqs send-message --queue-url "$Q" --message-body '{"orderId":"o-42","action":"bill"}'
aws sqs receive-message --queue-url "$Q" --max-number-of-messages 1 --wait-time-seconds 5 \
--query 'Messages[0].{Body:Body,Handle:ReceiptHandle}' --output table
aws sqs delete-queue --queue-url "$Q"
output
Queue=https://sqs.us-east-1.amazonaws.com/111122223333/saa-lab-q-18422
---------------------------------------------
| ReceiveMessage |
+---------------------------+---------------+
| Body | Handle |
+---------------------------+---------------+
| {"orderId":"o-42"...} | AQEB... |
+---------------------------+---------------+
# delete-queue removes the lab queue

Takeaway

Queues absorb spikes. Topics copy one event to many. EventBridge routes on what the event says. Loose coupling is how a producer survives a consumer that went slow or died overnight.

Next: take one synchronous internal API call from a system you know, redraw it with SQS or EventBridge in the middle, and name the failure you removed by doing it.

Quick check
01Your SQS consumer normally clears an order in a few seconds, but one slow downstream call can stretch that to 2 minutes. The queue is a standard queue with a 30 second visibility timeout. What actually goes wrong?
Incorrect — The visibility timeout is a deadline, not a padlock, and it does not stretch because your consumer is still busy.
Correct — Once processing outlasts the visibility timeout the message becomes visible again, and standard queues are at-least-once. Size the timeout above your worst case and make consumers idempotent.
Incorrect — SQS never extends it for you. Only an explicit ChangeMessageVisibility call, or a longer timeout to begin with, buys more time.
Incorrect — A message only lands in the DLQ after maxReceiveCount failed receives, not after one expired timeout.
02Which capability does Amazon EventBridge give you that Amazon Simple Notification Service (SNS) does not?
Incorrect — No. That describes SNS standard topics. EventBridge usually adds more delivery latency, and its PutEvents API is capped by regional quotas.
Correct — Yes. EventBridge rules match the fields inside the event document, and archive-and-replay pushes old events back through the bus. It also picks up AWS-service and SaaS-partner events.
Incorrect — No. That is the Amazon Simple Queue Service (SQS) and Kinesis model. EventBridge pushes to its targets.
Incorrect — No. Exactly-once processing belongs to the FIFO queue and topic variants, not to EventBridge in general.
03An 'order placed' event has to reach three independent services - email, inventory, and analytics. Each one needs its own copy of every event plus its own buffer, so a slow consumer never holds up the others. Which architecture fits BEST?
Correct — Yes. SNS pushes a copy of every event into each subscribed queue, and each queue holds its own backlog for its own consumer. That is the fan-out pattern.
Incorrect — No. A standard queue deletes each message once one consumer handles it, so the three services would fight over messages instead of each getting a copy.
Incorrect — No. Without a queue per service there is no independent buffer, so a slow or failing consumer loses its durability and its retry cushion.
Incorrect — No. Kinesis suits ordered real-time streams re-read by many consumers, but it is heavier than this needs and adds shard management the fan-out avoids.

Related