【Amazon SQS】メッセージがDLQに遷移するタイミングを調べてみた

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

こんにちは。ディベロップメントサービス1課の山本です。

今回は、Amazon Simple Queue Service(以下、SQS)でメッセージがデッドレターキュー(以下、DLQ)に遷移するタイミングについて解説します。

意外なタイミングで遷移したので記載しました。

この記事の対象者は?

  • SQS や AWS Lambdaを使用している方
  • DLQ の設定について理解を深めたい方
  • 処理の失敗に対する挙動について詳しく知りたい方

DLQ とは

まず、DLQ について簡単に説明します。 AWS ドキュメントに丁寧に記載されておりますので、本記事ではその内容を抜粋します。 デッドレターキューとは何か - デッドレターキュー (DLQ) の説明 - AWS

デッドレターキュー (DLQ) は、エラーのためにソフトウェアシステムが処理できないメッセージを一時的に保存する特別なタイプのメッセージキューです。

具体的に SQS のメッセージを Lambda で処理する例で書いてみます。

正常な場合、Lambda での処理が完了すると SQS のメッセージを削除して一連のメッセージ処理が完了となります。

SQS処理(正常)

異常な場合、DLQの設定をしていると Lambda 側の処理が失敗しても SQS のメッセージは削除されないまま残り、再取得・処理されます。

SQS処理(異常)

X回(最大受信数)処理が失敗したメッセージを退避して、解析もしくは後ほど再実行するための一時格納用キューが DLQ となります。

DLQ に遷移するタイミング

ここで問題です。 最大受信数が 2 回の場合、 どのタイミングで メッセージは DLQ に格納されるでしょうか。

  • A:2回目の処理失敗時
  • B:3回目の処理開始時

答えは



なんと



ななんと!



B:3回目の処理開始時 です!

DLQへの遷移タイミング

てっきり、2回処理に失敗したタイミングで DLQへ遷移するものだと思っていました。

AWS 公式ドキュメント内にはこの記載を見つけられなかったのですが、公式ブログ(英語)にはそれらしきことが書いておりました。

Introducing Amazon Simple Queue Service dead-letter queue redrive to source queues | AWS Compute Blog

When you create a source queue, you can specify a DLQ and the condition under which SQS moves messages from the source queue to the DLQ. This is called the redrive policy. The redrive policy condition specifies the maxReceiveCount. When a producer places messages on an SQS queue, the ReceiveCount tracks the number of times a consumer tries to process the message. When the ReceiveCount for a message exceeds the maxReceiveCount for a queue, SQS moves the message to the DLQ. The original message ID is retained.

機械翻訳してみます。

ソースキューを作成する際に、DLQと、SQSがソースキューからDLQにメッセージを移動する条件を指定することができます。これを redrive ポリシーと呼びます。redriveポリシーの条件は、maxReceiveCountを指定します。プロデューサが SQS キューにメッセージを配置すると、ReceiveCount は、コンシューマがメッセージを処理しようとする回数を追跡する。メッセージの ReceiveCount がキューの maxReceiveCount を超えると、SQS はメッセージを DLQ に移動する。元のメッセージ ID は保持される。

つまり、処理開始時に ReceiveCount(受信数) と maxReceiveCount(最大受信数)を比較して、受信数が超過している場合 DLQ へと転送する処理のようです。

検証

準備

テスト用 Lambda 関数

SQS からメッセージを取得し、メッセージが存在すれば例外。存在しなければ正常終了するコードとなります。

import boto3
import os

# 環境変数からキュー URL を取得
QUEUE_URL = os.environ.get("SQS_QUEUE_URL")

sqs = boto3.client("sqs")

def lambda_handler(event, context):
    try:
        # SQS からメッセージを1件取得
        response = sqs.receive_message(
            QueueUrl=QUEUE_URL,
            MaxNumberOfMessages=1,
            WaitTimeSeconds=1
        )

        messages = response.get("Messages", [])
        if not messages:
            return

        # 必ず失敗させる
        raise Exception("強制失敗")

    except Exception as e:
        raise

テスト用 SQS

以下設定のtest_move_QUEUEを作成します。

  • 最大受信数:2
  • デッドレターキュー:test_move_DLQ

テスト用キュー

メッセージの格納

test_move_QUEUE に適当なメッセージを送信します。

確認

1回目の Lambda 実行

Lambda は例外終了。

元々のキューにメッセージが再格納。

2回目の Lambda 実行

2回目も Lambda は例外終了。

最大受信数失敗しても、元々のキューにメッセージが再格納。

3回目の Lambda 実行

3回目はメッセージが取得できないため、Lambda は正常終了。

このタイミングで、DLQ への遷移を確認。

おまけ

今回は Lambda 関数内で SQS メッセージの取得処理を書いたので、3回目を実行するまでは元のキューにメッセージが残り続ける形となりました。
Lambda トリガーに SQS を設定している場合は2回(最大受信回数)失敗しても、すぐポーリングでメッセージ取得が発生するので DLQ への遷移が時間差なく行われます。

まとめ

  • DLQ の遷移タイミングは、最大受信回数 + 1 回目にメッセージを取得する時
  • Lambda トリガーに SQS を設定している場合は、そこまで気にしなくていい

最後に

2回失敗したらすぐ DLQ に送られるものだと思ってました。。。
本ブログがどなたかのお役に立てれば幸いです。

山本 真大(執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス1課

2023年8月入社。カピバラさんが好き。