Posted at

Amazon SQSを使う前に知っておきたい基本的なこと

More than 1 year has passed since last update.


はじめに

Amazon SQS + Amazon Lambdaを利用して実装を行ったのですが、恥ずかしながらSQSの基本的な仕組みをきちんと理解せずに実装をすすめたため、途中でハマったり、当初の設計を変更するハメになりました。

僕と同じような状況に陥る人が今後出ないように、勘違いしていたところや、気をつけるべき点を説明します。


ドキュメントを読む

公式のドキュメントはこちら


全て取得できることを保証しない / 重複取得もありえる

超基本的なこと、その1です。

SQS APIからメッセージを取得する際は、最大で10件まで同時に取得可能ですが、

( 詳しくはこちら )

例えば、キューにA,B,Cというメッセージが3件だけある状態で、SQS APIから10件メッセージの取得を行った場合に、Aのメッセージのみを10件取得する、というケースも普通にありえるのです。

複数のサーバーにメッセージを保存 / 取得している関係上、キューにメッセージがあるにもかかわらず、受信リクエストを送ってもレスポンスが空のケースも十分おこりえるし、削除済みのメッセージを再度取得してしまうケースもまれにある、とドキュメントには書いてあります。

このように、メッセージの取得は結構ファジーです。

このような仕様に対応するためには、


ロングポーリングを使う

ReceiveMessageのオプションのWaitTimeSecondsを0秒以上にすると、ロングポーリングとなります。ロングポーリングは上記の空レスポンスを防ぎます。


重複実行しても問題のない処理を書く

さっき処理したはずのメッセージを再度取得してしまうケースもあるので、同じ処理が重複して実行されても問題の無いよう、処理を書きましょう。


メッセージの取得順は保証されない

上とかぶりますが、超基本的なこと、その2です。

メッセージを取得する際に、古いものから取得したり、新しいものから取得したり、条件を指定することはできません。

そのため、処理順に影響されないような処理に、SQSを用いましょう。

もし、処理順序が重要な処理にSQSを使う場合は、オレゴン、オハイオリージョンにて、FIFO(先入れ先出し)キューが提供されています。


処理が終わったら削除処理を行おう

これも基本的なことですね。考えてみれば当たり前の事です。

処理が正常に終了したのに、キューに残っていては、再度同じ処理が実行されてしまいます。

削除処理にて気をつけるべきことは2点あります。


削除するためには最新のReceiptHandleを使用すること

メッセージを受信した際に、同時にReceiptHandleというハッシュのようなものもついてきます。

メッセージを削除するためには、キューのURLとReceiptHandleにて、削除する対象のメッセージを特定しなければならないので、処理が終わるまで大切に保管しましょう。

また、削除するためには、ReceiptHandleは最新のものでなければなりません。

たとえ同一のメッセージを何度も受信したとしても、ReceiptHandleは受信するたびに変化します。

あるメッセージAを受信した後に、削除するためにReceiptHandleA1を保存していたとします。

しかし、削除を行う前に、再度メッセージAを受信してしまうと、A1ではもう削除できなくなってしまいます。この場合は、再度メッセージAを受信してきた際に取得したReceiptHandleA2を使用しないと削除できません。

複数のプロセスで、1つのキューを取得するケースなどは、十分注意する必要があります。

後述しますが、このようなケースを防ぐために、可視性タイムアウト(VisiblityTimeout)を必ず設定しておく必要があります。


メッセージの削除処理は、処理が正常に終わった後にすること

メッセージの削除に最新のReceiptHandleが必要なら、取得したらすぐ削除したらいいじゃん、と思うかもしれませんが、おすすめはしません。もし処理にエラーが発生して、正常に終了しなかった場合に、デバッグがしづらくなってしまうためです。

後述するデッドキューを設定することで、効率的にエラーハンドリングが可能です。


可視性タイムアウトを設定すること

可視性タイムアウトとは、一定期間、他のプロセスからメッセージを取得できないようにする仕組みです。

複数のプロセスで、1つのキューを取得するケースの場合は、削除処理を行わなければ、複数のプロセスで1つのメッセージを受信してしまい、複数同時に実行される、といったケースが考えられます。

このようなケースを防ぐために可視性タイムアウトを設定しましょう。

1秒から12時間まで指定が可能です。

メッセージを受信してから、指定された時間まで、他のプロセスがそのメッセージを取得することができなくなります。


  • 例: プロセス1が、キューXから、メッセージAを取得した。メッセージAの可視性タイムアウトは1分間に設定されているので、プロセス2がキューXへメッセージ取得のリクエストを投げたとしても、プロセス2がメッセージAを1分間のうちは取得することができない

可視性タイムアウトの制限時間内に、処理を完了し、メッセージの削除を行いましょう。

よって、可視性タイムアウトの制限時間は、最大の処理時間をセットしたほうが良いでしょう。


デッドレタードキューを設定すること

先ほど、メッセージの削除処理は、処理が正常に終わった後にすることをおすすめしましたが、

「じゃあ、正常に処理が終わらない場合は削除されないから、ずっとキューにメッセージが残り続けて、延々と処理が反復して行われるじゃないか」と思われるかもしれません。

このような自体を防ぐために、デッドレターキューを設定しましょう。

正常に処理がされなかったメッセージを、別のキューに移動させることができます。

デッドレターキューは、キューごとに設定が可能です。

デッドレターキューを設定する際に、最大の受信回数を設定することができます。ここで設定した回数以上、メッセージが受信された(つまり、削除されずに何度も受信処理が行われたもの)を、指定したキューに移動させることができます。

定期的にデッドレターキューの中身を取得し、メールやSlackへ内容を送信する仕組みを作れば、容易にエラーが発生したことに気づくことができ、デバッグも楽に行なえます。


まとめ


  • 取得する際はロングポーリングを使おう

  • 重複実行しても問題のない処理を書こう

  • 処理順がどうしても重要な場合はFIFOキューを使おう

  • 処理がきちんと終わったらメッセージを削除しよう

  • 可視性タイムアウトを、処理にかかる時間だけ設定しよう

  • デッドレターキューを設定し、エラーハンドリングを行おう