You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Reliably processing messages from an SQS queue requires understanding how visibility timeouts work, how to handle failures gracefully, and how to design consumers that are idempotent. In this lesson, we dive deep into the message lifecycle and the patterns that make SQS-based systems production-ready.
Every message in SQS moves through a defined lifecycle:
[Sent] ——> [Available] ——> [In-flight] ——> [Deleted]
|
v (visibility timeout expires)
[Re-available]
The visibility timeout is the period during which a message, once received by a consumer, is invisible to all other consumers. This prevents multiple consumers from processing the same message simultaneously.
| Scenario | Recommendation |
|---|---|
| Processing takes < 10 seconds | Set timeout to 30 seconds (default is fine) |
| Processing takes 1-5 minutes | Set timeout to 6-10 minutes |
| Processing time varies widely | Use a shorter default and extend dynamically |
| Processing takes > 12 hours | Consider a different architecture (Step Functions) |
Rule of thumb: Set the visibility timeout to at least 6x the average processing time to allow for retries and variability.
If a consumer realises it needs more time, it can extend the visibility timeout using the ChangeMessageVisibility API call:
sqs.change_message_visibility(
QueueUrl=queue_url,
ReceiptHandle=message['ReceiptHandle'],
VisibilityTimeout=120 # Extend by 120 seconds
)
This is useful when processing time is unpredictable — the consumer can periodically "heartbeat" to extend the timeout while it works.
Because SQS Standard queues offer at-least-once delivery, your consumer may receive the same message more than once. Your processing logic must be idempotent — processing the same message twice should produce the same result as processing it once.
| Strategy | How It Works |
|---|---|
| Idempotency key | Include a unique ID in the message. Before processing, check if this ID has already been processed (e.g. in a database) |
| Database constraints | Use unique constraints or conditional writes to prevent duplicate records |
| Upsert operations | Use INSERT ... ON CONFLICT or equivalent to safely handle duplicates |
| Deduplication window | FIFO queues provide a 5-minute deduplication window using MessageDeduplicationId |
def process_order(message):
order_id = message['orderId']
# Check if already processed
existing = db.query("SELECT 1 FROM processed_orders WHERE id = %s", order_id)
if existing:
print(f"Order {order_id} already processed — skipping")
return
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.