APIの429エラー対策|Retry-Afterと指数バックオフの実装手順

APIが429 Too Many Requestsを返したときに、制限単位の確認、Retry-Afterの解釈、指数バックオフとジッター、停止条件、ログ設計までをNode.jsの例で解説します。

APIから429 Too Many Requestsが返ったら、すぐに同じリクエストを連打してはいけません。まず提供元が示す待ち時間と制限単位を確認し、再試行してよい処理だけを、間隔を広げながら限られた回数で再実行します。

この記事では、Node.jsの標準fetchを使い、Retry-After、指数バックオフ、ジッター(再試行時刻をランダムにずらす仕組み)を一つの処理へまとめます。各APIの具体的な上限値や独自ヘッダーは異なるため、実際に接続するサービスの公式資料も併せて確認してください。

429が返る理由

HTTPの仕様であるRFC 6585では、429は「一定時間内に送ったリクエストが多すぎる」状態を表します。ただし、何を1利用者とみなすか、どの範囲で回数を数えるかまでは共通仕様で決めていません。認証情報、Cookie、接続元、APIの機能などを基準に、提供元が独自に制限できます。

そのため、429は単なる通信失敗ではありません。短時間の集中、複数ワーカーからの同時実行、別処理との上限共有、契約プランの割当超過などを切り分ける必要があります。認証エラーや入力不備を同じ再試行処理へ混ぜると、成功しない通信を増やしてしまいます。

最初にレスポンス本文を安全に保存し、ステータス、日時、対象API、処理名を確認します。APIキーや個人情報はログへ残しません。エラーを追う基本手順はログから原因を切り分ける方法も参考になります。

制限単位を確認する

提供元の公式資料で、次の項目を確認します。

  • 1秒、1分、1日など、集計する時間枠
  • APIキー、利用者、接続元、組織、エンドポイントなど、回数を数える単位
  • 読み取りと書き込みで上限が分かれるか
  • 応答ヘッダーに残数やリセット時刻が含まれるか
  • SDKがすでに自動再試行するか

複数の場所で再試行すると、回数が掛け算になります。たとえばSDK、共通HTTP関数、ジョブ実行基盤がそれぞれ3回試す設計なら、意図した回数を超える可能性があります。再試行を担当する層は原則として一つに絞り、SDKの既定動作も実測します。

制限値はサービスや契約によって変わるため、この記事では固定値を示しません。公式資料に記載がない場合は、レスポンス本文とヘッダーを伏字化して提供元へ確認します。推測した上限を本番設定にしないでください。

Retry-Afterを読む

429のレスポンスには、次のリクエストまで待つ時間を示すRetry-Afterが付くことがあります。RFC 6585では付与は任意です。RFC 9110では値を「待つ秒数」または「HTTP日時」の2形式で定義しています。

Retry-After: 120
Retry-After: Fri, 24 Jul 2026 03:00:00 GMT

秒数なら受信時からその秒数、日時なら現在時刻との差を待ちます。過去の日時、負数として扱われる値、解釈できない値は採用せず、後述のバックオフへ切り替えます。日時形式は端末とサーバーの時計ずれの影響を受けるため、待ち時間が負にならないようにします。

function parseRetryAfter(value, now = Date.now()) {
  if (!value) return null;

  const seconds = Number(value);
  if (Number.isInteger(seconds) && seconds >= 0) {
    return seconds * 1000;
  }

  const timestamp = Date.parse(value);
  if (Number.isNaN(timestamp)) return null;
  return Math.max(0, timestamp - now);
}

API独自のリセット時刻ヘッダーがある場合、その意味と単位を公式資料で確認します。秒とミリ秒、待ち時間とUNIX時刻を取り違えると、即時再試行または極端な長時間停止につながります。

指数バックオフを実装する

Retry-Afterがないときは、試行するたびに待ち時間を増やします。基本形は初期待機時間 × 2の試行回数乗です。初回500ミリ秒なら、上限を設ける前の待機枠は500、1,000、2,000、4,000ミリ秒と増えます。これはこの記事の実装例であり、すべてのAPIに共通する推奨値ではありません。

Google Cloudの公式資料は、再試行に適した応答であり、かつ処理が冪等(同じ操作を複数回行っても結果が重複しない性質)であることを確認したうえで、指数バックオフとジッターを使うよう案内しています。まず利用中のSDKに検証済みの機能がないか確認し、自作する場合だけ次のように実装します。

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function fetchWithRetry(url, options = {}) {
  const maxRetries = 4;
  const baseDelayMs = 500;
  const maxDelayMs = 30_000;

  for (let attempt = 0; ; attempt += 1) {
    const response = await fetch(url, options);

    if (response.status !== 429) return response;
    if (attempt >= maxRetries) {
      throw new Error(`429が解消せず停止しました: retries=${attempt}`);
    }

    const retryAfterMs = parseRetryAfter(
      response.headers.get('retry-after'),
    );
    const backoffMs = Math.min(
      maxDelayMs,
      baseDelayMs * 2 ** attempt,
    );
    const waitMs = retryAfterMs ?? backoffMs;

    await sleep(waitMs);
  }
}

fetchは404や429を受けても、それだけでは例外を投げません。response.statusを確認します。ネットワーク切断やタイムアウトも対象にする場合は、429とは別の条件として設計してください。

ジッターで同時再試行をずらす

