AWS · lesson 15 of 19
Beginner
4Intermediate
13- 5 VPC and Networking Basics
- 6 RDS Basics
- 7 AWS Lambda Fundamentals
- 8 CloudFormation Basics
- 9 CloudWatch Monitoring Basics
- 11 DynamoDB: Tables and Keys
- 12 DynamoDB: Scan, Reserved Words, and Global Secondary Indexes
- 13 DynamoDB: Conditional Writes, Atomic Counters, and Streams
- 14 SQS: Visibility Timeout and Dead-Letter Queues
- 15 SQS: FIFO Queues, Message Groups, and Deduplication
- 16 SNS: Fan-Out to SQS and Filter Policies
- 17 ELB and Auto Scaling: Wiring an ASG Behind a Load Balancer
- 18 ECS: Task Definitions and Services
Also part of AWS
8- + Linting AWS IAM Policies Before Deploy with Parliament from Cloud Security
- + Auditing the AWS IAM Credential Report with Python from Cloud Security
- + Hunting in AWS CloudTrail Logs with Python from Cloud Security
- + Dead-Letter Queues: What Happens When Retries Keep Failing from Event-Driven Architecture
- + Idempotent Consumers: Surviving a Duplicate Delivery from Event-Driven Architecture
- + Load Balancing: Round Robin vs Least Connections from System Design
- + Kubernetes Fundamentals from Kubernetes
- + Horizontal Pod Autoscaling: Real Replicas, Real CPU Numbers from Kubernetes
SQS: FIFO Queues, Message Groups, and Deduplication
Send the same message body twice to a FIFO queue and watch content-based deduplication deliver it only once, then interleave two message groups and confirm each group's own order is preserved — against the moto emulator.
By SkillByExample · Updated
A FIFO queue (name ending in .fifo) adds two guarantees a standard SQS queue doesn’t make:
deduplication of identical sends, and strict ordering within a message group. Both run for
real here against moto.
Content-Based Deduplication
With ContentBasedDeduplication enabled, two sends with the same body — no explicit dedup ID
needed — are treated as one message.
#!/usr/bin/env bash
set -e
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
Q_URL=$(aws sqs create-queue --queue-name orders.fifo \
--attributes '{"FifoQueue":"true","ContentBasedDeduplication":"true"}' \
--query 'QueueUrl' --output text)
echo "queue: $Q_URL"
aws sqs send-message --queue-url "$Q_URL" --message-group-id customer-alice \
--message-body '{"orderId":"o-1"}' --query 'MessageId' --output text
aws sqs send-message --queue-url "$Q_URL" --message-group-id customer-alice \
--message-body '{"orderId":"o-1"}' --query 'MessageId' --output text
echo "messages actually in the queue:"
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 10 --query 'length(Messages)'
$ docker network create aws15-lab
5d5fce82bf55588a17d87b489deb91158f419aeca96d154afe00ae14a28368c9
$ docker run -d --name aws15-moto --network aws15-lab motoserver/moto:5.2.5
6b2ed2f759976e2cc141a62ba79b924765abd8e2dfbe03c8501cf85a8b80680d
$ until docker run --rm --network aws15-lab curlimages/curl:8.11.1 -s -o /dev/null http://aws15-moto:5000; do sleep 0.3; done
$ bash dedup.sh
queue: http://aws15-moto:5000/123456789012/orders.fifo
715bb9d0-f6c6-4636-810a-8af8df2e9119
5e64663d-fc6c-4e11-9c1f-474f8418e018
messages actually in the queue:
1
Two SendMessage calls, each returning its own MessageId, but exactly one message ends up
receivable — the second send was accepted, not rejected, but deduplicated against the first
within the (default 5-minute) dedup interval, so it never results in a second delivery.
Ordering Within a Message Group
Two message groups, alice and bob, interleaved on the same queue — each should keep its own
send order even though the groups are mixed together.
#!/usr/bin/env bash
set -e
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
Q_URL=$(aws sqs create-queue --queue-name groups.fifo \
--attributes '{"FifoQueue":"true","ContentBasedDeduplication":"true"}' \
--query 'QueueUrl' --output text)
for i in 1 2 3; do
aws sqs send-message --queue-url "$Q_URL" --message-group-id alice --message-body "alice-$i" >/dev/null
aws sqs send-message --queue-url "$Q_URL" --message-group-id bob --message-body "bob-$i" >/dev/null
done
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 10 \
--attribute-names MessageGroupId --query 'Messages[].[Attributes.MessageGroupId,Body]' --output text
$ bash groups.sh
alice alice-1
bob bob-1
alice alice-2
bob bob-2
alice alice-3
bob bob-3
Sends alternated alice, bob, alice, bob, … but each group’s own three messages still come
back in send order (alice-1, alice-2, alice-3 and bob-1, bob-2, bob-3, each internally
in order) — MessageGroupId orders within itself, with no promise about how two different groups
interleave relative to each other.
Practice
1. Send the same body again after changing even one character, and confirm it's treated as a new, undeduplicated message.
#!/usr/bin/env bash
set -e
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
Q_URL="http://aws15-moto:5000/123456789012/orders.fifo"
aws sqs send-message --queue-url "$Q_URL" --message-group-id customer-carol \
--message-body '{"orderId":"o-2"}' >/dev/null
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 10 --query 'length(Messages)'
$ bash practice1.sh
1
One new message is receivable — o-2’s body hashes differently from o-1’s, so content-based
deduplication has nothing to match it against; a single changed character is enough.
2. Create a second FIFO queue without ContentBasedDeduplication, and confirm sending a message with no explicit MessageDeduplicationId is rejected outright.
#!/usr/bin/env bash
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
aws sqs create-queue --queue-name strict.fifo --attributes '{"FifoQueue":"true"}' >/dev/null
aws sqs send-message --queue-url http://aws15-moto:5000/123456789012/strict.fifo \
--message-group-id g1 --message-body '{"x":1}'
$ bash practice2.sh
aws: [ERROR]: An error occurred (InvalidParameterValue) when calling the SendMessage operation: The queue should either have ContentBasedDeduplication enabled or MessageDeduplicationId provided explicitly
Rejected with InvalidParameterValue — ContentBasedDeduplication is what lets a FIFO
queue hash the body automatically; without it, a caller has to pass MessageDeduplicationId
itself on every send, and the service refuses to guess.
3. Try FifoQueue=true on a queue name that doesn't end in .fifo, and read moto's validation error.
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
aws sqs create-queue --queue-name orders-not-fifo --attributes '{"FifoQueue":"true"}'
$ bash practice3.sh
aws: [ERROR]: An error occurred (InvalidParameterValue) when calling the CreateQueue operation: Queue name must end in .fifo for FIFO queues
Rejected — the .fifo suffix is enforced as part of CreateQueue’s own validation, not just a
documentation convention; the API refuses to create a mismatched queue rather than silently
ignoring FifoQueue=true.
4. Receive with MaxNumberOfMessages=1 three times with no DeleteMessage in between, then delete the in-flight message and confirm the group unblocks.
#!/usr/bin/env bash
set -e
aws() { MSYS_NO_PATHCONV=1 docker run --rm --network aws15-lab -e AWS_ACCESS_KEY_ID=test -e AWS_SECRET_ACCESS_KEY=test -e AWS_DEFAULT_REGION=us-east-1 amazon/aws-cli:2.39.5 "$@" --endpoint-url http://aws15-moto:5000; }
Q_URL=$(aws sqs create-queue --queue-name onebyone.fifo \
--attributes '{"FifoQueue":"true","ContentBasedDeduplication":"true"}' \
--query 'QueueUrl' --output text)
for i in 1 2 3; do
aws sqs send-message --queue-url "$Q_URL" --message-group-id carol --message-body "carol-$i" >/dev/null
done
echo "receive 1 (saves its receipt handle, does not delete yet):"
IFS=$'\t' read -r BODY RECEIPT <<< "$(aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 1 \
--query 'Messages[0].[Body,ReceiptHandle]' --output text)"
echo "$BODY"
echo "receive 2 (carol-1 still in flight, undeleted):"
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 1 --query 'Messages[0].Body' --output text
echo "receive 3 (same):"
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 1 --query 'Messages[0].Body' --output text
echo "now delete carol-1 using the saved receipt handle:"
aws sqs delete-message --queue-url "$Q_URL" --receipt-handle "$RECEIPT"
echo "receive 4 (after the delete):"
aws sqs receive-message --queue-url "$Q_URL" --max-number-of-messages 1 --query 'Messages[0].Body' --output text
$ bash practice4.sh
receive 1 (saves its receipt handle, does not delete yet):
carol-1
receive 2 (carol-1 still in flight, undeleted):
None
receive 3 (same):
None
now delete carol-1 using the saved receipt handle:
receive 4 (after the delete):
carol-2
Receives 2 and 3 both come back empty (None) even though carol-2 and carol-3 are sitting in
the queue — a FIFO queue won’t hand a group’s next message to anyone while an earlier one from
that same group is still in flight (received but neither deleted nor timed out). That’s the
mechanism that makes the ordering guarantee real: it would be meaningless if carol-2 could be
picked up before anyone finished (or gave up on) carol-1. The moment carol-1 is deleted, the
block lifts and receive 4 hands out carol-2 — confirming it was specifically about the undeleted
message, not a permanent stall. A standard (non-FIFO) queue has no such restriction.
Cleanup
$ docker rm -f aws15-moto
aws15-moto
$ docker network rm aws15-lab
aws15-lab
Next: SNS fan-out to multiple SQS queues, with a filter policy routing different messages to different subscribers.