指数バックオフ(Exponential Backoff)とは、処理が失敗したときに、リトライ(再試行)までの待ち時間を「1秒・2秒・4秒・8秒…」と倍々に延ばしていく方法です。実務では、待ち時間に上限を設け、さらにランダムなばらつき(ジッター)を加えて使うのが基本です。

通信エラーや一時的な混雑は、少し待てば回復することが多いため、リトライは有効な対策です。一方で、リトライの仕方を間違えると、障害中のサーバーにアクセスが集中し、回復を遅らせる原因にもなります。

この記事では、指数バックオフの計算式、ジッターの種類と選び方、リトライしてよいエラー・してはいけないエラー、Retry-Afterヘッダー、冪等性、リトライの増幅といった実務の注意点を、PythonとJavaScriptの実装例つきで解説します。

固定間隔のリトライでは、なぜ足りないのか

「失敗したら1秒待ってリトライ」を繰り返す固定間隔の方法には、2つの問題があります。

  • 障害が長引くと、無駄なリトライを繰り返す — 数分続く障害でも、1秒ごとに試し続けます
  • サーバーの回復を妨げる — 多くのクライアントが同じ間隔でリトライすると、回復しかけたサーバーに再びアクセスが集中します

指数バックオフは、失敗が続くほど間隔を広げることで、短い障害にはすぐ再試行し、長い障害にはサーバーを休ませるという両立を図ります。

計算式と待ち時間の例

待ち時間 = min(上限, 基準時間 × 2 ^ 試行回数)

基準時間1秒、上限30秒の場合、待ち時間は次のようになります。

何回目のリトライか 計算 待ち時間
1回目 1 × 2⁰ 1秒
2回目 1 × 2¹ 2秒
3回目 1 × 2² 4秒
4回目 1 × 2³ 8秒
5回目 1 × 2⁴ 16秒
6回目 1 × 2⁵ = 32 → 上限 30秒

上限を設けないと、10回目で約8分半、15回目で約4時間半と、待ち時間が実用的でない長さになります。上限は必ず設定します。

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

(記載例)出典:株式会社RJC「指数バックオフとは?リトライ間隔の決め方とジッターの実装」

ジッター:待ち時間をばらつかせる

指数バックオフだけでは、まだ問題が残ります。同じ時刻に失敗した多数のクライアントは、同じ時刻にリトライするからです。

例えば、サーバーが一瞬止まって1,000台のクライアントが同時に失敗すると、1秒後・2秒後・4秒後に、1,000台が一斉にリトライします。この「一斉に押し寄せる」現象は、サンダリングハード(Thundering Herd)と呼ばれます。

これを防ぐのがジッターで、待ち時間にランダムなばらつきを加え、リトライのタイミングを分散させます。

種類 計算方法 特徴
ジッターなし 待ち時間そのまま 一斉リトライが起きる
Full Jitter 0〜待ち時間の間でランダム 分散効果が最も大きい。多くの場面での推奨
Equal Jitter 待ち時間の半分+0〜半分の間でランダム 最低限の待ち時間を保証したいとき
Decorrelated Jitter 基準時間〜前回の待ち時間×3の間でランダム(上限あり) 前回の待ち時間をもとに決める方式

AWSのアーキテクチャブログの検証(Exponential Backoff And Jitter)では、ジッターなしに比べて、ジッターありの方式は完了までの総リクエスト数が大幅に少なく、中でもFull Jitterは処理の量と完了時間のバランスが良い結果でした。迷ったらFull Jitterを選ぶのが一般的です。

Full Jitter の待ち時間 = random(0, min(上限, 基準時間 × 2 ^ 試行回数))

リトライしてよいエラー、してはいけないエラー

リトライは、時間をおけば成功する見込みがあるエラーにだけ行います。リクエスト自体に問題があるエラーは、何度送っても同じ結果になります。