同じ時刻に失敗した多数のクライアントが、同じ計算式で同時に再試行すると、次の瞬間にも負荷が集中します。ジッターは待機枠の中からランダムな時間を選び、再試行の山を分散させます。AWSの公式設計資料も、指数バックオフ、ジッター、最大再試行値を組み合わせるよう案内しています。

先ほどのwaitMsを、そのまま待つ代わりに0から上限までの乱数へ変えます。

const jitteredWaitMs = Math.floor(Math.random() * (waitMs + 1));
await sleep(jitteredWaitMs);

ただし、提供元がRetry-Afterで最低待機時間を明示している場合、その値より短くする実装は避けます。Retry-Afterはそのまま下限として守り、ジッターは追加分またはヘッダーがない場合のバックオフへ適用する設計が安全です。

const waitMs = retryAfterMs ??
  Math.floor(Math.random() * (backoffMs + 1));
await sleep(waitMs);

乱数を使うテストでは、乱数生成関数を差し替えられるようにすると、待機時間の上限と下限を再現して検証できます。

429を受けたときの判断表

次の表は、再試行するかを決めるための入口です。最終判断では、接続先APIの公式仕様と処理の性質を優先してください。

状況待機時間再試行の判断確認すること
429で有効なRetry-Afterがある指定された秒数または日時まで冪等性と停止条件を満たす場合のみ候補値の形式、時計ずれ、API独自仕様
429でRetry-Afterがない指数バックオフにジッターを加える一時的な制限と判断できる場合のみ候補初期待機、倍率、上限、最大回数
400・401・403など原因が明確原則として待機では解決しない自動再試行せず原因を修正入力、認証、権限、契約状態
POSTなど状態を変更する処理API仕様による冪等性を確認できなければ停止冪等性キー、重複実行時の影響
最大回数・全体期限へ到達追加待機しない停止して上位処理へ返す最終エラー、経過時間、通知先

最大回数と停止条件を決める

再試行は必ず終わる設計にします。回数だけでなく、処理全体の期限、利用者が待てる時間、キューの滞留量を基準に停止条件を決めます。AWSの資料も、最大回数または最大経過時間を設け、再試行がバックログを作り続けないよう求めています。

特にPOSTなど状態を変更する処理は注意が必要です。クライアントには失敗に見えても、サーバー側では処理済みかもしれません。提供元が冪等性キーに対応する場合は公式仕様どおりに使い、対応しない処理を無条件に再送しません。GETだから常に安全とも決めつけず、APIの仕様を確認します。重複実行を防ぐ考え方はWebhookの二重実行を冪等性で防ぐ方法でも解説しています。

次の場合は再試行せず停止します。

  • 最大回数または全体の期限へ到達した
  • 中断信号を受けた
  • 400や401など、待っても直らない応答だった
  • 処理の冪等性を確認できない
  • 待機時間が業務上の締切を超える

停止時は元のエラーを握りつぶさず、上位処理へ返します。定期処理の失敗を見逃さない設計は自動処理の失敗通知を作る方法も参照してください。

ログと通知で運用を見直す

ログには、秘密を除いたリクエストの種類、HTTPステータス、試行番号、採用した待機時間、Retry-Afterの有無、最終結果、処理全体の経過時間を残します。URLに利用者IDやトークンが含まれる場合は、そのまま記録しません。

監視では429の件数だけでなく、全リクエストに占める割合、再試行後の成功率、停止まで達した件数、待機時間の分布を追います。同じ時間帯に増えるなら、呼び出しの平準化、キャッシュ、重複リクエストの統合、同時実行数の削減を検討します。上限の引き上げだけでなく、不要な通信を減らせないか先に確認します。

実装後は、429とRetry-Afterあり・なしを返すテスト用サーバーで、待機、最大回数、停止、ログを検証します。本番APIへ大量リクエストを送り、意図的に429を発生させる試験は避けてください。

よくある質問

429エラーは何秒待てば直りますか?

一律の秒数はありません。まずRetry-Afterと提供元の公式資料を確認し、指定がなければ上限付きの指数バックオフを使います。待てば必ず解消するとは限らないため、最大回数または全体期限も設けます。

Retry-Afterがない429はすぐ再試行してよいですか?

即時再試行は避けます。処理が再試行可能かを確認し、指数バックオフとジッターで間隔を広げます。認証、契約上限、恒常的な呼び出し過多が原因なら、待機だけでは解決しません。

429と503は同じ再試行処理で扱えますか?

共通化できる部分はありますが、意味は異なります。429はリクエスト頻度の制限、503はサービスが一時的に処理できない状態を示します。再試行対象、停止条件、通知をステータスごとに設定してください。

POSTリクエストも自動再試行できますか?

無条件にはできません。サーバー側で処理済みなのに応答だけ失われた場合、再送で二重登録や二重決済が起こり得ます。公式の冪等性キーや重複防止条件を確認できる場合だけ候補にします。

指数バックオフの初期値と最大回数は何回が適切ですか?

全APIに共通する正解はありません。提供元の推奨値、利用者が待てる時間、1回の処理時間、SDKの既定値を基に決め、429を模擬するテストで確認します。この記事の500ミリ秒・最大4回は実装例です。

一次情報

以下はすべて2026年7月24日に確認しました。

続き(結論と実データ)はnoteに置いています

ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。

noteで続きを読む Xをフォローする