Amazon SQS と処理の重複
前編 ~ 可視性タイムアウトの役割
Author : 杉本 圭太
テクニカルインストラクターの杉本圭太です !
最近読んで面白かった漫画は「百木田家の古書暮らし」です。
Amazon Simple Queue Service (SQS) を使用することで、アプリケーション同士の通信を非同期にして、さらに信頼性やスケールを高められます。しかし SQS を使用する場合「処理重複が発生する可能性があるため、冪等性 (べき等性) の考慮が必要」と言われますが、どんな場合に処理重複が発生するのかは把握できていますか ?
▼ SQS を使用したシステムの概要
SQS では処理重複を制御するために「可視性タイムアウト」や「FIFO キュー」の機能がありますが、これらを使用しただけで処理重複がゼロになる訳ではありません。そのため、それぞれの機能が何を制御してくれるのかを理解することは、SQS をより適切に扱うために重要です。しかし同時に理解が難しい部分でもあります。
そこで今回は、SQS の処理重複とも関係する「可視性タイムアウト」を前編、「FIFO キュー」の機能や役割を後編 として、喩え話や自作した図などを交えて 2 回に分けて解説していきます。
まずは前編で SQS の概要と可視性タイムアウトなどの機能を確認し、どんな場合に処理重複が発生するのか学んでいきましょう φ(•ᴗ•๑)
前提知識となる、非同期処理やメッセージキュー、冪等性についての概要を知りたい方はこちらを参考にどうぞ !
1. SQS キューを使用した非同期通信の概要と主要な用語
SQS はサービス間の通信を非同期で実現するための メッセージキュー をマネージドサービスとして提供しています。ここではキューにメッセージを送る役割のサービスをプロデューサー、キューのメッセージを取得して処理する役割をコンシューマーと呼びます。
現実世界でこの関係を喩えてみます。私はたまに立ち食い寿司のお店に行くことがあるのですが、そのお店では複数の寿司職人さんがいます。私は注文を書いた紙を所定の位置に置いておけば、手が空いた寿司職人さんが注文を受け取ってお寿司を握ってくれます。その場合の関係を以下のように当てはめてみます。
- 注文する客 → プロデューサー
- 寿司職人 → コンシューマー
- 注文 → メッセージ
- 注文を置く場所 → キュー
私は注文を書いた後、手の空いた寿司職人さんを捕まえられるまで探し続けなくても良いですし、依頼したお寿司を握ってもらっている時間も自由に使えるので「次に何を頼もうかな」と考えたり他の作業ができます。寿司職人さんは自分の手が空いたタイミングで次の注文を受け取ることで、今自分にできる適切な作業量を保てます。さらに客が増えても寿司職人さんを増やせば、お店全体での提供量も増やしていけます。
このようにキューを介して役割を分担して非同期な処理をすることで、プロデューサーは時間のかかる処理を依頼する時に処理が終わるまで待ち続ける必要はありません。コンシューマーは一時的なピークでもそれぞれの処理量の均一化、もしくは必要に応じてスケールできるなどのメリットを得られます。
AWS のサービスを使う場合、EC2 インスタンスやコンテナで作られたアプリケーション、Lambda 関数などは SQS のプロデューサーやコンシューマーとして使用できます。S3 バケットや SNS トピックなどは SQS と連携されているため、プロデューサーとして設定できます。
2. SQS キューを使用した非同期通信の特徴
SQS キューを使用した処理は「ある 1 つのメッセージがコンシューマーに処理されるのは 1 度で良い」場合に使用します。立ち食い寿司のお店で考えると「ある客の 1 つの注文は、寿司職人さんが誰か 1 度だけ対応すれば良い」のと同じです。1 つの注文に対して複数の寿司職人さんが同時に対応してしまうと無駄になっちゃいますよね。
では SQS キューに複数のコンシューマーがいる場合、どのように 1 つのメッセージを 1 度だけ処理するように制御するのでしょうか ?
SQS のスタンダードキューを使用して、コンシューマーがメッセージを受け取ってから処理を完了するまでの流れを確認してみましょう。こちらのアニメーション gif で表した内容を、順番に解説していきます。
2-1. メッセージの受け取り (RecieveMessage) と可視性タイムアウト
SQS では RecieveMessage の API でキューからメッセージを取り出せます。取り出す時にはキューからメッセージは削除せず、代わりに「指定した時間内は今後 RecieveMessage では取り出せない」という状態に設定できます。これを可視性タイムアウトと言います。なぜ時間の指定が必要 ? という疑問は後ほどお伝えします。
ここで処理が終わったメッセージはどうすれば良いでしょうか ? この時点では取り出せない状態のままキューに残っています。
2-2. 完了したメッセージの削除 (DeleteMessage)
SQS では、コンシューマーは処理が完了したメッセージをキューから削除する必要があります。メッセージ受け取り時に発行された ReceiptHandle を指定して DeleteMessage の API を使用することで、キューから 1 つずつメッセージを削除できます。これでプロデューサーからの 1 つの依頼が確実に完了したことを示せます。
ではメッセージを受け取ったコンシューマーが、障害や不具合で処理を完了しきれなかったらそのメッセージはどうなってしまうでしょう ? 処理されないままのメッセージがキューに残り続けてしまうと困りますよね ?
2-3. 処理が完了しなかったメッセージの再試行
ここで可視性タイムアウトに設定された時間が効いてきます。キューから取り出さないでという状態は、可視性タイムアウトで設定した時間を経過すると、再度キューから取り出せるようになります。この仕組みによって、コンシューマーが受け取ったけど処理が完了しなかったメッセージが取り出せないままキューに残り続けずに、他のコンシューマーで再試行できます。
この仕組みにより、キュー内のメッセージが処理される確実性を高められます。
2-4. 余談: メッセージが処理できない問題に対処するには ?
メッセージ内容の問題やコンシューマーのプログラムの問題により、処理が完了できないメッセージが出る場合もあります。そうなるとメッセージの削除がされず、何度も「メッセージの取り出し→可視性タイムアウトの期限切れ」を繰り返してキューにメッセージが残り続けてしまうかもしれません。そこでキューには「メッセージの保持期間」や「デッドレターキューと最大受信数 (maxReceiveCount)」を設定して、処理できないキューが削除されない状態を回避できます。
ここまでがスタンダードキューでコンシューマーがメッセージを受け取ってから処理を完了するまでの流れでした。今回説明した可視性タイムアウトの動作や、処理したメッセージをキューから削除するという部分は FIFO キューにも共通しています。
それでは次からいよいよ本題です。可視性タイムアウトが機能すれば処理の重複は避けられそうに見えますが、SQS を使用する場合になぜ冪等性の考慮が必要なのでしょうか ?
3. SQS で処理重複が起きうる場面
ここからは SQS を使用していて、処理の重複が避けられない可能性がある場面を紹介していきます。スタンダードキューでの例を挙げますが、中には FIFO キューでも考慮が必要な場面も含まれます。
3-1. 可視性タイムアウトの設定により起きうる重複
可視性タイムアウトの特徴をおさらいしましょう。
- メッセージの受け取り時に「一定期間はこのメッセージをキューから取り出せない」という状態にできる
- 可視性タイムアウトで指定した時間を経過した場合は、再度キューから取り出せるようになる
では可視性タイムアウトで指定する時間はどれくらいにすれば良いでしょうか ? これはシステム次第なので正解はありません。
しかし気をつけないといけないのは、コンシューマーが処理にかかる時間より可視性タイムアウトを短くすると処理重複が発生するということです。
※ FIFO キューでも同様にこの考慮は必要です。
こちらの図はコンシューマー X がメッセージを受け取り処理をしているけれど、処理が完了するまでに可視性タイムアウトの時間が経過した場合です。コンシューマー Y が、すでにコンシューマー X で処理したメッセージを再度受け取って処理してしまっています。
注意点としては、この問題を回避するために可視性タイムアウトの時間を長くしすぎると、本来の目的であるコンシューマー障害によってメッセージを再試行するまでの時間が長くなってしまいます。
さらに、可視性タイムアウトは長くしたからといって以下の問題には対処できません。
- コンシューマーの過負荷などによって処理は中断しないが想定より遅延して、可視性タイムアウトの時間を超過してしまう場合
- コンシューマーとして処理は完了したけど、何らかの障害により DeleteMessage のリクエストが送れなかった場合
つまり可視性タイムアウトの設定だけでは処理重複を完全に排除はできないということです。
3-2. スタンダードキューの仕様により起きうる重複
スタンダードキューの現時点の仕様は 少なくとも 1 回の配信 (At-Least-Once) であり、1 つのメッセージがキューから複数回配信される可能性があるとされています。
※ FIFO キューの配信は Exactly-Once となっているため、この事象は発生しないよう設計されています。
こちらの図では、004 のメッセージをコンシューマー X、Y それぞれが受け取っています。
つまりスタンダードキューを使用する場合は、可視性タイムアウトの設定とは関係なく処理の重複が発生することがあります。
3-3. プロデューサーのメッセージ送信時に起きうる重複
コンシューマー側での処理重複ではなく、プロデューサー側の観点でも考えてみましょう。
状況によってはプロデューサーが SendMessage リクエストでキューにメッセージが保存した後、ネットワークなど何らかの問題でレスポンスが受け取れないこともあります。例えば車や電車などで移動中のモバイル端末からリクエストを送ったけれど、途中で接続が失われた場合などを想定してみてください。
プロデューサーはリクエストに対するレスポンスを受け取れなかった場合、リクエストが失敗しているかもしれないので再度リクエストを送るよう設定したとしましょう。その結果、同じ内容のメッセージがキューに複数送られることになります。
※ FIFO キューであれば重複排除 ID (deduplication ID) を使用してこれを緩和する機能があります。
こちらの図では、“ABC” という内容のメッセージを 2 度キューへ送信してしまっています。
コンシューマーはそれぞれのメッセージを適切に処理していても、プロデューサー側を起因とした処理重複もあり得るということです。
4. おわりに
今回は SQS の概要と可視性タイムアウトについて解説し、処理重複が発生するいくつかの場面を紹介したことで冪等性が必要なことを確認していただきました。SQS を使用する場合の注意点を知ることで、SQS のメリットを活かしてより適切なシステム設計ができるはずです ! また公式ドキュメントには Amazon SQS のベストプラクティス も記載されていますので、こちらも確認してみてください。
次回 は FIFO キューの特徴について説明し、今回挙げた中でも FIFO キューの機能により重複が防げる場面などをお伝えします。
このようにテクニカルインストラクターは、自学だけではつまずきやすい部分などを含め、みなさんにより AWS を理解してもらいやすくなる工夫を日々行いながら クラスルームトレーニング を提供しております ! 質問もリアルタイムでできるため分からない部分はとことんお付き合いしますし、ほとんどのコースには演習の時間も多くあるため学んだ内容を AWS 環境で実践できます !
AWS のトレーニングについてもっと知りたい方は、以下の連載記事も参考にどうぞ。
これまで自分で勉強してきたけど AWS を体系的に学ぶことでもっと詳しくなって業務で活用したい ! という方はぜひ AWS のトレーニングを受講してみてください !
筆者プロフィール
杉本 圭太
アマゾン ウェブ サービス ジャパン合同会社
トレーニングサービス本部 テクニカルインストラクター
テクニカルインストラクターとして、知識をつけることが目的ではなく実際に業務で活用できる力を得ることを目指したトレーニングを提供しています。自分自身が新しいことを知るのが好きなので、「AWS を知るのは面白い ! もっと学んでみよう !」と多くの方に感じてもらえる工夫を常に考えながら活動しています。
AWS を無料でお試しいただけます