エラー リトライ 理由
接続できない・タイムアウト する(冪等な処理のみ) 一時的なネットワークの問題の可能性
408 Request Timeout する サーバー側のタイムアウト
429 Too Many Requests する(Retry-Afterに従う) 回数制限。待てば回復する
500 Internal Server Error 状況による 一時的なこともあるが、恒常的なバグの場合もある
502 Bad Gateway する 中継先の一時的な問題
503 Service Unavailable する(Retry-Afterに従う) 過負荷・メンテナンス
504 Gateway Timeout する(冪等な処理のみ) 中継先の応答遅れ
400 Bad Request しない リクエストの内容が誤っている
401 Unauthorized しない(トークンを再取得してから1回だけ) 認証の問題
403 Forbidden しない 権限の問題。何度送っても同じ
404 Not Found しない 対象が存在しない
409 Conflict 状況による 最新の状態を取得し直してから判断する
422 Unprocessable Content しない 入力内容の問題

4xx(クライアント側のエラー)は、408と429を除いて基本的にリトライしない、と覚えておくと判断しやすくなります。

Retry-Afterヘッダーを優先する

429や503のレスポンスには、「何秒後に再試行してよいか」を示すRetry-Afterヘッダーが付くことがあります。

HTTP/1.1 429 Too Many Requests
Retry-After: 30

サーバーが指定している場合は、自分で計算した待ち時間より、Retry-Afterを優先します。値は秒数のほか、Retry-After: Wed, 30 Sep 2026 10:00:00 GMT のように日時で指定される場合もあります。

リトライする前に確認すべき「冪等性」

冪等(べきとう)とは、同じ操作を何回行っても、結果が1回行ったときと同じになる性質です。

タイムアウトしたリクエストは、サーバー側では処理が完了している可能性があります。例えば「決済する」リクエストがタイムアウトし、実は決済が完了していた場合、リトライすると二重に決済されてしまいます。

  • GET・PUT・DELETE — HTTPの仕様上、冪等なメソッド。リトライしやすい
  • POST — 冪等ではない。そのままリトライすると、登録や決済が重複する可能性がある

POSTをリトライする場合は、冪等キー(Idempotency-Key)をリクエストに付け、サーバー側で「同じキーのリクエストは1回しか処理しない」ようにしておくのが定石です。決済APIなどでは、この仕組みが用意されていることが多くあります。

回数と、全体の締め切りを決める

リトライには、回数の上限と、全体でどれだけ待てるかの締め切りの両方を設定します。

処理の種類 回数の目安 全体の締め切りの考え方
ユーザーが画面で待っている処理 2〜3回 数秒以内。それ以上はエラー表示の方が親切
バックグラウンドのAPI連携 3〜5回 数十秒〜数分
バッチ処理・キューのジョブ 5〜10回 数分〜数時間。最後は失敗キューに移す

画面で待っているユーザーにとって、30秒のリトライは「固まった」のと同じです。誰がその結果を待っているかで、回数と締め切りを決めます。

実装例

Python

import random
import time
import urllib.request
import urllib.error

RETRYABLE_STATUS = {408, 429, 500, 502, 503, 504}

def call_with_retry(url, max_attempts=5, base=0.5, cap=20.0, deadline=30.0):
    start = time.monotonic()
    for attempt in range(max_attempts):
        try:
            with urllib.request.urlopen(url, timeout=5) as res:
                return res.read()
        except urllib.error.HTTPError as e:
            if e.code not in RETRYABLE_STATUS:
                raise                       # 400・401・404などはリトライしない
            retry_after = e.headers.get("Retry-After")
            error = e
        except (urllib.error.URLError, TimeoutError) as e:
            retry_after = None
            error = e

        if attempt == max_attempts - 1:
            raise error                     # 最後の試行も失敗
        if retry_after and retry_after.isdigit():
            wait = float(retry_after)       # サーバーの指定を優先
        else:
            wait = random.uniform(0, min(cap, base * 2 ** attempt))  # Full Jitter
        if time.monotonic() - start + wait > deadline:
            raise error                     # 全体の締め切りを超えるなら諦める
        time.sleep(wait)

JavaScript(Node.js 18以降・ブラウザ)

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);

async function fetchWithRetry(url, { maxAttempts = 5, baseMs = 500, capMs = 20000 } = {}) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    let res;
    try {
      res = await fetch(url, { signal: AbortSignal.timeout(5000) });
      if (res.ok || !RETRYABLE.has(res.status)) return res;   // 成功、またはリトライしないエラー
    } catch (e) {
      if (attempt === maxAttempts - 1) throw e;               // 通信エラー・タイムアウト
    }
    if (attempt === maxAttempts - 1) return res;
    const retryAfter = Number(res?.headers.get("Retry-After"));
    const wait = retryAfter > 0
      ? retryAfter * 1000                                     // サーバーの指定を優先
      : Math.random() * Math.min(capMs, baseMs * 2 ** attempt); // Full Jitter
    await sleep(wait);
  }
}

