API からの 500、502、503、504 エラー: その意味と処理方法

500 から 599 までの状態コードは、一見有効に見える要求に対して、サーバーが処理に失敗したことを意味します。 要求は問題ではないので、もう一度送信するとうまくいく場合があります。 もう一度送信する必要があるかどうかは、状態コードと要求の内容によって異なります。 定義については、 RFC 9110、セクション 15.6 を参照してください。

各状態コードの意味

ステータスコード 意味 再試行しますか?
500 Internal Server Error サーバーが予期していなかった状態に遭遇した API によって異なります。 Claude API などの一部の API では、500 エラーが返された場合は、指数バックオフで再試行するように指示されています。 API のドキュメントを確認します。
502 Bad Gateway ゲートウェイまたはプロキシが、その背後にあるサーバーから無効な応答を受け取った はい (その要求を繰り返し実行しても問題ない場合)
503 Service Unavailable サーバーは一時的に負荷が高くなっているか、メンテナンスのため停止していますが、しばらくすれば復旧する可能性があります。 サーバーは、 Retry-After ヘッダーを送信できます。 はい、サーバーが 1 回送信した場合、Retry-After 回経過しています
504 Gateway Timeout ゲートウェイまたはプロキシが、その背後にあるサーバーから時間内に応答を受け取らなかった はい、リクエストを安全に繰り返せる場合。 ゲートウェイは待機をやめたので、サーバーが作業を行ったかどうかがわかりません。

再試行しても安全な要求

RFC 9110 では、同じリクエストを複数回送信したときの効果が 1 回送信したときと同じである場合、そのメソッドをべき等と呼びます。 GET、 HEAD、 OPTIONS、 TRACE、 PUT、および DELETE はべき等です。 POST と PATCH はそうではありません。 RFC によると、クライアントは、その要求がそもそもべき等性を持つことが分かっている場合、あるいはサーバーが元の要求を一度も処理していないことが確認できる場合を除き、非べき等なメソッドを用いた要求を自動的に再試行すべきではありません。 詳細については、「 べき等メソッド」を参照してください。

502 または 504 エラーの後に POST を再試行すると、2 回目の注文が行われたり、2 通目のメールが送信されたりすることがあります。 一部の再試行ライブラリでは、既定ですべてのメソッドが再試行されます。 たとえば、.NET Standard のレジリエンス ハンドラーは、options.Retry.DisableForUnsafeHttpMethods() を呼び出さない限り、POST を再試行します。 詳細については、回復性のあるHTTPアプリを構築するを参照してください。

503 の Retry-After

503 には、 Retry-After ヘッダーを含めることができます。 その値は、 120などの秒数か、 Fri, 31 Dec 1999 23:59:59 GMTなどの HTTP 日付のいずれかです。 コードで両方を処理する必要があります。 詳細については、「 Retry-After」を参照してください。

失敗し続けるAPIを呼び出すのをやめる

再試行は、一時的なエラーの解消に役立ちます。 API が数分間ダウンすると、すべてのリクエストを再試行するたびに、すでに逼迫しているサーバーに負荷が追加され、ユーザーは再試行が失敗するたびに待たされます。 サーキット ブレーカーはエラーを監視し、エラーが一定数を超えると、しばらくの間 API への呼び出しを停止して、迅速に処理を中断します。 その時間が過ぎると、少数のリクエストを通して、API が復旧したかどうかを確認します。 詳細については、「サーキットブレーカー パターン」を参照してください。 .NET標準の回復性ハンドラーには、少なくとも 100 個の要求がある 30 秒のウィンドウで少なくとも 10% の要求が失敗した場合に 5 秒間開くサーキット ブレーカーが含まれています。

