SQS のデッドレターキューのリドライブ機能を試してみた。

記事タイトルとURLをコピーする

こんにちは。技術課の山本です。🐱

SQS のデッドレターキューのリドライブ機能の概要

以下に記載する公式さんのブログを読んだほうが早い気もするものの、下に自分の言葉で説明します。
Introducing Amazon Simple Queue Service dead-letter queue redrive to source queues | AWS Compute Blog

また、SQS 自体の概要については、以前ブログ記事を書きました。SQS の概要については以下をご参照ください。
Kinesis と SQS の使い分けについて調べてみた [後編:SQS の概要を把握する。] - サーバーワークスエンジニアブログ

そもそも「デッドレターキュー」とは

SQS において、正常時、メッセージについては以下のように処理をします。

  1. プロデューサーがキューにメッセージを入れる
  2. コンシューマー がキューからメッセージを取り出して処理する
  3. コンシューマーが処理したメッセージを削除する

なお、コンシューマーがメッセージを取り出して処理している間、他のコンシューマーは同じメッセージを取得できません。 他のコンシューマーがメッセージを取得できない期間は「可視性タイムアウト」という値で設定できます。0 秒~12 時間 で設定可能です。

参考 (設定画面):

また、コンシューマーが正常にメッセージを処理できなかった場合には、メッセージを削除しないように実装します。可視性タイムアウトが経過した後に、次に動く別のコンシューマーが処理するようにします。 後続のコンシューマーにリトライさせます。SQS のキューに入ったメッセージは定期的なポーリングをするように実装しましょう。

例えばメッセージ自体の構造や内容に問題があったりしてコンシューマーが処理できない場合、何回処理しても失敗することになります。
その場合、あるメッセージがキューの中に残り続けて、他の、それ以外のメッセージを処理することに影響が出てきます。
イメージは以下です。

  1. プロデューサーがキューにメッセージを入れる
  2. コンシューマー がキューからメッセージを取り出して処理する ▶ 失敗!!!削除しない! (失敗 1 回目)
  3. キューに設定した「可視性タイムアウト」の期間、後続のコンシューマーは 2 で失敗したメッセージを取得できない。そのためプロデューサーが入れた他のメッセージがあれば、それを処理する。
  4. 「可視性タイムアウト」の期間が過ぎ、後続のコンシューマー がキューから 2 で失敗したメッセージを取り出して処理する ▶ 失敗!!!削除しない! (失敗 2 回目)

このように失敗を繰り返していきます。
そんなとき、「デッドレターキュー」の出番になります。

「デッドレターキュー」には普通のキューを指定します。
キューがスタンダードキューならば、デッドレターキューもスタンダードキューとして事前に作成しておきます。
FIFO の場合は、FIFO のキューを事前に作成しておきます。
そして、「 XX 回失敗したらデッドレターキュー YY に入れるよ」という設定をします。

参考 (設定画面):

「最大受信数 」(Maximum receives)というのは、メッセージを取得した回数の最大値です。「XX 回失敗したら〜」の部分ですね。
このように、「取得して削除する際に失敗を繰り返すメッセージについては、他のメッセージを処理する邪魔になるので、別のキューに移してしまおう」と考えたときに、デッドレターキューを利用できます。

「デッドレターキュー」の実際の動作を確認

では、実際にデッドレターキューを試してみましょう。

  • キュー名:yamamoto
    • 可視性タイムアウト:30 秒 (デフォルト)
  • デッドレターキュー名:yamamoto-dlq (事前に要作成)
    • 最大受信数:10 (作成時のデフォルト)

10 回失敗したら デッドレターキューに入るということですね。

SQS キューにメッセージを送信する AWS CLI のコマンドを用意して、キューに 'Hello World' というメッセージをいれました。
なお、メッセージの送受信は、AWS マネジメントコンソールからもできます。AWS CLI の方が分かりやすいので、本記事では AWS CLI で実施します。
メッセージ送信コマンドです。

# 送信
QUE_NAME='yamamoto'
MSG='Hello World'
QUE_URL=$(aws sqs get-queue-url --queue-name $QUE_NAME --output text)
aws sqs send-message --queue-url $QUE_URL --message-body "$MSG"

無事にメッセージをキューに入れることができました。

AWS マネジメントコンソールでも利用可能なメッセージ数が 1 になっています。

次にメッセージを受信するコマンドで、メッセージを取得します。

# 受信
QUE_NAME='yamamoto'
QUE_URL=$(aws sqs get-queue-url --queue-name $QUE_NAME --output text)
aws sqs receive-message --queue-url $QUE_URL

メッセージを取得できました。
また、「可視性タイムアウト」である 30 秒の間は、同コマンドを何回実行しても、同じメッセージを取得できません。

AWS マネジメントコンソールでも処理中のメッセージになっています。

30 秒経つと、もう一度メッセージを取得できました。
1 回目で取得したメッセージを削除していないため、同じメッセージの 2 回目の取得です

AWS マネジメントコンソールでメッセージをポーリングすると、取得回数が増えています。9 回目の取得の後の画面です。10 回失敗するとデッドレターキューに入るので、もうリーチですよ。🐱

そして、メッセージを 10 回取得すると、デッドレターキューにメッセージが入り、元のキューからはなくなりました。
AWS マネジメントコンソールから確認しました。

デッドレターキューに入ったメッセージをどう処理する?

