デッドレターキュー(DLQ)は、何度受信しても処理に成功しない(削除されない)メッセージを移しておく、別の SQS キューです。 元のキューに RedrivePolicy を設定し、maxReceiveCount 回を超えて受信されたメッセージが DLQ に移ります。原因を直したら、start-message-move-task で元のキューに戻して再処理します。

# ① DLQ を作る(保持期間は最大の 14 日)
aws sqs create-queue --queue-name orders-dlq --attributes MessageRetentionPeriod=1209600

# ② 元のキューに RedrivePolicy を付ける(3 回受信して削除されなければ DLQ へ)
aws sqs set-queue-attributes --queue-url "$SRC_URL" --attributes '{
  "RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq\",\"maxReceiveCount\":\"3\"}"
}'

# ③ 原因を直したら、DLQ から元のキューへ戻す
aws sqs start-message-move-task --source-arn arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq
設定 付けるキュー 内容
RedrivePolicy 元のキュー deadLetterTargetArn(DLQ の ARN)と maxReceiveCount(1〜1,000)
RedriveAllowPolicy DLQ どの元のキューがこの DLQ を使えるか(allowAll・byQueue・denyAll)
MessageRetentionPeriod DLQ 元のキューより長く(最大 14 日)

確認環境:AWS CLI 1.46.1・boto3 1.43.111(Linux)。AWS には接続せず、moto 5.2.4 のサーバーモードで、DLQ と RedrivePolicy の設定、受信を繰り返して DLQ に移る動き、list-dead-letter-source-queues、FIFO と標準キューを混ぜたときのエラー、Lambda のイベントソースマッピング(ReportBatchItemFailures)と CloudWatch アラームの作成を確かめました。部分的なバッチ失敗は、イベントソースマッピングと同じ手順(受信・ハンドラ呼び出し・成功分だけ削除)を boto3 で再現して動かしています。start-message-move-task は moto が対応していないため、botocore の Stubber でリクエストの形だけ確かめました。再処理の仕様・保持期間・Lambda 側の動き・上限値は AWS のドキュメントに基づいています(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQSデッドレターキューの設定|maxReceiveCountと再処理」

DLQ に移る仕組み

送信 → [orders]  受信1回目(ReceiveCount=1)→ 処理失敗、削除しない
                   ↓ 可視性タイムアウトが切れて再び見える
                  受信2回目(ReceiveCount=2)→ 失敗
                  受信3回目(ReceiveCount=3)→ 失敗
                  受信4回目の時点で ReceiveCount が maxReceiveCount(3) を超える
                   ↓
       [orders-dlq] に移る(元のキューからは消える)

SQS は「処理に失敗した」ことを知りません。 受信されたのに削除されないまま可視性タイムアウトが切れ、また受信される、という回数(ApproximateReceiveCount)だけを数えています。API リファレンスでは「ReceiveCount が maxReceiveCount を超えたとき DLQ に移す」とされています。

moto で maxReceiveCount=3・可視性タイムアウト 1 秒にして、削除せずに受信を5回繰り返した結果です。

--- receive 1
e4c1a4ed-269b-4082-a1be-e2877c67cbaa    1
--- receive 2
e4c1a4ed-269b-4082-a1be-e2877c67cbaa    2
--- receive 3
e4c1a4ed-269b-4082-a1be-e2877c67cbaa    3
--- receive 4
None
--- receive 5
None
--- DLQ
e4c1a4ed-269b-4082-a1be-e2877c67cbaa    {"orderId":"A-1001"}    4   1791826389010

3 回までは元のキューで受信でき、4 回目の受信で DLQ に移りました。DLQ でもメッセージ ID と本文はそのままで、元のキューの数は 0 になりました。

可視性タイムアウトと受信・削除の基本は、関連記事「Amazon SQSの使い方|送受信・削除と可視性タイムアウト、FIFO」で扱っています。

CLI での設定一式

1. DLQ を作り、ARN を取る

REGION=ap-northeast-1
DLQ_URL=$(aws sqs create-queue --queue-name orders-dlq \
  --attributes MessageRetentionPeriod=1209600 \
  --query QueueUrl --output text)
DLQ_ARN=$(aws sqs get-queue-attributes --queue-url "$DLQ_URL" \
  --attribute-names QueueArn --query Attributes.QueueArn --output text)

DLQ は、元のキューと同じアカウント・同じリージョン・同じ種類(標準なら標準、FIFO なら FIFO)にします。 ドキュメントでそう決められています。

2. 元のキューを作るとき(または後から)RedrivePolicy を付ける

RedrivePolicy の値は「JSON を文字列にしたもの」です。JSON の中に JSON 文字列を入れるため、ファイルに書くと引用符の扱いで迷いません。

cat > attrs.json <<EOF
{
  "VisibilityTimeout": "180",
  "RedrivePolicy": "{\"deadLetterTargetArn\":\"$DLQ_ARN\",\"maxReceiveCount\":\"5\"}"
}
EOF

SRC_URL=$(aws sqs create-queue --queue-name orders \
  --attributes file://attrs.json --query QueueUrl --output text)

# 既にあるキューなら
aws sqs set-queue-attributes --queue-url "$SRC_URL" --attributes file://attrs.json
aws sqs get-queue-attributes --queue-url "$SRC_URL" --attribute-names RedrivePolicy
{
    "Attributes": {
        "RedrivePolicy": "{\"deadLetterTargetArn\": \"arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq\", \"maxReceiveCount\": 3}"
    }
}
項目 意味・値
deadLetterTargetArn DLQ の ARN(URL ではない)
maxReceiveCount DLQ に移すまでの受信回数。コンソールの範囲は 1〜1,000。API リファレンスの既定は 10

DLQ を外すときは、RedrivePolicy を空文字にします。

aws sqs set-queue-attributes --queue-url "$SRC_URL" --attributes RedrivePolicy=""

3. DLQ 側に RedriveAllowPolicy を付ける

SRC_ARN=$(aws sqs get-queue-attributes --queue-url "$SRC_URL" \
  --attribute-names QueueArn --query Attributes.QueueArn --output text)

cat > allow.json <<EOF
{
  "RedriveAllowPolicy": "{\"redrivePermission\":\"byQueue\",\"sourceQueueArns\":[\"$SRC_ARN\"]}"
}
EOF
aws sqs set-queue-attributes --queue-url "$DLQ_URL" --attributes file://allow.json
redrivePermission 意味
allowAll(既定) 同じアカウント・リージョンのどのキューも、この DLQ を使える
byQueue sourceQueueArns に書いたキューだけ(最大 10 個)
denyAll どのキューも DLQ として使えない

4. どのキューの DLQ になっているかを見る

aws sqs list-dead-letter-source-queues --queue-url "$DLQ_URL"

queueUrls に、この DLQ を RedrivePolicy で指定している元のキュー(.../123456789012/orders)の URL が返りました。

設定の誤りで出たエラー

操作 エラー(moto で確認)
FIFO のキューに標準キューの DLQ を付けた InvalidParameterCombination ... Fifo queues cannot use non fifo dead letter queues
存在しない ARN を deadLetterTargetArn にした AWS.SimpleQueueService.NonExistentQueue ... Could not find DLQ for arn:aws:sqs:...:nope

文面は moto のもので、AWS の文面とは違うことがあります。キューの種類をそろえること、DLQ を先に作ることは、ドキュメントでも決められています。

maxReceiveCount の決め方

使い方 目安(ドキュメント)
Lambda で処理する 5 以上。スロットリングなどで受信だけ増えても、すぐに DLQ に行かないように
1 にする 1 回失敗しただけで DLQ に移る。ドキュメントでは、再試行の余地を残すよう書かれている
自前のワーカー 「一時的な失敗で何回までやり直すか」+余裕

標準キューで maxReceiveCount が 3 より大きいと、3 回以上受信されて削除されていないメッセージは、キューの後ろに回されます。 ドキュメントの記載です。何度も失敗するメッセージが、ほかのメッセージの処理を妨げにくくなります。

Lambda と組み合わせるとき:部分的なバッチ失敗

Lambda のイベントソースマッピングは、SQS からメッセージをまとめて(バッチで)受信して関数を呼び、成功したら削除します。関数が例外を出すと、バッチ全部が削除されず、全部が再受信されます。成功したメッセージまでやり直し、受信回数も増えます。

ReportBatchItemFailures を有効にすると、失敗したメッセージの ID だけを返して、それだけをやり直せます。

aws lambda create-event-source-mapping \
  --function-name order-worker \
  --event-source-arn "$SRC_ARN" \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5 \
  --function-response-types ReportBatchItemFailures

# 既存のマッピングに付ける
aws lambda update-event-source-mapping --uuid "<UUID>" \
  --function-response-types ReportBatchItemFailures
import json

def lambda_handler(event, context):
    failures = []
    for record in event["Records"]:
        try:
            order = json.loads(record["body"])
            if order.get("amount", 0) < 0:
                raise ValueError("amount must be >= 0")
            print(f"processed {order['orderId']}")
        except Exception as e:
            print(f"failed {record['messageId']}: {e}")
            failures.append({"itemIdentifier": record["messageId"]})
    return {"batchItemFailures": failures}

イベントソースマッピングと同じ手順(受信 → 関数 → batchItemFailures 以外を削除)を moto のキューで再現しました。5 件のうち amount が負の 2 件だけが失敗する設定、maxReceiveCount=3 です。

round 1: received=5 receiveCount=['1', '1', '1', '1', '1'] deleted=3 batchItemFailures=2
round 2: received=2 receiveCount=['2', '2'] deleted=0 batchItemFailures=2
round 3: received=2 receiveCount=['3', '3'] deleted=0 batchItemFailures=2
round 4: no messages
round 5: no messages
DLQ: ['A-1', 'A-4']

成功した 3 件は1回目で消え、失敗した 2 件だけが 3 回受信されたあと DLQ に移りました。

関数の返り値(ドキュメント) 扱い
batchItemFailures が空・null、返り値が空・null 全部成功
itemIdentifier にメッセージ ID の配列 その ID だけ失敗(再受信される)
例外を出した 全部失敗
不正な JSON、itemIdentifier が空文字・null・存在しない ID、キー名の誤り 全部失敗

Lambda 側の設定の目安

設定 目安(ドキュメント) 理由
キューの可視性タイムアウト 関数のタイムアウトの 6 倍以上(バッチウィンドウを使うならその秒数も足す) スロットリング中の再試行の余地。関数のタイムアウトより短いと、マッピングの作成・更新でエラー
maxReceiveCount 5 以上 再試行の前に DLQ に行かないように
DLQ の場所 SQS のキューに RedrivePolicy Lambda のイベントソースマッピング側ではない

moto でも、ReportBatchItemFailures 付きのマッピングは "State": "Enabled"・"FunctionResponseTypes": ["ReportBatchItemFailures"] で作られました。

Lambda 関数の設定にある「デッドレターキュー」(非同期呼び出し用)は、SQS のイベントソースでは使われません。 SQS から呼ぶ関数の失敗は、元のキューの RedrivePolicy で DLQ に送ります。Lambda のタイムアウトの調べ方は、関連記事「Task timed out afterの直し方|Lambdaタイムアウト原因別」で扱っています。

FIFO キューの場合

項目 内容(ドキュメント)
DLQ の種類 FIFO の DLQ(.fifo)。標準キューの DLQ は付けられない
順序 DLQ に移したメッセージは順序から外れる。厳密な順序が必要な処理には DLQ を使わないよう書かれている
重複排除 ID FIFO の DLQ に移るとき、重複排除 ID は元のメッセージ ID に置き換えられる
保持期間 DLQ に移った時点でエンキュー時刻がリセットされる(標準キューと違う)
Lambda の部分的な失敗 最初の失敗で処理を止め、失敗分とそれより後ろの未処理分を全部 batchItemFailures に入れる
再処理の戻し先 FIFO の DLQ からは FIFO のキューにだけ戻せる

FIFO で部分的なバッチ失敗を返す例です。

def lambda_handler(event, context):
    records = event["Records"]
    for i, record in enumerate(records):
        try:
            process(record)
        except Exception:
            # 失敗した1件と、まだ処理していない後ろの全件を返す(順序を守るため)
            return {"batchItemFailures": [{"itemIdentifier": r["messageId"]} for r in records[i:]]}
    return {"batchItemFailures": []}
aws sqs create-queue --queue-name orders-dlq.fifo \
  --attributes FifoQueue=true,MessageRetentionPeriod=1209600

DLQ から元のキューに戻す(redrive)

start-message-move-task

DLQ_ARN=arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq

# 元のキュー(RedrivePolicy で DLQ に送ってきたキュー)へ戻す
aws sqs start-message-move-task --source-arn "$DLQ_ARN"

# 戻し先を指定し、速さ(件/秒)を絞る
aws sqs start-message-move-task --source-arn "$DLQ_ARN" \
  --destination-arn arn:aws:sqs:ap-northeast-1:123456789012:orders \
  --max-number-of-messages-per-second 50

# 進み具合
aws sqs list-message-move-tasks --source-arn "$DLQ_ARN" --max-results 10

# 止める(RUNNING のときだけ)
aws sqs cancel-message-move-task --task-handle "<TaskHandle>"
項目 内容(API リファレンス・ドキュメント)
--source-arn DLQ の ARN。SQS のキューの DLQ になっているキューだけ(Lambda・SNS の DLQ は不可)
--destination-arn 省くと、それぞれの元のキューに戻る。指定するなら同じ種類のキュー
--max-number-of-messages-per-second 最大 500。省くと SQS が速さを決める
同時に動かせる数 1 つのキューにつき 1 つ。アカウント全体で 100
長さ 1 つのタスクは最大 36 時間
戻ったメッセージ 新しいメッセージ ID とエンキュー時刻が付く。保持期間もリセット
必要な権限 DLQ に sqs:StartMessageMoveTask・ReceiveMessage・DeleteMessage・GetQueueAttributes、戻し先に sqs:SendMessage(暗号化していれば KMS も)

botocore の Stubber で、start_message_move_task の引数の形を確かめました。MaxNumberOfMessagesPerSecond は数値で、文字列を渡すと送る前に止まります。

ParamValidationError Parameter validation failed:
Invalid type for parameter MaxNumberOfMessagesPerSecond, value: fast, type: <class 'str'>, valid types: <class 'int'>

原因を直してから戻します。 直さずに戻すと、また maxReceiveCount 回失敗して DLQ に戻ってきます。いきなり全部を戻さず、--max-number-of-messages-per-second を小さくして様子を見る方法がドキュメントで勧められています。

自分で戻す(スクリプト)

本文を直してから戻したい、一部だけ戻したい、というときは、受信 → 送信 → 削除を自分で書きます(SQS の再処理は、移動中の絞り込みや書き換えに対応していません)。

import boto3, sys

sqs = boto3.client("sqs")
dlq, dst = sys.argv[1], sys.argv[2]   # キューの URL
moved = 0
while True:
    msgs = sqs.receive_message(QueueUrl=dlq, MaxNumberOfMessages=10, WaitTimeSeconds=1,
                               MessageAttributeNames=["All"], VisibilityTimeout=60).get("Messages", [])
    if not msgs:
        break
    entries = []
    for i, m in enumerate(msgs):
        e = {"Id": str(i), "MessageBody": m["Body"]}      # ここで本文を直したり、絞り込んだりできる
        if m.get("MessageAttributes"):
            e["MessageAttributes"] = m["MessageAttributes"]
        entries.append(e)
    r = sqs.send_message_batch(QueueUrl=dst, Entries=entries)
    ok_ids = {s["Id"] for s in r.get("Successful", [])}
    sqs.delete_message_batch(QueueUrl=dlq, Entries=[{"Id": str(i), "ReceiptHandle": m["ReceiptHandle"]}
                                                   for i, m in enumerate(msgs) if str(i) in ok_ids])
    moved += len(ok_ids)
print("moved", moved)

moto で DLQ の 2 件を戻すと moved 2 と出て、元のキューの 2 件は ApproximateReceiveCount が 1 からの数え直しになっていました。FIFO のキューに戻すときは、MessageGroupId(と、内容による重複排除を使わないなら MessageDeduplicationId)も付けます。

送信に成功したものだけを消しているので、途中で止まっても失われません(重複はあり得ます)。エラーの扱いは、関連記事「boto3のエラーハンドリング|ClientErrorのコード取得とリトライ設定」で扱っています。

保持期間の注意:エンキュー時刻が引き継がれる

標準キューでは、DLQ に移ってもエンキュー時刻(最初に送った時刻)は変わりません。 メッセージが消える期限は、元の送信時刻から数えられます。

例:元のキューに 1 日いて、DLQ の保持期間が 4 日 標準キュー FIFO キュー
DLQ で消えるまで 3 日後 4 日後
DLQ の ApproximateAgeOfOldestMessage DLQ に移った時刻から DLQ に移った時刻から

ドキュメントの例です。DLQ の保持期間は、元のキューより長くすることがベストプラクティスとされています。元のキューが 4 日(既定)で、DLQ も 4 日なら、元のキューで 4 日近くかかって移ったメッセージは、DLQ に着いてすぐ消えます。

キュー 保持期間の例
元のキュー 4 日(345600、既定)
DLQ 14 日(1209600、最大)

start-message-move-task で戻したメッセージは、新しいメッセージとして扱われ、保持期間も数え直しになります。

監視:DLQ にメッセージが入ったら知らせる

DLQ に1件でも入ったら通知するアラームです。ApproximateNumberOfMessagesVisible の最大値が 0 を超えたら、SNS に送ります。

aws cloudwatch put-metric-alarm \
  --alarm-name orders-dlq-not-empty \
  --namespace AWS/SQS \
  --metric-name ApproximateNumberOfMessagesVisible \
  --dimensions Name=QueueName,Value=orders-dlq \
  --statistic Maximum --period 60 --evaluation-periods 1 \
  --threshold 0 --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching \
  --alarm-actions arn:aws:sns:ap-northeast-1:123456789012:ops-alerts
見る指標 キュー 分かること
ApproximateNumberOfMessagesVisible DLQ 処理できなかったメッセージの数
ApproximateAgeOfOldestMessage DLQ 最も早く入ったメッセージの経過時間。保持期間に近づいたら急ぐ
ApproximateAgeOfOldestMessage 元のキュー 処理が詰まっていないか
NumberOfMessagesDeleted 元のキュー Lambda の部分的な失敗で、0 に落ちたら返り値の誤りを疑う(Lambda のドキュメント)

moto でアラームを作ると、orders-dlq-not-empty ApproximateNumberOfMessagesVisible 0.0 GreaterThanThreshold OK の状態で登録されました。アラームの作り方と SNS の通知は、関連記事「CloudWatchアラームをAWS CLIで作成|SNS通知・カスタム指標」で扱っています。

よくある質問

Q. DLQ を作っただけでは動きませんか。 A. 動きません。DLQ は普通のキューで、元のキューの RedrivePolicy で指定して初めて使われます。

Q. コンソールでメッセージを見たら、受信回数が増えました。 A. コンソールの「メッセージを送受信」でのポーリングも受信なので、ApproximateReceiveCount が増えます。maxReceiveCount が小さいと、見ただけで DLQ に移ることがあります。

Q. SNS のサブスクリプションや Lambda の非同期呼び出しの DLQ と同じですか。 A. どれも SQS のキューを DLQ にしますが、設定する場所が違います(SNS はサブスクリプションの RedrivePolicy、Lambda の非同期呼び出しは関数の DeadLetterConfig)。この記事の RedrivePolicy は、SQS のキューから受信する処理の失敗を扱います。SNS からのファンアウトは、関連記事「Amazon SNSの使い方 CLI・boto3とSQSファンアウト早見表」で扱っています。

Q. DLQ のメッセージはいつまで残りますか。 A. DLQ の保持期間(最大 14 日)までです。標準キューは元の送信時刻から数えるので、その分短くなります。

まとめ

  • 元のキューに RedrivePolicy(DLQ の ARN と maxReceiveCount)、DLQ に RedriveAllowPolicy
  • maxReceiveCount を超えた受信で DLQ に移る(moto では 3 にして4回目の受信で移った)。Lambda なら 5 以上
  • Lambda は ReportBatchItemFailures で失敗したメッセージだけをやり直す。可視性タイムアウトは関数のタイムアウトの 6 倍以上
  • FIFO は FIFO の DLQ。順序が大事な処理には DLQ を使わない
  • 戻すのは start-message-move-task(最大 500 件/秒、新しいメッセージ ID)
  • 標準キューはエンキュー時刻が引き継がれるので、DLQ の保持期間は元より長く(14 日)
  • DLQ の ApproximateNumberOfMessagesVisible > 0 でアラーム

参考・出典

確認日はいずれも 2026年10月13日です。

  • Amazon SQS Developer Guide「Using dead-letter queues in Amazon SQS」:https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html
  • Amazon SQS Developer Guide「Configure a dead-letter queue using the Amazon SQS console」:https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-dead-letter-queue.html
  • Amazon SQS Developer Guide「Learn how to configure a dead-letter queue redrive in Amazon SQS」:https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-dead-letter-queue-redrive.html
  • Amazon SQS API Reference「SetQueueAttributes」「StartMessageMoveTask」:https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_StartMessageMoveTask.html
  • AWS Lambda Developer Guide「Handling errors for an SQS event source in Lambda」:https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html
  • AWS Lambda Developer Guide「Creating and configuring an Amazon SQS event source mapping」:https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-configure.html
  • AWS re:Post ナレッジセンター「Lambda 関数が有効な Amazon SQS メッセージを再試行し、デッドレターキューに入れるのはなぜですか?」:https://www.repost.aws/ja/knowledge-center/lambda-retrying-valid-sqs-messages

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「SQSデッドレターキューの設定|maxReceiveCountと再処理」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?