😇

【AWS】標準 SQS の 可視性タイムアウト「0秒」のトラップ

に公開
2025/12/21

対象読者

  • 標準SQSキューの可視性タイムアウト(Visibility Timeout)の挙動に違和感を覚えている方
  • SQS で予期せぬメッセージの重複取得が発生して原因を探っている方
  • 単一コンシューマーを理由に、可視性タイムアウトを「0秒」にすることを検討している方

AWS SQS の基本的な用語は理解していることを前提に記述しているので、この記事では用語自体の説明は割愛しています。
必要に応じて公式ドキュメント等を参照してください。

個人的にはこの記事が非常にわかりやすく、かつ視覚化されていて記憶に定着しやすいと思います。

https://aws.amazon.com/jp/builders-flash/202401/sqs-process-duplication/

結論

SQSのデフォルトの可視性タイムアウトを 0秒 にするのはやめましょう。
Receive Message API が、同じメッセージを3件取得してしまいます。

検証

  1. SQS を以下の設定で作る
項目 設定値 / 内容
AWS サービス Amazon SQS (Standard Queue)
可視性タイムアウト 0秒
DLQ最大受信数 5回
  1. キューにメッセージを1つだけ入れる 

  2. 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
  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メッセージを、単一のコンシューマー(バッチ)で全件処理する以下の仕組みを作りました。

  1. ReceiveMessage でメッセージを最大10件取得。

  2. 取得件数が 0件 なら全処理終了。

  3. 取得できたメッセージを処理し、成功したら削除。

  4. 1に戻る。

今回のプロジェクトは、何かしらの影響でメッセージ処理が失敗する可能性を考慮するため、3回取得時のメッセージは、DLQ に入れるようにしました。

可視性タイムアウトを N秒 に設定していると、手順3の処理が N秒より早く終わった場合、「キューにはまだ未処理のメッセージがあるのに、それらがインフライト(可視性タイムアウト中)で見えない」 という時間が生まれます。このタイミングで手順1のポーリングが発生すると、取得件数が 0件 となり、「まだメッセージがあるのにバッチが終了してしまう」という事象が起きます。

可視性タイムアウトより長いロングポーリングにすることや、インフライト中のメッセージ数を取得するAPIを呼ぶことなどが挙げられますが、ロジックの簡単化のために 可視性タイムアウトを0秒にしたら超ラクなのでは?と、試そうとしたというのが経緯となります。

そして、「メッセージが削除できなかった」のは、ReceiptHandle の有効期限が 可視性タイムアウトと同じだったからですね。

結局

大人しく getQueueAttributes API で処理中メッセージの数もループ条件に入れることで対応しました。

Discussion