はじめに
SQSのデッドレターキュー(DLQ)、使っていますか?
AWSの公式ドキュメントには下記のように書いてありますが、「そんなケースに遭遇したことがない」方がほとんどかもしれません。
正常に処理 (消費) できないメッセージの送信先として、他のキュー (ソースキュー) をターゲットにすることができます
今回、その「そんなケース」に当たったので、キューの動きや送受信されているメッセージに焦点を当てて調べてみました。
デッドレターキューとは
公式ドキュメントが一番端的に説明していますので、日本語ドキュメントのページから引用します。
このキューは、正常に処理 (消費) できないメッセージの送信先として、他のキュー (ソースキュー) をターゲットにすることができます。デッドレターキューは、未使用のメッセージを分離して処理が失敗した理由を特定できるため、アプリケーションやメッセージングシステムのデバッグに役立ちます。
うーん、これだけではどう使えばいいのかイマイチつかみにくいですね。。理解している人からすれば「それ以外に書きようがない」という側面もありますが。。
実験用環境の準備
というわけで、実験用に下記3つのリソースを作ります。
リソース名(論理ID) | 役割 | 指定するDLQ |
---|---|---|
MainQ | 外からメッセージが投げ込まれるキュー | SubQ |
SubQ | MainQのデッドレターキュー | SuperSubQ |
SuperSubQ | SubQのデッドレターキュー | なし |
CloudFormationでサクッと作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "Nested queue sample", "Parameters": { "MaxReceiveCount": { "Type": "Number", "MinValue": 1, "MaxValue": 5, "Default": 2 } }, "Resources": { "MainQ": { "Type": "AWS::SQS::Queue", "Properties": { "RedrivePolicy": { "deadLetterTargetArn": { "Fn::Sub": "${SubQ.Arn}" }, "maxReceiveCount": { "Ref": "MaxReceiveCount" } } } }, "SubQ": { "Type": "AWS::SQS::Queue", "Properties": { "RedrivePolicy": { "deadLetterTargetArn": { "Fn::Sub": "${SuperSubQ.Arn}" }, "maxReceiveCount": { "Ref": "MaxReceiveCount" } } } }, "SuperSubQ": { "Type": "AWS::SQS::Queue" } } } |
できたものがこちらになります。
ここにテスト用のメッセージを送信しておきます。
投入するメッセージは下記のとおりです。
1 2 3 4 5 6 7 8 |
{ "MyUUID": "8677f8a6-a198-4169-8ffe-7b4bb2efd252", "Data": { "Key1": "Value1", "Key2": "Value2", "Key3": [ "Value3", "Value4", "Value5" ] } } |
これで準備完了です。
本題
MainQ
の詳細表示画面から「メッセージの送受信」に進みます。
MainQでの受信1回目
正しく受信できています。受信数が1になっていますね。
メッセージの内容も送った内容そのものです。
1 2 3 4 5 6 7 8 |
{ "MyUUID": "8677f8a6-a198-4169-8ffe-7b4bb2efd252", "Data": { "Key1": "Value1", "Key2": "Value2", "Key3": [ "Value3", "Value4", "Value5" ] } } |
MainQでの受信2回目
受信できました。受信数は2に変わりました。
MainQでの受信3回目
受信できなくなりました。
DLQに転送されるまでの最大受信回数を2
として(CloudFormation経由で)指定しましたので、期待する挙動です。
受信しようと思っていたメッセージはDLQとして指定したSubQ
に転送されましたので、そちらを見てみます。
SubQでの受信1回目
受信できました。受信数はMainQ
に住んでいたころの受信回数とあわせて3になっています。
注目なのは、「メッセージ内容がMainQ
で受信した内容と同一であること」です。
あとで再処理するときも、メッセージの再加工をすることなく「受信先のキューを変更する」だけで同一の処理が再適用できる点がうれしいですね。
1 2 3 4 5 6 7 8 |
{ "MyUUID": "8677f8a6-a198-4169-8ffe-7b4bb2efd252", "Data": { "Key1": "Value1", "Key2": "Value2", "Key3": [ "Value3", "Value4", "Value5" ] } } |
SubQでの受信2~3回目
2回目は受信に成功しましたが、3回目で受信できなくなりました。
これも、DLQとして指定したSuperSubQ
に転送されていますので、そちらを見てみます。
SuperSubQでの受信
受信できました。受信数は、延べ4回の受信を持ち越してきていますので5になっています。
やはりメッセージの内容は当初投入した内容から変わっていません。
1 2 3 4 5 6 7 8 |
{ "MyUUID": "8677f8a6-a198-4169-8ffe-7b4bb2efd252", "Data": { "Key1": "Value1", "Key2": "Value2", "Key3": [ "Value3", "Value4", "Value5" ] } } |
受信回数はいつカウントされる?
ポーリングベースの場合は、ReceiveMessage
アクションでメッセージを受信した後にDeleteMessage
アクションでメッセージを削除しなければ受信回数が積み上がっていきます。これはわかりやすいですね。
Lambdaのようなイベントソースマッピングの場合は、トリガーの発射とともにカウントアップします。関数が正常終了すればメッセージが削除され、正常終了しなかった場合はメッセージが削除されずキューに残り、再トリガーとともに受信回数が積み上がっていきます。
使い道
後続処理に冪等性があることが条件になりますが、後続の処理がOOMやタイムアウト等の理由により正しく処理を完了できなかった場合に、計算資源をより多く割り当てた別の系統で再処理を行う、などの方法が考えられます。AWSリソースをスケールアップしても、そのうえで動かすロジックは同じものが使える、という点がおいしいポイントですね。
また、DLQと言ってもそれはただの役割でしかなく、キューそのものとしては同じメトリクスが取得できますので、組み合わせ方によっては多段処理も可能になりますね。
まとめ
直前に書いたような再処理が設計しやすいのも、「DLQに転送されるメッセージそのものはオリジナルキューに送信されたメッセージと同一である」からこそですね。SQSよくできてますね!
ではまた!
投稿者プロフィール
- 根っこはインフラ屋な古いおじさん。
最新の投稿
- AWS2024年11月1日【小ネタ】cfnresponseが見つからない?
- CloudFormation2024年10月23日スポットインスタンスな起動テンプレートのインスタンスサイズを可変にしたい
- AWS2023年11月14日DLQを積み重ねる
- SQS2023年10月2日1分より短いサイクルで定期的にLambdaを実行する