5xx エラーを処理する方法

  1. 冪等リクエストに対してのみ、502、503、および 504 を再試行してください。 POST と PATCH については、安全に再試行できる方法が API ドキュメントに記載されている場合にのみ、再試行してください。
  2. 再試行する前に待ってください。 サーバーがそれを送信するときに Retry-After を使用します。 それ以外の場合は、ランダム ジッターで指数バックオフを使用し、数回試行した後に停止します。
  3. 500 の API のドキュメントを参照してください。 API で安全であると言われる場合にのみ、再試行してください。
  4. 失敗し続けるAPIの呼び出しをやめてください。 サーキット ブレーカーを使用して、API が復旧する間、アプリが速やかに失敗するようにしてください。
  5. 何が起こったかをユーザーに伝える。 一般的なエラーまたはスタック トレースの代わりに、"サービスで問題が発生しています。後でもう一度やり直してください" と表示します。
const RETRYABLE_STATUS = new Set([502, 503, 504]);
const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"]);

function retryAfterMs(response) {
  const value = response.headers.get("retry-after");
  if (!value) return null;
  const seconds = Number(value);
  if (Number.isFinite(seconds)) return seconds * 1000;
  const date = Date.parse(value);
  return Number.isNaN(date) ? null : Math.max(0, date - Date.now());
}

export async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  const method = (options.method ?? "GET").toUpperCase();
  const canRetry = IDEMPOTENT_METHODS.has(method);

  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url, options);
    if (!RETRYABLE_STATUS.has(response.status) || !canRetry || attempt === maxRetries) {
      return response;
    }
    await response.body?.cancel();
    const backoff = 2 ** attempt * 1000 + Math.random() * 1000;
    const wait = retryAfterMs(response) ?? backoff;
    await new Promise((resolve) => setTimeout(resolve, wait));
  }
}

アプリが 5xx エラーを処理することをテストする方法

開発中に 5xx が表示されることはほとんどありません。また、API を任意に失敗させることはできません。 テスト方法次第で、ユーザーより先にバグを見つけられるかどうかが決まります。

Approach 見つけたもの 欠落しているもの
運用環境の待機 実際の中断 すべて、ユーザーがクリックするまで
テストで API をモックするか、コーディング エージェントにモックを記述させる エラー ブランチが実行されるかどうか 実際の HTTP クライアントと再試行ライブラリ、そして実際に何回再試行するか。 アプリでは、モックに到達するためにテスト専用のスイッチも必要です。
実際の API を呼び出し、失敗するまで待機する 実際の動作 API を意図的に失敗させることはできません
アプリの実際のトラフィックをインターセプトし、選択した割合で 5xx エラーを返します 真の HTTP クライアント、再試行ライブラリ、およびサーキット ブレーカー アプリ自体には何の変化もないため、コードを単独でテストすることにはなりません。 それ用に単体テストを取っておいてください。

アプリで試す

開発プロキシ はアプリの要求を傍受し、GenericRandomErrorPlugin を使用して、ユーザーが定義したエラーでその一部を失敗させます。 アプリは実際の URL を呼び出し続けます。 設定ファイルにプラグインを追加し、その errorsFile を 5xx エラーが発生しているファイルに指定します。 この例では、 https://api.contoso.comを使用します。 これを、アプリが呼び出す API の URL に置き換えます。

ファイル: server-errors.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
  "errors": [
    {
      "request": {
        "url": "https://api.contoso.com/*"
      },
      "responses": [
        { "statusCode": 500 },
        { "statusCode": 502 },
        {
          "statusCode": 503,
          "headers": [
            { "name": "Retry-After", "value": "10" }
          ]
        },
        { "statusCode": 504 }
      ]
    }
  ]
}

デフォルトでは、このプラグインは要求の 50% を失敗させます。 開発プロキシの出力を確認し、アプリが GET の要求を再試行し、それぞれの POST を 1 回だけ送信していることを確認してください。 開発プロキシは、503 エラーが発生した際にアプリが Retry-After を待機しているかどうかを確認しないため、要求時間は自身で比較してください。 次に、 --failure-rate 100 を使用して開発プロキシを起動し、API が失敗し続けるときにアプリの動作を確認します。 詳細については、変更リクエストの失敗率を参照してください。 Dev Proxy をインストールするには、「Dev Proxy のセットアップ」を参照してください。

次のステップ

参照