TT Lab
はじめる
学ぶ 学習パス コース

キューと非同期API

無限リトライが作り出すもの

TT Labで続きを見る

一言でいうと

リトライには、上限、指数バックオフ、ジッター、そして諦めたあとの行き先が必要です。4つのうち1つでも欠けると、リトライは障害の増幅器です。

なぜ必要なのか

最も悪いリトライは、即座に、固定間隔で、無限に行うものです。サーバーが一時的な過負荷で遅くなったときに、すべてのクライアントがすぐにまた叩くと、かろうじて耐えていたサーバーが完全に崩れ落ちます。リトライが障害を治す代わりに、悪化させます。

1つ目の改善は、指数バックオフです。1秒、2秒、4秒、8秒と間隔を延ばします。失敗が続くほどリトライの圧力が指数的に減り、サーバーに息をつく余裕を与えます。

2つ目がジッターです。指数バックオフだけでは足りません。サーバーが一時的に死ぬと、1万のクライアントが同時に失敗を検知し、全員が同じ規則に従って、まったく同じ瞬間にリトライします。サーバーが回復しようとするその一瞬に1万が押し寄せ、また倒れます。そしてこの波は、2秒後、4秒後、8秒後にも繰り返されます。フルジッター(full jitter)は、0から計算されたバックオフ値までの間のランダムな時間を待つことで、この同期を壊します。

どう動くのか

上限と諦めも必要です。間隔が際限なく大きくならないように、最大待ち時間(例: 30秒)を置き、一定の回数のあとは諦めます。諦めたメッセージの行き先が、DLQ(dead-letter queue)です。

DLQはゴミ箱ではなく、調査対象の一覧です。そのため、メッセージだけを入れても役に立ちません。一緒に入れるべきものは4つです。元のメッセージ、失敗の原因(例外メッセージやステータスコード)、試行回数、そして最初に受信した時刻です。これがあって初めて「このメッセージはなぜここにあるのか」に答えられ、原因を直したあとに再処理できます。

再処理のスクリプトも、あらかじめ作っておく必要があります。午前3時にDLQが溜まったときに、再処理のツールをその場で作ることになると、ミスが起きます。

そして、どんな失敗でもリトライしてはいけません。リトライに意味があるのは、一時的なエラーだけです。メッセージ自体が間違っている場合(スキーマ違反、存在しない参照)は、100回試しても同じです。こうしたものは、最初の失敗ですぐにDLQに送るほうがよいです。この区別をしないと、間違ったメッセージ1つがキュー全体を塞ぎます。これを、ポイズンメッセージ(poison message)と呼びます。

現場での姿

DLQにアラートをかけないチームが多いです。DLQは静かなので、数週間後に発見されます。「DLQの深さ > 0」は、ページ(担当者の呼び出し)でなくても、最低でも1日に1回は誰かが見るべきメトリクスです。

もう1つ。DLQ自体のコンシューマーを作って自動で再処理させる設計は、危険です。原因がそのままなら、無限ループになります。再処理は、人が原因を確認したあとに、明示的にトリガーするほうが安全です。

バックオフにジッターがないと

失敗したジョブが同じ時刻に集中してリトライします。 下流が一時的に死んでから復活する瞬間に、その群れが一斉に押し寄せて、また殺します(thundering herd)。

# ❌ 모두가 정확히 1s, 2s, 4s 뒤에 재시도한다
delay = base * (2 ** attempt)

# ✅ 흩어진다 — full jitter
delay = random.uniform(0, base * (2 ** attempt))

# ✅ 최소 대기는 보장하면서 흩기 — decorrelated jitter
delay = min(cap, random.uniform(base, prev * 3))

full jitterが最も単純で、実測でもよく散らばります。上限(cap)を置いて、指数が際限なく大きくならないようにします。

リトライ回数は何で決めるのか

「3回」は慣例にすぎません。総待ち時間から逆算するほうがよいです。

하류가 보통 30초 안에 복구된다면 → 총 대기가 60초쯤 되게 잡는다
base=1s, 지수 2, 상한 20s → 1, 2, 4, 8, 16, 20 … 6번이면 51초

そして、リトライしても無駄な失敗は、すぐに諦めます。 400(不正なリクエスト)、401・403(権限)、404は、何回送っても同じです。これを区別しないと、永遠に失敗するジョブがキューを塞ぎます。

class Permanent(Exception): pass      # 재시도하지 않는다

if 400 <= status < 500 and status not in (408, 429):
    raise Permanent(f"고칠 수 없는 실패: {status}")

デッドレターキューを運用する方法

リトライを使い切ったジョブは、DLQに送ります。ところが、DLQに入れて終わりだと、誰も見ません。3つが一緒に必要です。

# 예: 원인을 고친 뒤 되돌리기
labhub-cli dlq replay --queue orders --since 2026-09-06T12:00 --limit 500

戻すときに一度にまとめて入れないことが重要です。5,000件を一度に再投入すると、それがそのままスパイクになって、また崩れます。レート制限をかけて、少しずつ流し込みます。

次のラボですること

キューのコンシューマーに指数バックオフとフルジッターを付け、上限をかけ、5回失敗したらDLQに送り、DLQのメッセージに原因と試行回数を入れ、最後に原因を取り除いてから再処理スクリプトで復旧します。