では、デッドレターキューに入ったメッセージをどう処理するか?考えます。
以下の 3 パターンに大別できるかと思います。

  1. メッセージの内容に問題があり、コンシューマーの処理がエラーになる。
  2. メッセージの内容は正しいものの、プロデューサーがメッセージを間違った形式で入れていたり、コンシューマーがメッセージを正しく処理をしていないため、エラーになっている。
  3. メッセージの取得・処理は正しくできており、メッセージの削除に失敗している。

1 の場合は、デッドレターキューに入ったメッセージを削除し、新しく正しいメッセージをキューに入れる必要がありそうです。

2 の場合は、プロデューサーやコンシューマーのプログラムを改修したあとに、デッドレターキューにあるメッセージをキューに入れ直す必要がありそうです。

3 の場合は、コンシューマー側にメッセージを削除する権限がなかったり、キューになんらかの障害が起きている可能性があるため、AWS 関連の設定やプログラムコードを見直すと良さそうです。そのあとに、デッドレターキューにあるメッセージをキューに入れ直す必要がありそうです。

2 , 3 のときには、問題への対処が終わった後に、本記事で紹介するリドライブ機能を使用すると良いでしょう。
リドライブは、デッドレターキューにあるメッセージを元のキューに入れ直す処理です。

デッドレターキューのリドライブ機能

AWS マネジメントコンソールからリドライブ

以下の記事を参考にリドライブします。

デッドレターキューに 1 つメッセージがあります。

デッドレターキューをクリックして出てくる画面に [DLQ 再処理の開始] ボタンがあります。押しましょう。

画面から分かる通り、メッセージを他のキューに入れ直すこともできます。
また、メッセージを元のキューに戻す速度( 1 秒あたりのメッセージ数)を変えることもできます。元のキューへの流量制御に使えそうですね。キューの現在のメッセージの量に応じて、デッドレターキューから戻すメッセージの量を変えることができます。
右下の「メッセージをポーリング」ボタンを押すと、今のタイミングでデッドレターキューに入っているメッセージもリドライブ対象にできます。
では、[ DLQ 再処理 ] ボタンを押しましょう。

ステータスに「正常に完了しました」と出ています。

デッドレターキューのメッセージ数が 0 になり、元のキューのメッセージ数が 1 に戻っています。

元のキューからメッセージを取得してみました。
MessageId が変わっているものの、メッセージの中身、MD5チェックサムは同じです。

AWS CLI からリドライブ

以下の記事を参考にリドライブします。

まず、AWS CLI のバージョンが古かったりすると、コマンドがない可能性もあるため、以下の資料を参考にアップデートしましょう。

デッドレターキューを対象にしてリドライブコマンドを実行します。
なお、元のキューを対象にしてしまうと、以下のエラーになります。デッドレターキューじゃないと言われています。

コマンドです。

DLQ_QUE_NAME='yamamoto-dlq'
DLQ_QUE_URL=$(aws sqs get-queue-url --queue-name $DLQ_QUE_NAME --output text)
DLQ_QUE_ARN=$(aws sqs get-queue-attributes --queue-url $DLQ_QUE_URL --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
aws sqs start-message-move-task --source-arn $DLQ_QUE_ARN 

リドライブを開始しました。

確認コマンドです。

aws sqs list-message-move-tasks --source-arn $DLQ_QUE_ARN 

すぐ完了になっていました。メッセージが多くある場合は、「ApproximateNumberOfMessagesMoved」に移動済み件数が出るので、進捗を確認できます。
"StartedTimestamp": 1700798954934 は、2023/11/24 13:09:14 です。UNIXTIME の変換しないといけない感じです。中身は Lambda で動いているのでしょうかね・・(邪推)。

メッセージが元のキューに戻っていました。

2023/11/24 13:09 のメッセージが 6 つあります。 成功ですね。

他の主なオプションには以下があります。

--destination-arn:リドライブ先のキューを指定可能
--max-number-of-messages-per-second: 1 秒あたりのメッセージ数 を設定可能

なお、コマンドの方は FIFO キューをサポートしていなかったり、少し制約がありますのでご注意ください。

2023/11/28 追記
FIFO キューもサポートされたようです🎉
Amazon SQS announces support for FIFO dead-letter queue redrive

(参考) デッドレターキューに入るメッセージの監視

ApproximateNumberOfMessagesVisible という CloudWatch メトリクスがあります。
SQS キューに入っているメッセージ数を確認できるメトリクスです。
デッドレターキュー でこの値が 1 以上になったことをきっかけに通知するようにCloudWatch アラームなどを設定しておくと、エラーの把握に役立ちます。
CloudWatch アラームでは Lambda 等も呼び出せるので、場合によっては上に紹介したリドライブの API 等を使って、なんらかの自動化もできるかもしれません。

参考:利用可能です。 CloudWatch Amazon SQS が - Amazon Simple Queue Service

その他、検証の際に気づいたこと

「可視性タイムアウト」を 0 秒にして、AWS マネジメントコンソールでメッセージをポーリングすると、ボタンを 1 回押しただけなのに、受信数が 1 よりも大きくなったのでびっくりしました。

可視性タイムアウト: 0 秒
デッドレターキューの最大受信数:1000
での検証結果です。

255 回取得されました。キュー内の複数メッセージを取得する処理なので、まあそりゃそうって感じですが。
ポーリングボタンにはご注意ください。とくに本番運用中は注意ですね。

まとめ

SQS のリドライブ機能を試してみました。
この機能がなかった頃は、メッセージの再作成や入れ直しをユーザーが手動や作り込み行っていたので、便利だなと感じました。

余談

ランニングの季節です。(いつもですが。)
紅葉が終わって葉が散ったあとの青空が美しいです。

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア(一応)

好きなサービス:ECS、ALB

趣味:トレラン、登山(たまに)