【実務・中級編】 APIのステータスコード429 (Too Many Requests) の意味とRetry-Afterヘッダー – Web APIアーキテクチャ・データ連携実践ガイド

サーバーが悲鳴を上げる前に:HTTP 429 Too Many RequestsとRetry-Afterの正しい作法

インフラエンジニアとして現場を渡り歩いていると、「APIが繋がらない」というアラートほど心臓に悪いものはありません。特に、クライアントからの無慈悲なリクエストの波状攻撃によって、バックエンドのデータベースが悲鳴を上げている状況は、まさに地獄絵図です。

そんな時、設計者として「ただエラーを返す」のではなく、「どうすれば健全に回復できるか」をクライアントに伝えることこそが、優れたWeb APIの条件です。今日は、RFC 6585で定義された 429 Too Many Requests と、その相棒である Retry-After ヘッダーについて、現場の知見を交えて深く掘り下げていきます。

—

1. なぜ「429」なのか?:503 Service Unavailableとの境界線

初心者がやりがちなミスとして、レート制限超過時に 503 Service Unavailable を返してしまうケースがあります。しかし、これはプロトコル的には誤りです。

  • 503 Service Unavailable: サーバーが過負荷やメンテナンスで「一時的に処理できない」状態。クライアントに非はない。
  • 429 Too Many Requests: クライアントが「許容範囲を超えてリクエストを送ってきた」状態。クライアント側に「少し自重してくれ」と伝えるためのステータスコードです。

この違いは重要です。負荷分散装置(ロードバランサー)やCDNの設定で、「429なら制限を強める」「503ならバックエンドの死活監視を行う」といった切り分けをするためにも、セマンティクスを正しく守ることは運用コストを劇的に下げます。

—

2. 賢い再試行を促す Retry-After ヘッダーの流儀

ただ「やりすぎだ」と拒絶するだけでは、クライアントは短時間のループで再試行を繰り返し、ネットワークを無駄に占有する「DDoSじみた挙動」を引き起こします。ここで登場するのが Retry-After ヘッダーです。

このヘッダーには、以下のいずれかを指定します。

1. 秒数 (HTTP-date または Delta-seconds): 「何秒待てばいいか」という整数値。
2. 絶対時刻 (HTTP-date): 「この時刻になれば再開してよい」という日付形式。

圧倒的におすすめなのは「秒数」です。クライアントとサーバーの時計が同期していないリスクを回避できるからです。

—

3. 実践:クライアント側の再試行ロジック

もしあなたがAPIクライアントを実装する側なら、以下のようなロジックを組み込むのが「大人の作法」です。単にリトライするのではなく、Retry-After を尊重しましょう。

Pythonによる実装例

import requests
import time

def call_api_with_retry(url):
    response = requests.get(url)
    
    # 429が返ってきた場合
    if response.status_code == 429:
        # Retry-Afterヘッダーを取得(デフォルトは60秒待機)
        wait_time = int(response.headers.get("Retry-After", 60))
        print(f"制限超過です。{wait_time}秒待機します...")
        time.sleep(wait_time)
        # 再帰的に再試行
        return call_api_with_retry(url)
    
    return response.json()

—

4. インフラ側での制御:Nginxによるレート制限

インフラスペシャリストとして、アプリケーションに負荷をかけずにフロントゲートウェイでこの制限をかけるのが最も効率的です。Nginxを例に挙げましょう。

Nginx設定ファイル (nginx.conf)

# IPアドレス単位でリクエストを制限する設定
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;

server {
    location /api/ {
        # 5リクエスト/秒を超えたら429を返す
        # nodelayを指定しないと、バーストしたリクエストはキューに入れられ、
        # 処理が遅延する可能性があります。即座に429を返すには必須です。
        limit_req zone=api_limit burst=10 nodelay;
        
        # 429エラーをカスタマイズ(必要に応じて)
        error_page 429 = @rate_limited;
    }

    location @rate_limited {
        add_header Retry-After 30 always; # 30秒待機させる
        return 429 '{"error": "Too Many Requests", "retry_after": 30}';
    }
}

—

5. 現場のシニアからのアドバイス:指数バックオフの重要性

最後に一つだけ、現場でよくある失敗談を。
Retry-After を守ることは基本ですが、もしサーバー側が何らかの理由でヘッダーを返せなかった場合、クライアントが即座にリトライを繰り返すと「自爆リトライ」の嵐が始まります。

必ず「指数バックオフ(Exponential Backoff)」を実装してください。
リトライするたびに「待機時間を2倍、4倍、8倍…」と増やし、さらにランダムな「ジッター(揺らぎ)」を加えることで、リクエストの集中を緩和できます。

  • 1回目失敗: 1秒待機
  • 2回目失敗: 2秒待機 + ジッター
  • 3回目失敗: 4秒待機 + ジッター

ネットワークというものは、常に不安定で、予測不可能です。だからこそ、APIの設計には「相手を信じすぎない」という謙虚な精神が必要なのです。

さあ、皆さんもセマンティクスを正しく守った、美しいAPI設計を心がけてください。それが結果として、あなたの深夜の呼び出し回数を減らす唯一の道なのですから。

コメント

タイトルとURLをコピーしました