Amazon SQSを使って得た知見をまとめておく

Amazon SQS(Simple Query Service)を使う機会があったので、その過程で得た知見をざっとまとめておく。

キューURL

キューを作成するとURLが発行される。
SQSのAPIを使用するときは、このURLをエンドポイントとして、操作対象のキューを指定できる。

なお、このURLは以下のような規則で命名される。

https://sqs.{リージョン}.amazonaws.com/{AWSアカウントID}/{キューの名前}

AWS Regions and Endpoints

メッセージの送信

キューへメッセージを送信するアクションには SendMessageSendMessageBatch があり、一度に複数のメッセージを送信したい場合は SendMessageBatch を使う。

送信可能なメッセージ件数は最大10件まで。
メッセージ件数分の SendMessageBatchRequestEntry をリクエストパラメータとして含める。
SendMessageBatchRequestEntry にはIdが必要であり、これは 一回のリクエストで送信される メッセージの中でユニークであれば良い。
このIdは SendMessageBatch の結果として返ってくるメッセージを識別するために利用する。

注意しなければいけないのは、 バッチ内の個々のメッセージが失敗していても、バッチリクエストとしては成功として結果を返すことがある こと。

以下は、AWS SDK for PHPを使用していた場合の戻り値。

<?php
// ...
[
    'Failed' => [
        [
            'Code' => '<string>',
            'Id' => '<string>',
            'Message' => '<string>',
            'SenderFault' => true || false,
        ],
        // ...
    ],
    'Successful' => [
        [
            'Id' => '<string>',
            'MD5OfMessageAttributes' => '<string>',
            'MD5OfMessageBody' => '<string>',
            'MessageId' => '<string>',
            'SequenceNumber' => '<string>',
        ],
        // ...
    ],
]

このように、失敗したメッセージは Failed へ、成功したメッセージは Successful へ格納されてレスポンスが返ってくる。
そのため、必ず失敗したメッセージがないかを確認し(どのメッセージが失敗したかの判別には SendMessageBatchRequestEntry のIdを使うと良いだろう)、適宜リカバリ処理を行う。

送信できるメッセージは最大で256KBと、大容量のデータ送信には不向きであるため、IDであったりリソースへのアクセスパスだったりをメッセージとして含めるのが良い。

メッセージの受信

キューからメッセージを受信するには ReceiveMessage アクションを使う。
このアクションで最大10件までのメッセージを受信することができる。

メッセージの受信にはいくつかの注意点がある。

順番が保証されない

できるだけ古いメッセージから受信するようになっているが、あくまでベストエフォートであり、その順番が完全に保証されているわけではない。
当然、受信するメッセージの順番は指定することができない。
順番が保証されている前提でメッセージ受信後の処理を実装するのは避けること。

全てのメッセージを受信できることを保証しない

例えば、キューの中にメッセージがあるにもかかわらず、受信メッセージが空の場合もあり得るため、受信メッセージが空であるからキューの中身が空という保証はない。
このように、キューの中にあるメッセージを必ず受信できるとは限らない。

メッセージが重複する可能性がある

メッセージ受信アクションが複数のコンシューマによって同時に実行された場合、タイミングの問題で同じメッセージを受信してしまうことがある。
また、意図せず複数回メッセージ受信が実行され、メッセージが重複してしまうことも考慮しなければならない。
同じメッセージを複数回受信しても問題ないように実装する。

できるだけ同じメッセージを受信しないように、受信メッセージを処理したら削除してしまうのが基本となる。
ただ、メッセージ受信→メッセージ受信後の処理→メッセージ削除という流れの間に、他のコンシューマが同じメッセージを受信してしまう可能性がある。
これを防ぐために Visibility Timeout を設定して、設定した時間だけ他のコンシューマからの同メッセージ受信を不可能にすると良い。
設定する時間は、受信から削除までの一連の処理時間よりも多めの時間を設定する。


なお、この辺りの問題を解消した「FIFOキュー」も用意されている。
ただし、標準キューとは違い、FIFOキューにはスループットに制限があるため、要件に合わせてFIFOキューを利用するかしないかを決めると良い。

ロングポーリングの設定

Receive Message Wait time を設定することでロングポーリングを有効にできる。
ロングポーリングを有効にすると、キューにメッセージが存在しない場合は接続した状態で受信処理を待機し、キューにメッセージが入ると即時受信するようになる。
これにより、キューが空のときのリクエスト数を削減できる。

メッセージの削除

メッセージを取得して無事処理が終わったら、一般的にはそのメッセージは削除することになる。
このメッセージ削除に必要になる識別子が ReceiptHandle である。
ReceiptHandleを指定することで削除対象メッセージを特定できるが、ReceiptHandleは同一メッセージでも取得するたびに変化する。
そのため、ReceiptHandleは常に最新のものを使用しなければならない。

エラーハンドリング

SQSを利用するときに限らず、キューにアクセスできなかった、メッセージ制限サイズを超えてしまった、などのエラーを想定して処理を組む必要がある。
また、何かしらの原因で処理が正常に行われずキューにメッセージが残り続けてしまう問題を解決しなければいけない。

このための機能としてデッドレターキューが用意されている。

指定回数以上のメッセージ受信が行われた場合、そのメッセージをデッドレターキューに移動する。
これでキューにメッセージが残り続けてしまう問題を解消することができ、正しく処理されなかったメッセージを分離することで、原因の調査に利用することもできる。

デッドレターキュー監視用のコンシューマを用意しておき、デッドレターキューからメッセージを受信したら通知を飛ばすようにするとメッセージが処理できない状況をすぐに把握できる。
また、デッドレターキューも通常のキューと同様メッセージ保持期間が存在するので、必要なら永続化しておく。

なお、複数のキューに同じデッドレターキューを紐付けることもできる。

開発環境について

開発環境からSQSの接続が制限される場合、「ElasticMQ」のようなツールを使うと良い。

www.tomcky.net

ただ、ElasticMQにはSQSのようにGUIがない(ElasticMQのためにGUIを提供しているOSSもあるが)ので、例えばキューにあるメッセージ件数を確認したい場合は GetQueueAttributes アクションを使い ApproximateNumberOfMessages などで確認できる。