どちらも、GETのような冪等なリクエストを前提にした例です。POSTに使う場合は、冪等キーを付けてください。

ライブラリ・SDKを使う

実務では、実績のあるライブラリを使うと、細かな考慮漏れを防げます。

言語・環境 主な選択肢
Python tenacity
Java Resilience4j、Spring Retry
JavaScript p-retry
.NET Polly
AWS SDK リトライ(指数バックオフ+ジッター)が標準で組み込まれている
Google Cloud クライアントライブラリ リトライの設定が組み込まれている

クラウドのSDKを使っている場合は、SDK自体がすでにリトライしていることが多いので、その外側で自前のリトライを重ねていないか確認します(次の「増幅」の問題につながります)。

リトライの増幅に注意する

リトライで最も見落とされやすいのが、複数の層でリトライを重ねたときの増幅です。

ブラウザ → API → 内部サービス → データベース

各層がそれぞれ「最大3回試行」すると、一番奥では最大3 × 3 × 3 = 27回のアクセスが発生します。障害でデータベースが遅くなっているときに、アクセスが27倍になれば、回復はさらに遠のきます。

  • リトライは、原則として1つの層だけで行う(多くの場合、最も外側か、障害に最も近い層)
  • 内側の層は、リトライせずにすぐエラーを返す
  • システム全体で、リトライに使ってよいリクエストの割合を決める(リトライ予算)

サーキットブレーカーと組み合わせる

障害が長引いているときは、リトライを続けるより、しばらく呼び出しを止める方が、呼び出す側と呼ばれる側の両方にとって有益です。

サーキットブレーカーは、失敗が一定の割合を超えたら、一定時間は呼び出しをせずにすぐエラーを返し、時間が経ったら少しだけ試して回復を確認する仕組みです。

  • 指数バックオフ — 1回の処理の中で、どう再試行するか
  • サーキットブレーカー — システム全体として、いつ呼び出しを止めるか

前述のResilience4jやPollyなどのライブラリは、両方を組み合わせて使えます。

よくある質問

Q. 2倍ではなく、別の倍率にしてもいいですか? A. 問題ありません。2倍が一般的ですが、1.5倍などにして間隔の伸びを緩やかにすることもあります。重要なのは、上限とジッターを必ず組み合わせることです。

Q. 基準時間はどれくらいにすべきですか? A. 画面の操作に関わるAPIなら数百ミリ秒、バックグラウンドの処理なら1秒程度から始めることが多いです。相手のサービスのドキュメントに推奨値があれば、それに従います。

Q. タイムアウトの時間もリトライと関係しますか? A. 関係します。1回のタイムアウトが30秒で3回リトライすると、最大で1分半以上待つことになります。1回あたりのタイムアウトと、全体の締め切りをセットで設計します。

Q. 固定間隔のリトライを使ってよい場面はありますか? A. 呼び出し先が1つで、呼び出す側も1つしかないような、アクセスの集中が起きない場面なら、固定間隔でも問題になりにくいです。複数のクライアントから同じサービスを呼ぶ場合は、指数バックオフとジッターを使います。

まとめ

  • 指数バックオフは、待ち時間を倍々に延ばすリトライ方法。上限は必ず設ける
  • 同時に失敗したクライアントの一斉リトライを防ぐため、ジッター(迷ったらFull Jitter)を加える
  • リトライするのはタイムアウト・408・429・502・503・504など。400・401・403・404・422はリトライしない
  • Retry-Afterヘッダーがあれば、それに従う
  • POSTのリトライは、冪等キーで二重処理を防ぐ
  • 回数と全体の締め切りは、誰が結果を待っているかで決める
  • 複数の層でリトライを重ねると増幅する。長い障害にはサーキットブレーカーを組み合わせる

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

(記載例)出典:株式会社RJC「指数バックオフとは?リトライ間隔の決め方とジッターの実装」

株式会社RJCでは、「失敗したときにどう振る舞うか」まで設計することが、安心して使えるシステムにつながると考えています。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。

株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。

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で一緒に開発しながら、 成長を楽しみませんか?