SQSのキューからメッセージを受け取ったのに、削除した覚えもないのに次の受信では取れなくなる。しばらくすると、また同じメッセージが現れる。これはバグではなく「可視性タイムアウト」というSQSの中心的な仕様です。
この記事では、可視性タイムアウトを2秒に設定したキューを用意し、メッセージの見え方が時間でどう変わるかを実際にコードで検証しました。
なお、検証はAWS APIのモックライブラリ「moto」(バージョン5.2.5)の上で行っています。可視性タイムアウトの仕組み自体は公式ドキュメントで定められた仕様で、実AWSでも同じです。ネットワークや実際のレイテンシは検証対象外です。
検証環境
- Python + boto3 1.43.123
- moto 5.2.5(AWS APIのモック)
- リージョン設定:ap-northeast-1
- 可視性タイムアウトを2秒に設定した標準キューに、メッセージを1件送信
検証コードと実行結果
受信を3回試すコードです。1回目と2回目の間は間隔を空けず、2回目と3回目の間に3秒待ちます。
import time
import boto3
sqs = boto3.client("sqs", region_name="ap-northeast-1")
q = sqs.create_queue(
QueueName="verify-q",
Attributes={"VisibilityTimeout": "2"}, # 可視性タイムアウト2秒
)["QueueUrl"]
sqs.send_message(QueueUrl=q, MessageBody="order-123")
r1 = sqs.receive_message(QueueUrl=q)
print("1回目:", len(r1.get("Messages", []))) # 受信できるはず
r2 = sqs.receive_message(QueueUrl=q)
print("2回目(直後):", len(r2.get("Messages", [])))
time.sleep(3)
r3 = sqs.receive_message(QueueUrl=q)
print("3回目(3秒後):", len(r3.get("Messages", [])))
実行結果はこうなりました。
1回目: 1
2回目(直後): 0
3回目(3秒後): 1
1回目で受け取ったメッセージ(本文は order-123)は、直後の受信では0件になり、3秒後の受信で同じものが戻ってきました。
何が起きているのか
SQSでは、メッセージは受信しても削除されません。その代わり、受信した時点で「可視性タイムアウト」の間だけ他の受信者から見えなくなります。
- 受信に成功したら、処理が終わったタイミングで
DeleteMessageを呼んで明示的に削除するのが前提 - 削除されないまま制限時間を過ぎると、メッセージは再び見えるようになり、別のワーカーが受け取れる
- これにより、処理中にワーカーが落ちても、メッセージが失われずに再処理される
つまり可視性タイムアウトは「処理の猶予時間」です。今回は2秒に縮めたので、3秒待つと再出現が観察できました。実際のキューのデフォルトは30秒です。
実務で使うときの注意点
- 可視性タイムアウトは「想定される最大の処理時間」より長く設定するのが基本です。短すぎると、処理の途中でメッセージが再出現して二重処理になります
- 処理時間が伸びそうな場合は、
ChangeMessageVisibilityで受信後に延長できます(上限は12時間) - 再出現が前提の仕組みなので、処理側は同じメッセージを2回受けても壊れない設計(冪等性)が必須です
- 何度も失敗するメッセージは、デッドレターキュー(DLQ)に逃がす設定とセットで使うのが定番です
まとめ
- SQSのメッセージは「受信=削除」ではなく、「受信=一時的に見えなくなる」が正しい理解です
- 削除しないと、可視性タイムアウトの経過後に再び受信されます(今回の検証では2秒設定で再出現を確認)
- 可視性タイムアウトの長さと、処理の冪等性が、SQS設計の要です
参考リンク
- Boto3「SQS.Client.create_queue」(VisibilityTimeoutの説明): https://docs.aws.amazon.com/boto3/latest/reference/services/sqs/client/create_queue.html
- Boto3「SQS.Client.change_message_visibility」: https://docs.aws.amazon.com/botocore/latest/reference/services/sqs/client/change_message_visibility.html