AWS SQSの可視性タイムアウト(Visibility Timeout)を調整する話です。
ここではAWS SQSの概要は言及しません。ことわりがない限り、SQSはFIFOを利用しているものとします。
原因と対策
発端
可視性タイムアウトを1minに設定したキューで、メッセージを削除できない事案が発生していました。具体的にはアプリケーションでSQSからエラーメッセージを受け取っています。
ReceiptHandle is invalid. Reason: The receipt handle has expired
受信した際に確保した受信ハンドルを使ってメッセージを削除しようとすると、「ハンドルの期限が切れている」と言われます。
受信ハンドルと可視性タイムアウト
SQSは、メッセージを受信するとレスポンスとして受信ハンドルを受け取れます。受信ハンドルは、メッセージに対する一意のキー情報のようなもので、有効期限が設けられるものです。
受信ハンドルはキューに設定された可視性タイムアウトの範囲を超えると有効期限切れとなり、削除ができません。
メッセージキューを利用するようなユースケースでは、ただ一度だけ受信することをアーキテクチャ上厳密に管理したいことがあります。
AWS SQSでは、そのような重複処理を排除する仕組みとして、一度受信リクエストを受け付けたメッセージを一定期間コンシューマから見えないようにする期間が設けられています。これをSQSの仕組みで「可視性タイムアウト」と呼んでいます。
「メッセージを見えなくする」イメージについては、下記記事がとても具体的でわかりやすいのでご参考ください。
https://dev.classmethod.jp/articles/sqs-visibility-timeout/
受信者であるコンシューマから、メッセージが見えない期間を設定することでメッセージの重複処理を避けられるようになるのですが、受信ハンドルが有効な期間にメッセージの削除までできなければ「メッセージをただしく処理できた」とは言えない、ということでもあります。
公式ドキュメントでも次のように紹介されています:
コンシューマーがキューからメッセージを受信して処理しても、そのメッセージはキューに保留されたままです。Amazon SQSでは、メッセージが自動的に削除されません。Amazon SQSは分散システムであり、接続の問題やコンシューマーアプリケーションの問題などが原因で、コンシューマーが実際にメッセージを受信するという保証がありません。そのため、コンシューマーはメッセージを受信して処理した後、キューからメッセージを削除する必要があります。
この概要からわかるように、受信と削除は別の概念に位置づけられています。そのため、受信ハンドルを使って明示的に削除する必要があります、もし可視性タイムアウトの期間内に削除できなければ受信ハンドルを無効化、削除リクエストを受け付けない仕様としています。
対策
頻繁に可視性タイムアウトを超えてしまう場合、削除までの処理が長すぎる、ないしは可視性タイムアウトが要件に対して短すぎる、などの可能性がありえます。
プロジェクトの要件次第ですが、3つほどの対策が考えられます:
- 可視性タイムアウトを引き伸ばす
- トランザクション単位を受信〜処理にまとめる
- 処理の性能を改善する
1.はたしかに簡単な選択肢ですが、可視性タイムアウトがながければ長いほど、キュー内で削除を待つ期間が長くなります。したがって、キューを受信してからの処理でエラーが一定量想定される場合には、削除を待つ時間が長くなり、結果キューの長さを長くしてしまいます。可視性タイムアウトを長くすることはトレードオフが発生します。
安直に可視性タイムアウトを伸ばすのが要件に合わない、ないしは設計上不都合が生じる場合は1トランザクションで受信 -> 処理 -> 削除をできるよう、アプリケーションの修正を試みるのも有効です。
3は、当たり前といえば当たり前ですが、キューのコンシューマになるアプリケーションを、キューを利用する前提でチューニングするということです。2と被る部分もありますが、結局一定の速度で処理し続けられるような可用性・性能をアプリケーションで実装できれば、可視性タイムアウトにまつわるトラブルも少ないはずです。
おわりに
他のメッセージキューイングシステムを事細かに調べたわけではないので断言はできないですが、SQSの可視性タイムアウトは概念以上に用語がわかりづらかったです。
重複処理させない仕組みであること、設定は柔軟にできるがトレードオフが存在することを念頭に運用することで、アーキテクチャにあった設定を選択できるかと思います。