Amazon SQS Visibility Timeout and Message Processing
Amazon SQS’s visibility timeout is the period during which a message is hidden from other consumers after one worker receives it. It gives a worker time to process the message before SQS makes it available again.
The timeout is not deletion, acknowledgement, or a guarantee that only one worker will ever see the message. It is temporary protection around the usual SQS workflow:
- Receive a message.
- Process it.
- Delete it from the queue.
The problem the timeout solves
Without a visibility timeout, a worker could receive a message and begin processing it while another worker immediately received the same message. Both workers might charge a customer, send an email, or update the same record.
SQS instead hides a received message for a configurable interval. The first worker gets an opportunity to finish. If it succeeds, it deletes the message. If it crashes or gets stuck, the message eventually becomes visible again so another worker can retry it.
This is why SQS standard queues provide at-least-once delivery: a message is expected to be delivered one or more times, rather than exactly once. Duplicate delivery is a normal condition that consumers must tolerate.
Receive, process, and delete
When a worker calls ReceiveMessage, SQS returns the message and a receipt handle, an identifier for that particular receive operation. SQS then starts the message’s visibility timeout. The default queue timeout is commonly 30 seconds, though it can be configured.
During that interval, an ordinary receive call will not return the message to another consumer. The worker processes the message and, only after successful processing, calls DeleteMessage using the receipt handle.
Deletion is the successful end of the workflow. It removes the message rather than merely hiding it. A worker should not delete the message before its work is durable: if it deletes first and then crashes, SQS has no message left to retry.
A useful mental model is:
The visibility timeout delays another delivery;
DeleteMessageconfirms that no retry is needed.
The timeout is therefore not a transaction spanning SQS and your database or external services. SQS cannot automatically undo a database update if the worker crashes, and it cannot know whether an HTTP request sent by the worker completed. The application must handle those failure boundaries.
What a crash looks like
Suppose the timeout is 30 seconds:
- At 12:00:00, worker A receives message M.
- SQS hides M and starts its 30-second timer.
- Worker A crashes at 12:00:10, before deleting M.
- At about 12:00:30, M becomes visible again.
- Worker B receives M and tries again.
The same thing happens when processing simply takes longer than the timeout. If worker A is still working at 12:00:30, another worker may receive M while A continues. Both workers can then perform the work.
A duplicate can also occur even when the timeout appears sufficient. SQS is a distributed service, and a worker may lose the response to a delete request. The worker cannot tell whether SQS processed the deletion or whether the network failed first. Standard queues can also occasionally deliver a message more than once. Consumers should make important operations idempotent, meaning repeating the operation produces the same intended result as performing it once. A common approach is to record a message or business-operation ID and reject work already completed.
Extending a message’s visibility
A worker should choose an initial timeout long enough for normal processing, including expected database and network delays. It should not choose a value based only on the fastest successful run. If processing can exceed that timeout, the worker can call ChangeMessageVisibility with the message’s receipt handle to set a new timeout for that receive.
For work with a predictable upper bound, one extension may be enough. For long-running work, a worker can use a heartbeat pattern: while processing is demonstrably making progress, periodically extend visibility before the current timeout expires.
For example, with an initial timeout of five minutes, a worker might extend it after three minutes if the job is still active. The exact interval should leave room for scheduling delays, API latency, and transient failures. Extending at the last possible second is risky.
Extend only while the worker owns useful, active work—not indefinitely because the process is alive.
Visibility changes are made using ChangeMessageVisibility; they are not automatic merely because the worker is still processing. If the worker stops sending extensions, the current timeout eventually expires and the message can be delivered again.
Choosing an extension strategy
A worker should extend the timeout when:
- processing duration varies substantially;
- some valid jobs take longer than the queue’s initial timeout;
- abandoning the message midway would cause avoidable duplicate work; and
- the worker can detect progress and stop extending after a failure.
It should not extend forever. SQS has a maximum visibility period, and an endlessly invisible message can delay recovery from a stuck worker. Long-running jobs may need checkpoints, smaller messages, or a separate job system so progress is durable and retryable.
Even with extensions, design for duplicates. A timeout is a recovery mechanism, not an exclusive lock. The reliable contract is: process successfully, make the side effects repeat-safe, and delete only after success. If the worker crashes before deletion, SQS’s visibility timeout gives another worker a chance to try.