API Rate Limiting429 Error HandlingProduction Reliability

LLM API レート制限と429エラーの本番対策:完全ガイド

約1分

429 Too Many Requests は、LLMプロバイダーが送る最も有用なレスポンスです。JSONボディよりも有用で、エラーコードよりも実用的です。なぜなら、そのヘッダーの中にリアルタイムの容量ゲージ——x-ratelimit-remainingretry-after——が埋め込まれているからです。ところが、ほとんどの本番コードはそれを無視します。エンジニアはレート制限を「ぶつかる壁」として扱います。しかし、それは「読むべきゲージ」なのです。

壁にぶつかったときに何が起きるかを見てみましょう。アプリは午後2時には正常に動いています。午後3時にトラフィックが増加し、3時15分にはすべてのリクエストが429を返します。リトライロジックが作動します——固定の1秒間隔です——そして群衆雪崩(thundering herd)を引き起こします。すべてのリトライが同じレート制限ウィンドウに衝突します。10分間にわたる連鎖的な障害。ユーザーはエラーを目にします。オンコール担当が呼び出されます。しかし、解決策は「もっと強くリトライする」ことではありませんでした。より強いリトライは、決して正解ではなかったのです。

この2つの結果——壁にぶつかるのか、ゲージを読むのか——の差は、3層のコードです。この記事では、その3層すべてを解説します。リアクティブ層:ジッター付き指数バックオフ。プロアクティブ層:残りの予算を読み、壁にぶつかる前に減速するヘッダー対応スロットリング。予測層:次の呼び出しが割り当て(クォータ)を使い果たす前に、エージェントの状態をチェックポイントする呼び出し前サスペンション。各層の動作するPythonコードと、本番で検証済みのパターンをお届けします。

レート制限処理の基盤となるAPI認証とキー管理の基本概念については、APIキーセキュリティガイドをご覧ください。

レート制限が存在する理由——そして実際の仕組み

レート制限は罰ではありません。インフラ保護です。すべてのAPIリクエストはGPUメモリとコンピューティングを消費します。スロットリングされていないクライアントは、数秒でプロバイダーの推論クラスターを飽和させかねません。レート制限は、すべてのユーザーに対する公平な割り当てを保証します。

注目すべき3つの制限:

  • RPM(Requests Per Minute): 1分あたりに実行できるAPI呼び出しの数。従量課金プラン:通常500〜3,000 RPM。無料枠:10〜50 RPM。エンタープライズ:カスタム。
  • TPM(Tokens Per Minute): 全リクエストを合計したトークン数——入力+出力。100Kトークンのプロンプト1つで、通常リクエスト500回分の割り当てを消費します。TPM制限はこれを防ぎます。
  • 同時リクエスト数: 同時に処理中にできるリクエストの数。これを超えると、新しいリクエストは待機または拒否されます。この制限は文書化されていないことが多く、痛い経験を通じて学ぶことになります。

プロバイダー別の階層が、これらの制限に具体的な数字を与えます。 OpenAIのTier 5(最高の従量課金レベル)は、GPT-4.xモデルに対して10,000 RPMと30,000,000 TPMを付与します——しかしTier 1はわずか500 RPMと200,000 TPMから始まります。AnthropicのClaude APIは、標準プランで1,000 RPMを提供し、Claude Opusでは80,000 TPM、Claude Sonnetでは400,000 TPMとなります。これはモデルごとの推論コストの違いを反映しています。

GoogleのGemini APIは、従量課金で1,500 RPMを提供し、TPMの上限は2,000,000です。各プロバイダーはモデルごとの上書き(per-model override)も適用します——Claude Opus 4のTPM制限がClaude Sonnet 4よりも厳しいのは、大きなモデルほど比例して多くのコンピューティングを消費するためです。OpenAIでTier 1からTier 5へ移行するには、支出履歴の増加(月額$250以上)と、30日以上にわたる不正利用のない使用実績の証明が必要です。

高い制限は丁寧にお願いして得られるものではありません——クラスターを氾濫させないことを証明する、持続的な本番トラフィックのパターンによって勝ち取るものです。自分の正確な階層と制限を知ることが、この後の3層を構築する最初のステップです。

現在の制限の読み方。 すべてのAPIレスポンスにはレート制限ヘッダーが含まれています——しかし、ほとんど誰も読みません。OpenAIのレート制限ドキュメントがヘッダーの形式と階層構造を説明しています:

