Help us understand the problem. What is going on with this article?

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

More than 3 years have 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キューを使おう
  • 処理がきちんと終わったらメッセージを削除しよう
  • 可視性タイムアウトを、処理にかかる時間だけ設定しよう
  • デッドレターキューを設定し、エラーハンドリングを行おう
tomoya_ozawa
エンジニア js、tsが好きです。 もし記事の内容に間違いがありましたら、ご指摘のほど、よろしくお願いします…。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away