Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Automation By James Joyner IV · · 9 min read Last reviewed Jul 2026

SQS Error: Visibility Timeout Too Short Causes Duplicate Message Processing

Quick answer

Fix SQS messages processed twice when the visibility timeout expires before the consumer finishes: diagnose redelivery, slow handlers, and idempotency.

  • #automation
  • #devops
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Amazon SQS hides a received message for the queue’s visibility timeout. If the consumer does not delete the message before that window expires, SQS makes it visible again and another consumer receives it — so a slow handler causes the same message to be processed more than once. There is no exception; the symptom is duplicated side effects and redelivery, visible in logs and metrics:

[worker-1] processing message id=b1f0... (attempt 1)
[worker-1] still working... (elapsed 34s)
[worker-2] processing message id=b1f0... (attempt 2)   <- redelivered while worker-1 runs

CloudWatch confirms the redelivery pattern:

ApproximateReceiveCount > 1 for messages that eventually succeed
NumberOfMessagesReceived >> NumberOfMessagesDeleted

Symptoms

  • The same message ID is processed by two workers, or by the same worker twice.
  • Downstream duplicate effects: double charges, double emails, repeated writes.
  • ApproximateReceiveCount climbs above 1 for messages that are not actually failing.
  • NumberOfMessagesReceived greatly exceeds NumberOfMessagesDeleted.
  • Messages sometimes land in the DLQ after maxReceiveCount despite eventually completing.

Common Root Causes

  • Visibility timeout shorter than processing time — the handler routinely takes longer than the timeout, so the message reappears mid-processing.
  • Occasional slow messages — most messages are fast but heavy ones exceed a timeout sized for the average.
  • Delete after slow post-processing — the message is deleted only after a long downstream call that sometimes overruns the window.
  • No visibility extension for long jobs — a legitimately long task does not extend visibility as it runs.
  • Consumer stall/GC pause — the worker pauses long enough for the timeout to lapse.
  • Non-idempotent handler — redelivery is inevitable at-least-once, but the handler treats each receive as unique.

Diagnostic Workflow

Read the queue’s configured visibility timeout:

aws sqs get-queue-attributes --queue-url <url> \
  --attribute-names VisibilityTimeout ApproximateNumberOfMessagesNotVisible

Measure actual handler duration against that timeout (from your logs):

grep -E 'processing message|completed message' worker.log \
  | awk '{print $0}' | tail -40
# compare elapsed-per-message to VisibilityTimeout

Check receive counts to confirm redelivery vs. genuine failure:

aws sqs receive-message --queue-url <url> \
  --attribute-names ApproximateReceiveCount --max-number-of-messages 10 \
  --query 'Messages[].{Id:MessageId,Recv:Attributes.ApproximateReceiveCount}'

Compare received vs. deleted over time in CloudWatch:

aws cloudwatch get-metric-statistics --namespace AWS/SQS \
  --metric-name NumberOfMessagesDeleted --dimensions Name=QueueName,Value=<q> \
  --period 300 --statistics Sum --start-time <t0> --end-time <t1>

If using a Lambda trigger, confirm the function timeout vs. the queue visibility (visibility should be >= 6x function timeout):

aws lambda get-function-configuration --function-name <fn> --query 'Timeout'

Example Root Cause Analysis

An order-fulfilment queue was double-shipping a small fraction of orders. Logs showed the same message ID handled by two workers seconds apart, and ApproximateReceiveCount was 2 for the affected messages — yet they were not in the DLQ, so nothing was “failing.” The queue’s VisibilityTimeout was 30 seconds. Handler timing showed most messages finished in ~8 seconds, but orders with many line items took 35-45 seconds.

Those heavy messages exceeded the 30-second window, so SQS made them visible again while worker-1 was still processing, and worker-2 picked them up and shipped a second time. The visibility timeout had been sized for the average, not the tail.

Two fixes were applied. First, the handler was made idempotent using the order ID as an idempotency key so a redelivered message is recognized and its side effect applied once — the durable safety net, since at-least-once delivery guarantees duplicates eventually. Second, for genuinely long messages the worker was updated to extend visibility with a heartbeat (ChangeMessageVisibility) as it works, and the base VisibilityTimeout was raised to comfortably exceed the observed tail. After both changes, redeliveries stopped causing duplicate shipments even when a message was occasionally re-received.

Prevention Best Practices

  • Set the visibility timeout above the tail (p99+) of handler duration, not the average, with headroom.
  • Make consumers idempotent on a business key — at-least-once delivery guarantees duplicates, so idempotency is the real fix, not just a longer timeout.
  • Extend visibility with ChangeMessageVisibility (a heartbeat) for legitimately long-running messages instead of relying on one large fixed timeout.
  • For Lambda-triggered queues, set VisibilityTimeout to at least 6x the function timeout, per AWS guidance.
  • Delete the message immediately after the work’s effect is durable, not after unrelated slow post-processing.
  • Alarm when ApproximateReceiveCount or the received-vs-deleted gap rises, to catch redelivery before it duplicates effects.

Quick Command Reference

# Read the visibility timeout
aws sqs get-queue-attributes --queue-url <url> \
  --attribute-names VisibilityTimeout

# Check receive counts (redelivery signal)
aws sqs receive-message --queue-url <url> \
  --attribute-names ApproximateReceiveCount \
  --query 'Messages[].Attributes.ApproximateReceiveCount'

# Extend visibility for a long job (heartbeat)
aws sqs change-message-visibility --queue-url <url> \
  --receipt-handle <rh> --visibility-timeout 120

# Lambda timeout vs. queue visibility (want visibility >= 6x)
aws lambda get-function-configuration --function-name <fn> --query 'Timeout'

Conclusion

SQS double-processing is not a delivery bug — it is a visibility timeout sized shorter than the work, letting SQS redeliver a message that is still being processed. Size the timeout to the slow tail, and extend it with a heartbeat for genuinely long jobs. But the real fix is idempotency: SQS is at-least-once by design, so duplicates will happen eventually no matter how you tune the timeout, and only an idempotent handler keyed on a business ID guarantees each message’s effect is applied exactly once.

Free download · 368-page PDF

Fixed it? Get 500 Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.