【AWS】標準 SQS の 可視性タイムアウト「0秒」のトラップ
対象読者
- 標準SQSキューの可視性タイムアウト(Visibility Timeout)の挙動に違和感を覚えている方
- SQS で予期せぬメッセージの重複取得が発生して原因を探っている方
- 単一コンシューマーを理由に、可視性タイムアウトを「0秒」にすることを検討している方
AWS SQS の基本的な用語は理解していることを前提に記述しているので、この記事では用語自体の説明は割愛しています。
必要に応じて公式ドキュメント等を参照してください。
個人的にはこの記事が非常にわかりやすく、かつ視覚化されていて記憶に定着しやすいと思います。
結論
SQSのデフォルトの可視性タイムアウトを 0秒 にするのはやめましょう。
Receive Message API が、同じメッセージを3件取得してしまいます。
検証
- SQS を以下の設定で作る
| 項目 | 設定値 / 内容 |
|---|---|
| AWS サービス | Amazon SQS (Standard Queue) |
| 可視性タイムアウト | 0秒 |
| DLQ最大受信数 | 5回 |
-
キューにメッセージを1つだけ入れる

-
Receive Message API で 最大受信メッセージ数 10件 として取得
❯ aws sqs receive-message \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/YOUR_QUEUE_NAME \
--max-number-of-messages 10 \
--profile your-profile-name \
--region us-east-1
- 結果
{
"Messages": [
{
"MessageId": "dbf4abde-xxxx-xxxx-xxxx-378ae5f7d42c",
"ReceiptHandle": "AQEBuy4pVxR...[省略]...mpch",
"MD5OfBody": "4b1c2ba4e668c5b16b76124b2715fb84",
"Body": "test message 1"
},
{
"MessageId": "dbf4abde-xxxx-xxxx-xxxx-378ae5f7d42c",
"ReceiptHandle": "AQEBBkHUiz4...[省略]...0oUyb",
"MD5OfBody": "4b1c2ba4e668c5b16b76124b2715fb84",
"Body": "test message 1"
},
{
"MessageId": "dbf4abde-xxxx-xxxx-xxxx-378ae5f7d42c",
"ReceiptHandle": "AQEBa/tRJGA...[省略]...v1j",
"MD5OfBody": "4b1c2ba4e668c5b16b76124b2715fb84",
"Body": "test message 1"
}
]
}
同じメッセージを3件受信しているはないか!!!!
まとめ
Receive Message API で 受け取るメッセージ数を複数にすると、最大3回同じメッセージが配信されてしまいます。これは稀な事象ではなく、毎回発生するものでした。
また、可視性タイムアウトを0にすると、ReceiptHandle の有効期限も連動して 0 になります。
ReceiptHandle は取得したメッセージに対する処理(削除や可視性タイムアウトの変更 etc) に必要なものなので、その有効期限が切れてしまうと何もできなくなってしまうので、その側面のおいても、可視性タイムアウトを0秒にするのは やはり控えておいた方がいいですね。
ちなみに、FIFOキュー ではしっかり1件ずつ取得されます。
あくまで 標準キュー の話だということをご留意くだだい。
補足
ここからは 可視性タイムアウトを 0 秒にするケースなんてないのでは?と思う方もいらっしゃるかもしれませんので、そもそもこの実験をするに至った経緯を補足として(備忘録として)まとめておこうかと思います。
「0秒」 にしようと思った経緯
実装したかったロジック
夜間に溜まったSQSメッセージを、単一のコンシューマー(バッチ)で全件処理する以下の仕組みを作りました。
-
ReceiveMessage でメッセージを最大10件取得。
-
取得件数が 0件 なら全処理終了。
-
取得できたメッセージを処理し、成功したら削除。
-
1に戻る。
今回のプロジェクトは、何かしらの影響でメッセージ処理が失敗する可能性を考慮するため、3回取得時のメッセージは、DLQ に入れるようにしました。

可視性タイムアウトを N秒 に設定していると、手順3の処理が N秒より早く終わった場合、「キューにはまだ未処理のメッセージがあるのに、それらがインフライト(可視性タイムアウト中)で見えない」 という時間が生まれます。このタイミングで手順1のポーリングが発生すると、取得件数が 0件 となり、「まだメッセージがあるのにバッチが終了してしまう」という事象が起きます。
可視性タイムアウトより長いロングポーリングにすることや、インフライト中のメッセージ数を取得するAPIを呼ぶことなどが挙げられますが、ロジックの簡単化のために 可視性タイムアウトを0秒にしたら超ラクなのでは?と、試そうとしたというのが経緯となります。
そして、「メッセージが削除できなかった」のは、ReceiptHandle の有効期限が 可視性タイムアウトと同じだったからですね。
結局
大人しく getQueueAttributes API で処理中メッセージの数もループ条件に入れることで対応しました。
Discussion