x-ratelimit-remaining-requests: 487
x-ratelimit-remaining-tokens: 823000
x-ratelimit-reset-requests: 12s

これらは429レスポンスだけでなく、200レスポンスにも付与されます。スロットリングされるまでにどれだけの予算が残っているかを正確に教えてくれます。モニタリングダッシュボードにゲージとして表示しましょう。残りが20%を下回ったらアラートを出します。「レート制限に当たった」ではなく「それが来るのを予見して回避した」という状態を作るのは、これらのヘッダーを読むことです。

レート制限の恐怖譚:繰り返したくない2つのインシデント

欧州のあるECプラットフォームが、GPT-4.5を搭載したブラックフライデー向けAIショッピングアシスタントをローンチしました。QAは同時50ユーザーでテストしていましたが——本番では最初の1時間で2,300に到達。固定間隔のリトライが、429を47分間の障害に変えてしまいました。

収益損失:その時間帯に追跡されたカート放棄だけで$180,000。根本原因はトラフィック量ではなく——スパイクを吸収する代わりに増幅してしまったリトライロジックでした。

あるSaaS分析企業が、レート制限の変更履歴を読まずにOpenAI APIのバージョンを移行しました。新バージョンは、彼らの階層のRPMを3,000から1,500に半減させました。既存のスロットリングコードは、古い制限を前提としていました。

本番環境は6日間正常に動作していました——月次レポートサイクルがリクエスト量を3倍にするまでは。すべてのレポートジョブが同時に429に衝突しました。検知に22分かかったのは、モニタリングが5xxエラーだけを追跡し、429を追跡していなかったからです。

修正は3行だけ——RPM定数の更新。しかし教訓は永続的です:APIバージョンの移行はすべて、レート制限の移行でもあるのです。

層1:クライアント側スロットリング

最もシンプルな層です。トークンバケットまたはセマフォにより、アプリケーションがプロバイダーの規定制限を超えることを防ぎます。

import asyncio
import time

class RateLimiter:
    """Token bucket rate limiter for LLM API calls."""

    def __init__(self, max_rpm: int):
        self.max_rpm = max_rpm
        self.tokens = max_rpm
        self.last_refill = time.monotonic()
        self.semaphore = asyncio.Semaphore(max_rpm // 6)  # Concurrency cap

    async def acquire(self):
        """Wait until a request can be sent without exceeding RPM."""
        # Refill tokens based on elapsed time
        now = time.monotonic()
        elapsed = now - self.last_refill
        refill = elapsed * (self.max_rpm / 60)
        self.tokens = min(self.max_rpm, self.tokens + refill)
        self.last_refill = now

        if self.tokens < 1:
            wait_time = (1 - self.tokens) / (self.max_rpm / 60)
            await asyncio.sleep(wait_time)
            self.tokens = 1

        self.tokens -= 1

limiter = RateLimiter(max_rpm=500)

async def rate_limited_api_call(model: str, messages: list):
    await limiter.acquire()
    # Make the API call

重要な順序: 常に、同時実行セマフォの前にRPMトークンを取得してください。順序を逆にすると先頭ブロッキング(head-of-line blocking)が発生します——送信できないリクエストで同時実行スロットが埋まり、送信できるはずのリクエストが枯渇してしまいます。

この層は、最も一般的な自滅型のレート制限ミス——呼び出し速度を追跡していなかったために、自分の階層の規定制限を超えてしまう——を防ぎます。低〜中程度のトラフィックのほとんどのアプリケーションでは、これで十分です。

層2:ヘッダー対応バックオフ

層1は既知の制限を超えるのを防ぎます。層2は、プロバイダーの実際の容量が変動するときにどう対応するかを扱います——そして実際の容量は、クラスター全体の負荷に応じて、常に変動します。

import random
from openai import OpenAI

client = OpenAI(
    base_url="https://api.tokspan.com/v1",
    api_key="ts-your-key-here"
)

def chat_with_backoff(messages, model="claude-opus-4-8", max_retries=4):
    """Exponential backoff with jitter + header awareness."""
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model=model,
                messages=messages,
                timeout=30
            )
            # Read remaining budget from headers —even on success
            remaining = response.headers.get("x-ratelimit-remaining-requests")
            if remaining and int(remaining) < 50:
                print(f"Rate limit low: {remaining} requests remaining. Slow down.")
            return response

        except Exception as e:
            if "429" in str(e) or "rate_limit" in str(e).lower():
                if attempt == max_retries - 1:
                    raise  # Out of retries
                # Exponential backoff: 1s —2s —4s —8s
                wait = (2 ** attempt) + random.uniform(0, 1)
                print(f"Rate limited. Retrying in {wait:.1f}s (attempt {attempt + 1}/{max_retries})")
                import time
                time.sleep(wait)
            else:
                raise  # Not a rate limit error —don't retry

