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 ヘッダーを送信できます。 はい、サーバーが送信した場合は Retry-After 時刻の後です
504 Gateway Timeout ゲートウェイまたはプロキシが、その背後にあるサーバーから時間内に応答を受け取らなかった はい、リクエストを安全に繰り返せる場合。 ゲートウェイは待機をやめたので、サーバーが作業を行ったかどうかがわかりません。

再試行しても安全な要求

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

502 または 504 の後で再試行された POST は、重複した注文を作成したり、メールを重複送信したりする可能性があります。 一部の再試行ライブラリでは、既定ですべてのメソッドが再試行されます。 たとえば、.NET標準の回復性ハンドラーは、POSTを呼び出さない限り、options.Retry.DisableForUnsafeHttpMethods()を再試行します。 詳細については、回復性のある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 回だけ送信することを、Dev Proxy の出力で確認します。 開発プロキシは、アプリが 503 で Retry-After を待機しているかどうかを確認しないため、リクエスト時間を自分で比較してください。 次に、 --failure-rate 100 を使用して開発プロキシを起動し、API が失敗し続けるときにアプリの動作を確認します。 詳細については、変更リクエストの失敗率を参照してください。 Dev Proxy をインストールするには、「Dev Proxy のセットアップ」を参照してください。

次のステップ

参照