デッドレターキュー(DLQ)は、何度受信しても処理に失敗するメッセージを本来のキューから外し、別の場所に保管しておくための退避先です。この記事では、メッセージがDLQへ移る条件、SQS・Pub/Sub・Service Bus・RabbitMQ・Kafka Connectでの設定値の違い、AWS CLIとgcloudによる設定手順、溜まったメッセージを再処理する流れ、滞留を検知するアラームの掛け方までを順に扱います。DLQを置いたのに誰も中身を見ていない、という状態を避けるための設計判断も条件付きで示します。
まとめ:DLQは失敗の隔離先であり、再処理と監視を同時に設計して初めて機能する
DLQの役割は、処理できないメッセージ(毒メッセージ)が後続の処理を詰まらせないよう隔離することにあります。隔離しただけでは失敗は解決しません。原因を直したあとに元のキューへ戻す手順と、DLQに1件でも入ったら気づける監視をセットで用意して、ようやく運用に乗ります。
受信回数の上限は基盤ごとに既定値が違い、Pub/Subは既定5回、Service Busは既定10回、SQSは自分で決める方式です。Kafkaのようにブローカー側にDLQを持たない基盤もあります。厳密な順序が必要な処理や、呼び出し元へ即座にエラーを返す同期APIでは、DLQより別の失敗処理が向いています。
デッドレターキューの定義と、メッセージがDLQへ退避される4つの条件
毒メッセージを隔離し、後続の処理が詰まるのを防ぐというDLQの役割
名前の由来は郵便の「配達も返送もできない郵便物」を保管する部署で、メッセージングでも同じく「届け先で処理できず、捨てることもできないメッセージ」の置き場を指します。不正な形式のJSON、存在しない顧客IDを参照する注文、コンシューマーのバグで必ず例外になる入力などが典型例です。
DLQが無い場合、こうしたメッセージはキューに戻されて再配信され続けます。SQSのFIFOキューでは、失敗したメッセージが同じメッセージグループの後続を止めてしまうとSQSのCloudWatchメトリクスの説明に明記されています。1件の不良データが業務全体の遅延になる構造を断つのがDLQの仕事です。
受信回数超過・有効期限切れ・明示的な拒否・サイズ超過という退避条件
どの基盤でも、退避の引き金はおおむね次の4種類に分かれます。
- 受信・配信の回数超過:SQSのmaxReceiveCount、Pub/Subの最大配信試行回数、Service BusのMaxDeliveryCount
- 有効期限(TTL)切れ:Service BusやRabbitMQで、期限を過ぎたメッセージを退避する設定
- アプリケーションによる明示的な拒否:RabbitMQのbasic.rejectやService BusのDeadLetterMessageAsync
- サイズや長さの上限超過:RabbitMQのキュー長制限、Service BusのHeaderSizeExceeded
実務で最も多いのは1つ目の回数超過です。RabbitMQのDead Letter Exchangesのドキュメントでは、拒否・TTL切れ・キュー長超過・クォーラムキューのdelivery-limit超過の4つが条件として並び、キュー自体の期限切れでは退避されないと書かれています。
リトライと冪等性が前提になる理由と、DLQだけでは足りない場面
DLQはリトライを使い切ったあとの受け皿です。何回・どの間隔で再試行するかはリトライパターンの設計(指数バックオフとリトライ予算)の領分で、DLQの上限回数はその設計と整合している必要があります。
もう1つの前提が冪等性です。DLQから戻したメッセージは「一度は途中まで処理された」可能性を持ちます。在庫の引き当てや課金のように副作用がある処理は、冪等性キーや一意制約による二重処理の防止が無いと、再処理のたびに二重計上が起きます。
主要メッセージ基盤のDLQ設定比較:上限回数・既定値・保持の違い
5つの基盤で異なる受信回数の上限・既定値・DLQの作り方の比較表
同じ「DLQ」でも、作り方と既定値はかなり違います。2026年10月時点の各公式ドキュメントから要点をまとめました。
| 基盤 | 回数の設定 | 既定値 | DLQの実体 |
|---|---|---|---|
| Amazon SQS | maxReceiveCount | 自分で指定 | 別キューを作成 |
| Pub/Sub | 最大配信試行回数 | 5回(5〜100) | 別トピックを作成 |
| Service Bus | MaxDeliveryCount | 10回 | 自動のサブキュー |
| RabbitMQ | delivery-limit | クォーラムのみ | DLXで転送 |
| Kafka Connect | errors.tolerance | none | DLQトピック |
Service Busは配信不能キューの公式解説のとおり、キューごとに$deadletterqueueというサブキューが自動で付き、明示的に作る必要がありません。一方でDLQは自動で掃除されず、取り出して完了するまで残り続けます。Pub/Subの上限回数はDead-letter topicsのドキュメントで「概数」とされ、設定値より少ない回数で転送されることもあります。
KafkaのブローカーにDLQは無く、Connectやコンシューマーで退避先を組む構成
Kafkaのブローカーには、受信回数を数えて自動で退避する仕組みがありません。コンシューマーがオフセットを進めるかどうかを自分で決める設計のため、DLQはアプリケーション側で「失敗したレコードを別トピックへ書く」形で実装します。
Kafka Connectのシンクコネクタには標準の設定があります。Confluentのシンクコネクタ設定リファレンスによると、errors.toleranceの既定はnoneで、1件のエラーでタスクが停止する設定です。allにしてDLQトピック名を指定すると、変換や書き込みに失敗したレコードだけがDLQへ回ります。コネクタ全体の構成はKafka Connectの構成要素とコネクタ運用で扱っています。
# シンクコネクタの設定(失敗レコードをDLQトピックへ回す)
errors.tolerance=all
errors.deadletterqueue.topic.name=dlq-orders-sink
errors.deadletterqueue.topic.replication.factor=3
errors.deadletterqueue.context.headers.enable=true
context.headers.enableの既定はfalseです。trueにすると__connect.errors.で始まるヘッダに例外の情報が入るため、原因の切り分けが格段に楽になります。
Lambda非同期呼び出しのDLQと失敗時の送信先で、残る情報が異なる点
AWS Lambdaの非同期呼び出しには、関数単位のDLQと「失敗時の送信先(on-failure destination)」の2つの退避先があります。Lambda非同期呼び出しのレコードのキャプチャによれば、DLQに入るのはイベント本体とRequestID・ErrorCode・ErrorMessage(先頭1KB)の属性だけです。送信先のほうは、条件や試行回数、関数の応答まで含むJSONレコードを送ります。
送信先はSQS・SNSに加えてS3・Lambda・EventBridgeも選べます。新しく作るなら送信先を選び、既存のDLQ設定は理由が無ければ移行するのが筋です。EventBridgeとSQSを組み合わせた構成でDLQをどこに置くかはEventBridge・SQS・Lambda構成でのDLQの置き場所にまとめています。
DLQを設定する手順:AWS CLI・gcloud・rabbitmqctlのコマンド例
SQSのRedrivePolicyで受信5回後にDLQへ送る設定のコマンド例
SQSでは、先にDLQ用のキューを作り、そのARNを元キューのRedrivePolicyに書きます。DLQは元キューと同じAWSアカウント・同じリージョンに置く決まりです。
# 1. DLQを作る(保持期間は元キューより長い10日に)
aws sqs create-queue --queue-name orders-dlq \
--attributes MessageRetentionPeriod=864000
# 2. 元キューにRedrivePolicyを設定する(redrive.json)
# {"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq\",\"maxReceiveCount\":\"5\"}"}
aws sqs set-queue-attributes \
--queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/orders \
--attributes file://redrive.json
DLQ側に付けられるのがredriveAllowPolicyです。SQSのデッドレターキューの説明では、既定は全キューに許可、byQueueなら送信元を最大10個まで指定でき、denyAllでDLQとしての利用を禁止できます。本番では無関係なキューが紛れ込まないよう、byQueueで送信元を絞っておくことが事故を減らす対策です。FIFOキューや可視性タイムアウトとの関係はAmazon SQSのFIFOキューとDLQ設計で詳しく扱っています。
Pub/Subのデッドレタートピック作成と、2つのロールを付与する手順
Pub/Subでは、DLQはトピックとして作り、サブスクリプションに紐づけます。見落としやすいのが権限で、Pub/Subのサービスエージェントに、DLトピックへのパブリッシャー権限と元サブスクリプションへのサブスクライバー権限の2つが必要です。
PROJECT_NUMBER=123456789012
SA_LOCAL="service-${PROJECT_NUMBER}"
SA_DOMAIN="gcp-sa-pubsub.iam.gserviceaccount.com"
gcloud pubsub topics create orders-dead-letter
gcloud pubsub subscriptions update orders-sub \
--dead-letter-topic=orders-dead-letter \
--max-delivery-attempts=5
gcloud pubsub topics add-iam-policy-binding orders-dead-letter \
--member="serviceAccount:${SA_LOCAL}@${SA_DOMAIN}" \
--role="roles/pubsub.publisher"
gcloud pubsub subscriptions add-iam-policy-binding orders-sub \
--member="serviceAccount:${SA_LOCAL}@${SA_DOMAIN}" \
--role="roles/pubsub.subscriber"
サブスクライバー権限が抜けていると、転送後に元のメッセージを確認応答できず、同じメッセージが元のサブスクリプションに残ります。DLトピック自体にもサブスクリプションを作っておかないと、退避したメッセージは誰にも読まれずに消えていく点に注意してください。トピックとサブスクリプションの基本構造はCloud Pub/Subの仕組みと送受信の実装で確認できます。
RabbitMQはポリシーでDLXを指定し、キューの再宣言を避ける設定
RabbitMQでは、退避先をキューではなくエクスチェンジ(DLX)で指定します。キュー宣言時の引数でも書けますが、公式はポリシーでの指定を勧めています。引数は、アプリを再デプロイしてキューを削除・再宣言しないと変えられないからです。
rabbitmqctl set_policy DLX "^orders$" \
'{"dead-letter-exchange":"orders.dlx","delivery-limit":5}' \
--apply-to queues --priority 7
DLXは同じvhostに存在している必要があり、退避の時点で無ければメッセージは黙って破棄されます。退避されたメッセージにはx-deathヘッダが付き、reasonにrejected・expired・maxlen・delivery_limitのいずれかが入るので、再処理の振り分けに使えます。
DLQに溜まったメッセージの再処理設計:原因の切り分けと戻し方の手順
再処理の前に、一時障害と恒久障害を失敗理由の属性で振り分ける
DLQの中身は、戻せば通るものと何度戻しても失敗するものが混ざっています。外部APIの一時的な停止で失敗したメッセージは原因の復旧後にそのまま戻せますが、データ不正やバグ由来のものは、修正を出すまで戻しても再びDLQへ落ちるだけです。
振り分けに使える材料は、基盤ごとに異なる属性です。Service BusにはDeadLetterReasonとDeadLetterErrorDescriptionがあり、RabbitMQにはx-death、Kafka Connectにはエラー用ヘッダ、Lambdaの退避先にはErrorCodeが付きます。SQSのRedrivePolicyで移ったメッセージには失敗理由を示す属性が付かないため、コンシューマーが失敗時にMessageIdと例外をログへ出しておくことが前提になります。
import boto3
sqs = boto3.client("sqs", region_name="ap-northeast-1")
url = sqs.get_queue_url(QueueName="orders-dlq")["QueueUrl"]
# 中身の確認だけ行い、削除はしない(可視性タイムアウト後に戻る)
resp = sqs.receive_message(
QueueUrl=url,
MaxNumberOfMessages=10,
WaitTimeSeconds=5,
VisibilityTimeout=30,
MessageSystemAttributeNames=["ApproximateReceiveCount", "SentTimestamp"],
MessageAttributeNames=["All"],
)
for m in resp.get("Messages", []):
print(m["MessageId"], m["Attributes"], m.get("MessageAttributes", {}))
SQSのStartMessageMoveTaskで毎秒の移動件数を絞って元のキューへ戻す
SQSには、DLQのメッセージを一括で戻すAPIがあります。StartMessageMoveTaskのAPIリファレンスによると、移動先を省略すれば元のキューへ戻り、移動速度は毎秒最大500件まで指定できます。1つのキューで同時に動かせるタスクは1つだけです。
aws sqs start-message-move-task \
--source-arn arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq \
--max-number-of-messages-per-second 10
# 進捗の確認と中止
aws sqs list-message-move-tasks \
--source-arn arn:aws:sqs:ap-northeast-1:123456789012:orders-dlq
aws sqs cancel-message-move-task --task-handle "<TaskHandle>"
速度を空欄にすると、滞留量に応じてSQSが速度を決めます。下流のDBや外部APIが復旧直後で弱っている場面では、10件程度から始めて様子を見るほうが安全です。この機能は送信元がSQSキューであるDLQだけが対象で、LambdaやSNSのDLQには使えません。その場合は自前のスクリプトで受信して再送信します。
戻す前に冪等性を確かめないと二重処理が起きる、FIFOでの順序の崩れ
再処理で最も多い事故は、途中まで成功していた処理をもう一度走らせることです。決済の確定まで進んでから通知の送信で失敗したメッセージを戻すと、決済が二重に走ります。冪等性キーで「処理済み」を判定できる作りかどうかを、戻す前に確認してください。
FIFOキューでは順序の問題も起きます。DLQへ移ったメッセージは元の並びから外れており、後続のメッセージはすでに処理済みの状態です。SQSの公式ドキュメントも、厳密な順序を守る必要がある場合はFIFOキューでDLQを使わないよう注意しています。
DLQの監視とアラート設計:見るべきメトリクスと誤検知を生む落とし穴
DLQの滞留件数メトリクスに1件以上でアラームを掛けるCLIの設定例
DLQの監視は、滞留件数が1件以上になったら通知、という単純な条件で十分です。DLQに入るのは「通常は起きないこと」なので、件数のしきい値を高くすると、気づいたときには保持期間切れで消えていることになります。
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 300 --evaluation-periods 1 \
--threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions arn:aws:sns:ap-northeast-1:123456789012:ops-alerts
あわせてApproximateAgeOfOldestMessageを見ると、放置されている期間がわかります。EventBridgeのターゲットに付けたDLQの調べ方はEventBridgeのログとDLQのERROR_CODEでの原因特定が参考になります。
NumberOfMessagesSentでは自動退避を数えられない、という計測上の注意
DLQへの流入件数をNumberOfMessagesSentで数えようとすると、0のまま動きません。SQSのメトリクス一覧には、maxReceiveCountを超えて自動で移ったメッセージはこのメトリクスに含まれないと書かれています。手動でDLQへ送信した分だけが数えられる仕様です。
ダッシュボードで「DLQ流入0件」と表示されていても安心できない、ということになります。DLQの監視にはApproximateNumberOfMessagesVisibleを使うよう公式も推奨しています。
DLQの保持期間を元キューより長くする理由と、エンキュー時刻の扱い
SQSの標準キューでは、DLQに移ったあとも有効期限は元のエンキュー時刻から数えられます。元キューで1日経ってからDLQ(保持4日)に移ると、残りは3日しかありません。週末をまたぐと調査前に消える計算です。
FIFOキューでは移動時にエンキュー時刻がリセットされ、DLQの保持期間をまるごと使えます。どちらの場合も、DLQの保持期間は元キューより長く取るのが公式のベストプラクティスです。
DLQを入れるべき処理と入れない処理:採用条件と見送る場面の線引き
非同期で順序に依存しない処理はDLQを必ず置き、退避先を監視する
キューやイベント経由で非同期に動く処理で、1件ずつ独立して成否が決まるものは、DLQを置かない理由がありません。メール送信、画像変換、外部SaaSへのデータ連携、注文イベントの集計などが該当します。
DLQを置くときは、上限回数・保持期間・アラーム・再処理の手順の4点を同時に決めます。DLQだけ作って監視が無い構成は、エラーを見えない場所へ移しただけで、DLQが無い状態より発見が遅れる分むしろ悪化します。
厳密な順序が要る処理や同期APIではDLQを置かず、別の失敗処理にする
口座残高の更新のように、前のメッセージが失敗したら後続も止めるべき処理では、DLQで失敗を外すと整合性が壊れます。この場合はDLQを置かず、処理を止めてアラートを上げ、人が判断してから再開する設計にします。
ユーザーの画面から呼ばれる同期APIも対象外です。呼び出し元へエラーを返せば利用者が再操作できるので、裏側で失敗を溜めておく意味がありません。DLQは「誰も待っていない処理」のための仕組みと割り切るのが判断の軸です。
設計レビューでDLQ運用の抜けを点検する観点と、外部へ相談する場面
既存システムのレビューでは、DLQの有無より「DLQに入った後」を確認します。アラームの通知先が実在する担当者に届くか、再処理の手順書があるか、再処理しても二重処理にならないか、の3点で多くの抜けが見つかります。
メッセージ基盤の選定やAWS上の非同期構成の設計から見直したい場合は、AWS・Google Cloud・Azureのインフラ構築支援で、キュー設計と監視の組み立てを含めてご相談いただけます。
デッドレターキューの設定・再処理・監視の判断についてのよくある質問
DLQの設計でよく寄せられる疑問に、公式ドキュメントの記述をもとに回答します。
DLQの最大受信回数(maxReceiveCount)は何回に設定すればよいですか?
設定の基準は、一時障害がリトライで回復するのに必要な回数です。SQSの公式は、1のような低い値だと1回の失敗でDLQへ移るため、回復力を持たせるなら高めに設定するよう勧めています。回数の目安は、Pub/Subの既定5回、Service Busの既定10回です。可視性タイムアウトとの掛け算で、DLQへ移るまでの時間が決まる点も考慮してください。
デッドレターキューとリトライはどう使い分けますか?
リトライは一時障害から自動で回復するための仕組みで、DLQはリトライを使い切っても失敗したメッセージの退避先です。どちらか一方を選ぶものではなく、リトライの上限に達した後の行き先としてDLQを置く関係にあります。リトライの回数と間隔はDLQの上限回数と整合させて設計します。
DLQに入ったメッセージは自動で消えますか?
自動で消えるかどうかは、基盤ごとに異なる仕様です。SQSは保持期間を過ぎると削除され、標準キューでは元のエンキュー時刻から数えられます。Service Busの配信不能キューは自動でクリーンアップされず、取り出して完了するまで残る仕様です。消える基盤では保持期間内に調査できるよう、アラームとの組み合わせが前提になります。
SQSのFIFOキューでもDLQは使えますか?
SQSのFIFOキューでもDLQは利用可能です。ただし、DLQへ移ったメッセージは元の順序から外れ、後続のメッセージが先に処理される仕組みです。SQSの公式ドキュメントも、正確な順序を維持する必要がある場合はFIFOキューでDLQを使わないよう注意しています。順序が業務上の整合性に直結するなら、DLQではなく処理停止とアラートで対応します。
KafkaでDLQを実装するにはどうすればよいですか?
Kafkaのブローカーには自動で退避する仕組みが無いため、アプリケーション側で失敗レコードを別トピックへ書き込みます。Kafka Connectのシンクコネクタなら、errors.toleranceをallにし、errors.deadletterqueue.topic.nameにトピック名を指定するだけで設定が可能です。自前のコンシューマーでは、例外を捕まえてDLQトピックへ送信してからオフセットを進める実装にします。
関連記事
- Amazon SQSとは?FIFOキューと標準キューの違い・重複排除・可視性タイムアウトとDLQの設計:SQSでのmaxReceiveCountの決め方とFIFOでの挙動の詳細。
- Azure Service Busとは?キューとトピックの仕組み・料金の実額と階層の選び方:配信不能サブキューを持つService Busの全体像。
- Cloud Pub/Subとは?トピックとサブスクリプションの仕組み・料金・Pythonでの送受信:デッドレタートピックを設定する前提の基本構造。
- RabbitMQとKafkaの違いとは?設計思想・性能・使い分けの判断基準:DLQの作り方が異なる2つの基盤の選び分け。
- EventBridgeとSQS・Lambdaの非同期構成|キューポリシー・部分バッチ応答・DLQの置き場所:AWSの非同期構成でDLQを置く位置の具体例。