バックオフの3原則:

  1. 固定間隔のリトライは絶対にしないこと。群衆雪崩を引き起こします。t+1秒後にリトライするすべてのクライアントが、同じレート制限ウィンドウに衝突します。
  2. 常にジッターを加えること。+ random.uniform(0, 1) がリトライをウィンドウ全体に分散させます。これだけで、ほとんどの連鎖的な障害を防げます。
  3. 401、403、400はリトライしないこと。APIキーの誤りや不正なリクエストをリトライしても直りません。リトライすべきは429と5xxだけです。

やってはいけないこと。 本番コードで驚くほどよく見られるこのパターンは、あなたの言葉を理解しない相手に向かって大声で叫ぶのと同じくらい効果がありません:

# DO NOT DO THIS
while True:
    try:
        response = client.chat.completions.create(...)
        break
    except:
        time.sleep(1)  # Fixed interval, no jitter, infinite retry

これは群衆雪崩を引き起こし、レート制限されたままになることを保証します。すべてのリトライが、レート制限ウィンドウのまったく同じ時点に到達します。プロバイダーのインフラは同一リクエストのスパイクを検知してすべてをスロットリングし、アプリケーションは死のスパイラルに陥ります。

層3:予測的サスペンション

層1と層2はリアクティブです——制限に当たった(または近づいた)後に反応します。層3は予測的です——呼び出しの前に予算を読み、「続行」「短い待機」「チェックポイントしてサスペンション」を決定します。

def predict_rate_limit(response_headers: dict) -> str:
    """Three-valued decision based on remaining budget."""
    remaining_req = int(response_headers.get("x-ratelimit-remaining-requests", 1000))
    remaining_tok = int(response_headers.get("x-ratelimit-remaining-tokens", 1000000))

    if remaining_req > 100 and remaining_tok > 200000:
        return "continue"      # Plenty of budget
    elif remaining_req > 20:
        return "wait"          # Budget running low —short pause
    else:
        return "checkpoint"    # Budget nearly exhausted —suspend

# Usage in an agent loop:
for step in agent_steps:
    response = call_llm(current_state)
    decision = predict_rate_limit(response.headers)

    if decision == "continue":
        process(response)
    elif decision == "wait":
        time.sleep(5)  # Short pause, let budget recover
        process(response)
    else:  # checkpoint
        save_agent_state(current_state)  # Save progress
        time.sleep(60)  # Wait for rate-limit window reset
        resume_agent_from_checkpoint()  # Resume without losing work

2026年現在の最先端:agentpause すべてのレスポンスでレート制限ヘッダーを読み、次の呼び出しの前に枯渇を予測し、サスペンションの前にエージェント状態をチェックポイントするPythonライブラリです。測定結果:クラッシュ率0%(リアクティブ・ベースラインは100%)を達成。429エラーはゼロ。失敗したリトライによるトークンの無駄を80%削減。高スループットの本番エージェントを運用しているなら、agentpause または同等の予測ロジックはもはや選択肢ではなく——「私たちのエージェントは信頼できる」と「トラフィックが急増するとランダムにクラッシュする」の分かれ目なのです。

マルチプロバイダールーティング:最高のレート制限対策

上記のすべての戦略は、1つのプロバイダーと通信することを前提としています。最も効果的なレート制限戦略は、複数のプロバイダーと通信することです。

核心的な洞察: 1つのプロバイダーのレート制限がリセットされるのを待つ代わりに、リクエストを別のプロバイダーに送ります。実効レート制限は、ルーティングできるすべてのプロバイダーの合計になります。

プロバイダーごとのトークンバケットと自動クールダウンを備えたラウンドロビンルーター:

PROVIDERS = {
    "openai": {"rpm": 2000, "cooldown_until": 0},
    "anthropic": {"rpm": 1500, "cooldown_until": 0},
    "google": {"rpm": 1000, "cooldown_until": 0},
}

def route_request(messages):
    now = time.time()
    available = [
        p for p, cfg in PROVIDERS.items()
        if now > cfg["cooldown_until"]
    ]
    if not available:
        raise RuntimeError("All providers in cooldown")

    # Round-robin among available providers
    provider = available[hash(str(messages)) % len(available)]
    try:
        return call_provider(provider, messages)
    except RateLimitError:
        PROVIDERS[provider]["cooldown_until"] = now + 30  # Cooldown for 30s
        return route_request(messages)  # Retry with a different provider

集約プラットフォームはこれをインフラストラクチャレベルで処理します——単一のエンドポイントがすべてのプロバイダーにルーティングし、自動クールダウン、フェイルオーバー、レート制限モニタリングを備えます。希望のスループットを設定すれば、プラットフォームがプロバイダーごとのレート制限を管理します。完全なルーティング実装とレイテンシ対応フェイルオーバーについては、本番マルチモデル構築ガイドカスタムルーティングのドキュメントをご覧ください。

FAQ

最も一般的なレート制限のミスは何ですか?

固定間隔のリトライです。すべてのクライアントでの1秒間隔のリトライが群衆雪崩を引き起こし、さらに多くの429を招くことを保証します。常にランダムジッター付きの指数バックオフを使いましょう。ジッターだけでも——待機時間にrandom.uniform(0, 1)を加えること——ほとんどの連鎖的な障害を防げます。

自分のレート制限をどうやって知ればよいですか?

プロバイダーのダッシュボードで、自分の階層の規定制限を確認してください。次に、すべてのAPIレスポンスのx-ratelimit-remaining-*ヘッダーを読みましょう——実際の残り予算をリアルタイムで教えてくれます。それをモニタリングしてください。多くのチームは、実際に制限に当たるまで自分の制限を知らない——それが現実です。

複数のプロバイダーを使えば本当にレート制限は解決しますか?

はい——事実上、解決します。プロバイダーあたり500 RPMの制限でも、ラウンドロビンルーターで4つのプロバイダーに分散すれば2,000 RPMになります。マルチプロバイダールーティングを備えた集約プラットフォームはこれを透過的にします:1つのエンドポイントで、プロバイダーレベルのレート制限管理が自動化されます。マルチプロバイダールーティングをコスト最適化戦略と組み合わせれば、請求額を倍増させずにスループットを向上できます——重要でないリクエストには安価なモデルをルーティングし、必要なタスクには高価なモデルを確保します。

今日すぐに実装できる最も簡単な修正は何ですか?

固定間隔のリトライを、指数バックオフ+ジッターに置き換えることです。5行のコードで済みます。単一の429を連鎖的な停止へと変える群衆雪崩を防げます。この記事の層2のコードは、コピーペーストですぐ使えます。

集約プラットフォームがレート制限を処理してくれますか?

はい。マルチプロバイダールーティング、スロットリングされたプロバイダーの自動クールダウン、統合されたレート制限ダッシュボードは標準機能です。希望のスループットを設定します。プラットフォームがプロバイダーごとの割り当て管理、ヘッダーモニタリング、自動フェイルオーバーを処理します。エンドポイントは1つ。429は発生しません。

この3層——スロットリング、バックオフ、予測——は、レート制限を信頼性への脅威から、解決済みのエンジニアリング問題へと変えます。しかし、マルチプロバイダールーティングが当たり前になり、プロバイダーがスループット保証で競い合うにつれて、問いは変化します:「レート制限超過」は、「ディスクフル」や「メモリ不足」と同じように、現代のインフラストラクチャが単に時代遅れにするエラーの仲間入りをするのでしょうか。今のところ、この記事のコードがあなたの運用を支えます。しかし、より長い道のりは、もっと興味深い場所を指し示しています。

層1と層2は、上記のコードを使って半日で実装できます。層3——予測的サスペンション——は、より多くの投資が必要です。集約プラットフォームは、3つすべてをリクエストパスに組み込みます:マルチプロバイダールーティングがプロバイダーレベルのスロットリングを吸収し、ヘッダーモニタリングが共有のレート制限ダッシュボードを支え、自動クールダウンが1つのプロバイダーに劣化があっても健全なプロバイダーをローテーションに維持します。どのアーキテクチャも429を完全に排除することはできませんが、トラフィックをプロバイダー間に分散し、壁にぶつかる前にヘッダーを読めば、週次のインシデントは稀なエッジケースへと変わります。