- AI ⚙️
LLM APIの429対策|TPM・RPMをトークンバケットで守る設計と実測
結論
LLM API の 429 は、リクエスト数(RPM)だけでなく「1分あたりのトークン数(TPM)」と同時実行数でも起きるため、送る側でトークン数を見積もって流量を絞るのが根本の対策です。127.0.0.1 のモックで同じ60件を流すと、素朴な並列は439回送って429を379回受け、送る前にトークンバケットで絞る方式は60回で429が0回でした。
この記事でわかること
- OpenAI・Anthropic・Google・Azure OpenAI の公式ドキュメントで確かめた制限の種類・数え方・ヘッダー名
- 素朴な並列・指数バックオフ・トークンバケット・優先度つきキューの、送信回数・429件数・待ち時間の実測
- max_tokens・プロンプトキャッシュ・まとめ送りが、トークン単位の制限にどう効くかの実測
この記事で実際に動かしたもの:RPM・入力 TPM・出力 TPM・同時実行数の4つを連続補充のトークンバケットで制限し、429 と Retry-After と各社の形のレート制限ヘッダーを返すモックサーバー(aiohttp、127.0.0.1)と、4方式のクライアント(asyncio+httpx)を自作し、RJC の技術記事の本文を入力にして11通りの条件で測りました。
LLM の API で 429 Too Many Requests が返る理由は、ふつうの Web API と少し違います。リクエストの「回数」に加えて、リクエストに含まれるトークンの量が上限に数えられるからです。1件ずつは小さなリクエストでも、長い文脈を並列で送ると、回数の上限よりずっと手前でトークンの上限に当たります。この記事は、トークン単位の制限をどう設計で避けるかに絞ります。サーバー側のレート制限のアルゴリズム(固定ウィンドウ・GCRA など)の比較は関連記事「レート制限の実装アルゴリズム|6方式を同じ条件で動かして比較」で、SDK の自動リトライ回数やステータス別のエラー一覧は関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。
flowchart TD
J[ジョブ] --> Q[優先度つきキュー]
Q --> E[トークン数を見積もる<br>RPM・TPM・同時実行]
E --> B{バケットに<br>空きはあるか}
B -->|無い・待つ| E
B -->|ある| A[LLM API]
A -->|200| S[usage で精算]
A -->|429| R[Retry-After以上<br>+ジッター]
R --> Q
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| LLM API の 429 とは | RPM・TPM(入力と出力のトークン)・同時実行数などのどれかを超えたときの応答。残高や支出上限でも 429 になることがある | LLM API の 429 とは |
| TPM と RPM の違い | RPM は1分あたりのリクエスト数、TPM は1分あたりのトークン数。先に当たった方で止まる | 公式ドキュメントで確かめた数え方 |
| OpenAI の 429 の対処 | Retry-After を最小値として待ち、ジッターを足す。根本は送る前にトークン数で流量を絞ること | 4方式を同じ条件で実測 |
| レート制限ヘッダーの名前 | OpenAI と Azure OpenAI は x-ratelimit-*、Anthropic は anthropic-ratelimit-*。待ち時間は Retry-After(Azure OpenAI は retry-after-ms も) | レート制限ヘッダーの読み方 |
| リトライだけで足りるか | 足りない。失敗も上限に数える仕様だと、リトライが上限を食いつぶして60件中45件が打ち切りになった | 失敗も数えられると何が起きるか |
| max_tokens は大きくしてよいか | 受付時に max_tokens を差し引く数え方では処理量が落ちる。手元では 4096 と 600 で完了まで103秒と31秒 | max_tokens が TPM を食う理由 |
| 対話の応答だけ速くしたい | 優先度つきキューで先に出す。対話ジョブの待ちの中央値が10.6秒から1.1秒になった | 優先度つきキュー |
| 送る量そのものを減らすには | 小さな仕事のまとめ送り・プロンプトキャッシュ・バッチ API。キャッシュ読み出しを数えない条件で完了が26.5秒から9.9秒 | 送る量を減らす |
確認環境:Ubuntu 24.04.5 LTS(2コア、ほかの作業と同時に動いている共有のマシン)で、Python 3.13.18・aiohttp 3.14.5(モックサーバー)・httpx 0.28.1(クライアント)を作業用の venv に入れて動かしました。サーバーはすべて 127.0.0.1 で、実際の LLM の API には接続していません。モックのトークン数は「ASCII 以外の1文字を1トークン、ASCII は4文字で1トークン」という簡略な規則で数えており、各社のトークナイザーとは数が違います。入力の文章は RJC の技術記事の本文(HTML タグを外したもの)から切り出しました。各社の制限の種類・数え方・ヘッダー名・数値の例は、OpenAI・Anthropic・Google(Gemini API)・Microsoft(Azure OpenAI)の公式ドキュメントで確かめた範囲だけを書いています。数値の例は各社が随時変えるもので、2026年11月4日時点の値です。図は mermaid で描画を確かめています(確認日 2026年11月4日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LLM APIの429対策|TPM・RPMをトークンバケットで守る設計と実測」
LLM API の 429 とは(RPM・TPM・同時実行数の3種類)
この節では、LLM の API で 429 が返る原因の種類と、ふつうの API のレート制限との違いが分かります。
HTTP の 429 Too Many Requests は RFC 6585 で決められたステータスで、「決められた時間の中でリクエストを送りすぎた」ことを表します。RFC 6585 は、どう数えるか(ユーザー単位か、サーバー全体か)は決めていません。LLM の API では、この「数え方」に3つの軸があります。
1RPM(リクエスト数)
1分あたりのリクエストの回数。短い問い合わせを大量に送るときに当たる。1日あたり(RPD)の上限がある API もある。
2TPM(トークン数)
1分あたりのトークンの量。入力と出力を合わせて数える API と、入力(ITPM)と出力(OTPM)を別に数える API がある。長い文脈を並列で送ると当たる。
3同時実行数
同時に処理中のリクエストの数。生成に数秒〜数十秒かかる LLM では、回数やトークンに余裕があっても、ここで止まることがある。社内のゲートウェイで設けることも多い。
3つ目の同時実行数は、この記事で確かめた各社のレート制限のページでは主な軸として書かれていませんが、間に置いたゲートウェイやプロキシが同時接続数で制限することがあるため、モックでは3つ目の制限として再現しました。
もう1つ、レート制限ではない 429 があります。Anthropic の公式ドキュメントは、月の支出上限に達したときの 429 には retry-after ヘッダーが付かず、SDK の自動リトライを含めて、翌月の 00:00 UTC にアクセスが戻るまで失敗し続けると書いています(本文の error_code は enforced_spend_limit_reached)。OpenAI の公式ドキュメントも、支出の上限(hard spend limit)に達すると 429 が返ると書いています。この種類の 429 は待っても直らないので、リトライの前に見分けます。見分け方の詳細は関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。
待てば直る 429
RPM・TPM・同時実行数の一時的な超過
急な利用増による制限(Anthropic の acceleration limits、OpenAI の slow_down)
Retry-After が付くことが多い
待っても直らない 429
月の支出上限・残高切れ
Anthropic では retry-after が付かない
リトライを止め、人に知らせる
各社の公式ドキュメントで確かめたレート制限の種類と数え方
この節では、OpenAI・Anthropic・Google(Gemini API)・Azure OpenAI の公式ドキュメントに書かれている、制限の軸・トークンの数え方・数値の例が分かります。
2026年11月4日に各社の公式ドキュメントを読み、設計に効く記述だけを抜き出しました。書き方や数え方は各社で違いますが、どれが良いという話ではなく、使う API の数え方に合わせてクライアントを作るための材料です。
| API | 制限の軸(公式の記述) | トークンの数え方で設計に効く点 | 適用の単位 |
|---|---|---|---|
| OpenAI | RPM・RPD・TPM・TPD・IPM。どれか先に当たったもので止まる | max_tokens と、リクエストの文字数から見積もったトークン数の大きい方で計算する。失敗したリクエストも1分あたりの上限に数える | 組織とプロジェクト。モデルごと(共有するモデル群もある) |
| Anthropic | RPM・ITPM(入力)・OTPM(出力)をモデルの区分ごと | ITPM は受付時に見積もり、処理中に実際の値へ直す。多くのモデルでキャッシュ読み出しは ITPM に数えない。OTPM は実際に生成した分だけで、max_tokens は数えない | 組織(Workspace の上限も設定できる) |
| Google(Gemini API) | 主に RPM・TPM・RPD の3軸。画像生成は IPM、TPD があるモデルもある | RPD は太平洋時間の0時にリセット。ティアごとに支出ベースの上限もある | プロジェクト(API キーごとではない) |
| Azure OpenAI | デプロイごとに割り当てた TPM と、それに比例する RPM | 受付時の見積もりにプロンプトと max_tokens(と best_of)を含める。RPM は1秒か10秒の短い区間で判定する | デプロイ |
アルゴリズムについても公式の記述があります。Anthropic は「トークンバケットのアルゴリズムで制限しており、容量は決まった間隔でリセットされるのではなく、上限まで連続的に補充される」と書き、「60 RPM が1秒あたり1リクエストとして適用されることがある」として短い時間のバーストでも 429 になり得ると注意しています。Azure OpenAI も、600 RPM のデプロイを1秒区間で監視している場合、1秒に10件を超えると制限がかかる、という例を出しています。「1分に600件まで」を「最初の1秒で600件送ってよい」と読まないのが、どの API でも共通の前提です。
数値の例(2026年11月4日時点・各社の公式ページの表から)
| API・区分 | 例として載っている値 | 出典の表 |
|---|---|---|
| OpenAI Build ティア(Astra・Sol・Terra) | 5,000 RPM・1,000,000 TPM | Standard の RPM/TPM の表 |
| Anthropic Start ティア(Claude Opus 5.5) | 4,000 RPM・ITPM 2,000,000・OTPM 400,000 | Messages API の表 |
| Anthropic Message Batches(Start ティア) | 処理待ちのバッチリクエスト 200,000・1バッチ100,000件 | Message Batches API の表 |
| Gemini API Tier 1(Gemini 2.5 Flash) | バッチの待ち行列に入れられるトークン 3,000,000 | Batch API の enqueued tokens の表 |
| Azure OpenAI(以前の chat モデル) | 1,000 TPM ごとに 6 RPM(モデルで比率が違う) | クォータの比率の表 |
自分の組織に今かかっている値は、各社のコンソールで確かめます(Gemini は AI Studio のレート制限のページと公式に案内されています)。Gemini の公式ページは「示した上限は保証されたものではなく、実際の容量は変わることがある」とも書いています。
レート制限ヘッダーの読み方(x-ratelimit・anthropic-ratelimit・Retry-After)
この節では、各社の応答に付くレート制限ヘッダーの名前と意味、読むときの落とし穴が分かります。
OpenAI の公式ドキュメントにあるヘッダーは次の表のとおりです(見本の値も公式の表のもの)。Azure OpenAI の公式ドキュメントにも同じ名前の6つ(project を除く)と、ミリ秒で待ち時間を示す retry-after-ms が載っています。
| x-ratelimit- に続く部分 | 公式の見本の値 | 意味 |
|---|---|---|
| limit-requests | 60 | 上限に達するまでに許されるリクエスト数 |
| limit-tokens | 150000 | 上限に達するまでに許されるトークン数 |
| remaining-requests | 59 | 残りのリクエスト数 |
| remaining-tokens | 149984 | 残りのトークン数 |
| reset-requests | 1s | リクエストの制限が初期の状態に戻るまでの時間 |
| reset-tokens | 6m0s | トークンの制限が初期の状態に戻るまでの時間 |
| limit-project-tokens など3つ | 60000 など | プロジェクト単位のトークンの上限がかかるときだけ付く |
Anthropic の公式ドキュメントにあるヘッダーは、次の名前に limit・remaining・reset が付く形です。
| anthropic-ratelimit- に続く部分 | 対象 | 読むときの注意(公式の記述) |
|---|---|---|
| requests-limit など | リクエスト数 | reset は RFC 3339 の日時 |
| input-tokens-remaining など | 入力トークン | remaining は千の位に丸めた値 |
| output-tokens-remaining など | 出力トークン | remaining は千の位に丸めた値 |
| tokens-remaining など | いちばん厳しい制限 | 今かかっている中で最も厳しい制限の値を示す |
待ち時間は Retry-After(Anthropic の表記は retry-after。HTTP のヘッダー名は大文字小文字を区別しない)で示されます。OpenAI は「この値を最小値として扱い、少なくともその時間は待ち、小さなランダムな遅れを足す」「無いか不正な値なら、ジッター付きの指数バックオフに戻す」と書いています。Anthropic は「この秒数より早いリトライは失敗する」「すべての 429 に付くわけではない」と書いています。HTTP の仕様(RFC 9110)では、Retry-After は秒数(delay-seconds)か HTTP の日時のどちらかの形をとります。
モックサーバーに、各社のヘッダー名で応答を返させた実物です。Anthropic の形で、入力の容量600トークンのうち400を使った直後の応答と、続けて送って 429 になった応答です。
HTTP/1.1 200 OK
anthropic-ratelimit-requests-limit: 60
anthropic-ratelimit-requests-remaining: 5
anthropic-ratelimit-requests-reset: 2026-11-04T09:27:47Z
anthropic-ratelimit-input-tokens-limit: 6000
anthropic-ratelimit-input-tokens-remaining: 0
anthropic-ratelimit-input-tokens-reset: 2026-11-04T09:27:50Z
anthropic-ratelimit-output-tokens-limit: 1200
anthropic-ratelimit-output-tokens-remaining: 0
anthropic-ratelimit-output-tokens-reset: 2026-11-04T09:27:48Z
{"usage": {"input_tokens": 400, "output_tokens": 50, "cache_read_input_tokens": 0}}
HTTP/1.1 429 Too Many Requests
anthropic-ratelimit-input-tokens-remaining: 0
retry-after: 2
{"type": "error", "error": {"type": "rate_limit_error", "message": "This request would exceed the rate limit for input_tokens."}}
実際の残りは200トークンあるのに、remaining は千の位に丸めて「0」になっています。上限が小さい組織やバケットの容量が小さい区間では、remaining の値をそのまま「送ってよい量」に使うと、送れるのに止まる・止まるべきなのに送るのどちらにもずれます。ヘッダーは「今どの制限が厳しいか」を知る手がかりにし、送る量は手元の見積もりで決める方が安定します。なお、エラー本文の文言はモックが返したもので、各社の実際の文言ではありません。
OpenAI の形(受付時に入力+max_tokens を差し引く数え方、TPM 30,000・容量3,000)で、入力105トークン・max_tokens 1024 を3回送った結果です。
200 x-ratelimit-remaining-tokens: 1883 x-ratelimit-reset-requests: 974ms x-ratelimit-reset-tokens: 3s
200 x-ratelimit-remaining-tokens: 770 x-ratelimit-reset-requests: 2s x-ratelimit-reset-tokens: 5s
429 x-ratelimit-remaining-tokens: 771 retry-after: 1
usage: input_tokens 105, output_tokens 10(成功した2回とも)
実際に使ったのは1回115トークンなのに、残りは1回で約1,100ずつ減っています。差し引かれているのは「入力105+max_tokens 1024」です。この差が、後の節で測る max_tokens の影響の正体です。reset の値が「974ms」「2s」「6m0s」のような時間の文字列である点も、パースのときのつまずきになります(よくあるエラーの節)。
実験の作り:127.0.0.1 のモックサーバーで3種類の制限を再現する
この節では、測定に使ったモックサーバーとクライアントの条件、比べた4つの方式が分かります。
実際の API を叩いて 429 を起こすのは、料金がかかるうえ、上限の値も自由に変えられません。そこで、各社の公式ドキュメントの記述に沿って制限をかけるモックサーバーを書きました。制限は4つのトークンバケット(リクエスト数・入力トークン・出力トークン・同時実行数)で、どれか1つでも足りなければ 429 と retry-after を返します。
受付時に入力トークンを数えて差し引く
Anthropic の「ITPM は受付時に見積もる」に合わせた。比べる実験用に、Azure OpenAI の説明に合わせて「入力+max_tokens」を受付時に差し引く数え方(reserve)も用意した。
出力トークンは生成した分だけ差し引く
1リクエストあたり毎秒200トークンの速さで少しずつ「生成」し、そのたびに出力のバケットから引く。残りがマイナスになると次の受付を止める。
バケットは連続的に補充する
1分あたりの上限を60で割った速さで毎秒補充し、容量は6秒分にした。600 RPM なら容量60件・毎秒10件の補充になる。
足りなければ 429 と retry-after を返す
足りない制限ごとに「空くまでの秒数」を計算し、いちばん長いものを切り上げて retry-after にする。どの制限で断ったかを数えておき、測定の後に取り出す。
基本の条件は、RPM 600・入力 TPM 90,000・出力 TPM 30,000・同時実行8です。ジョブは60件で、入力は RJC の技術記事の本文から200〜1,800文字を切り出したもの(モックの数え方で合計40,294トークン)、出力は対数正規分布で決めた長さ(合計12,511トークン、平均209・最大421)、max_tokens は1024です。入力の量から計算した理論上の最短時間は、容量の9,000トークンを最初に使い切ったあと毎秒1,500トークンで補充されるので、(40,294−9,000)÷1,500=約21秒です。
比べた方式は次の4つです。
素朴な並列
60件を一度に送り、429 なら1秒待って送り直す(最大30回)。
よく見かける最初の形
指数バックオフ
一度に送り、429 なら Retry-After+0〜1秒、無ければジッター付きの指数バックオフで待つ(最大10回)。
待ち方だけを工夫する形
トークンバケット
送る前にトークン数を見積もり、上限の95%の速さのバケットと同時実行8のセマフォで絞る。429 のときは2と同じく待つ。
送る量を先に決める形
優先度つきキュー
3の待ち行列を優先度つきにし、対話のジョブを先に出す。
誰を待たせるかを決める形
4つの対策を同じ条件で実測(素朴な並列・指数バックオフ・トークンバケット)
この節では、①〜③の方式で同じ60件を流したときの、送信回数・429件数・打ち切り件数・完了までの時間が分かります。
結果は次のとおりです。「打ち切り」は、決めた回数のリトライでも成功しなかったジョブの数です。
{"strategy": "naive", "makespan": 23.36, "sent": 439, "r429": 379, "fail": 0, "p50": 8.12, "p95": 20.81,
"by_limit": {"concurrency": 116, "input_tokens": 176, "output_tokens": 155}}
{"strategy": "backoff", "makespan": 17.92, "sent": 329, "r429": 269, "fail": 7, "p50": 7.16, "p95": 16.19,
"by_limit": {"concurrency": 94, "input_tokens": 98, "output_tokens": 125}}
{"strategy": "bucket", "makespan": 24.14, "sent": 60, "r429": 0, "fail": 0, "p50": 10.67, "p95": 22.83,
"by_limit": {}}
| 方式 | 送信回数(429の回数) | 打ち切り | 全件の完了 | 1件の待ち(中央値/95%) |
|---|---|---|---|---|
| ① 素朴な並列 | 439回(379回) | 0件 | 23.4秒 | 8.1秒/20.8秒 |
| ② 指数バックオフ | 329回(269回) | 7件 | 17.9秒(打ち切った7件を除く) | 7.2秒/16.2秒 |
| ③ トークンバケット | 60回(0回) | 0件 | 24.1秒 | 10.7秒/22.8秒 |
読み取れることは4つです。
- ①は全件終わるが、送信の86%が 429 です。断られた理由は入力トークン176回・出力トークン155回・同時実行116回で、RPM ではありません。600 RPM に対して60件しか送っていないのに 429 が出るのは、トークンと同時実行で止まっているためです。
- ②の「速い」17.9秒は、打ち切った7件を除いた時間です。同時に断られたジョブが似た時間に戻ってくるため、Retry-After が1秒の 429 を何度も受け、7件が10回の上限に届きました。指数バックオフは「いつか通る」ことを保証しません。
- ③は429が0回で、時間は理論上の最短の約1.15倍です。上限の95%で絞っている分(21秒÷0.95≒22秒)と、手元のバケットの容量をサーバーより小さくした分が差になっています。
- 1件の待ちの中央値は③がいちばん長くなります。③はクライアントの中の待ち行列で待ち、①②は「送っては断られる」を繰り返しながら待つという、待つ場所の違いです。
この条件では、サーバーが断ったリクエストを上限に数えていません。数える仕様だと結果が大きく変わります。次の節で測ります。
失敗したリクエストも数えられると何が起きるか(リトライの嵐)
この節では、429 で断られたリクエストも1分あたりの上限に数える仕様のとき、リトライ中心の方式がどうなるかが分かります。
OpenAI の公式ドキュメントは「失敗したリクエストも1分あたりの上限に数えられるので、送り直し続けてもうまくいかない」と書いています。モックに「断ったリクエストもリクエスト数のバケットから引く」設定(--count-rejected)を足し、同じ60件を流しました。
{"strategy": "naive", "makespan": 32.26, "sent": 1417, "r429": 1357, "fail": 45, "tok_per_s": 425,
"by_limit": {"concurrency": 52, "input_tokens": 17, "requests": 1342}}
{"strategy": "backoff", "makespan": 43.26, "sent": 508, "r429": 448, "fail": 44, "tok_per_s": 334,
"by_limit": {"concurrency": 74, "input_tokens": 12, "requests": 433}}
{"strategy": "bucket", "makespan": 24.15, "sent": 60, "r429": 0, "fail": 0, "tok_per_s": 2187,
"by_limit": {}}
| 方式 | 送信回数 | 打ち切り | 処理できたトークン(毎秒) |
|---|---|---|---|
| ① 素朴な並列 | 1,417回 | 60件中45件 | 425 |
| ② 指数バックオフ | 508回 | 60件中44件 | 334 |
| ③ トークンバケット | 60回 | 0件 | 2,187 |
①と②では、429 の理由が「requests」にほぼ入れ替わりました。断られたリクエストがリクエスト数のバケットを食い、そのせいで次も断られる、という循環です。①は1秒ごとに全員が送り直すため1,417回送り、45件が30回の上限に届きました。②は待ち時間をばらしていますが、Retry-After が1〜4秒と短く、60件が同時に待っているため、戻ってきた時点でまたバケットが空になっています。処理できたトークンは③の5分の1以下でした。
③は送る前に絞っているので、この設定の有無に関係なく同じ結果(24.15秒・429が0回)でした。リトライは「たまに起きる 429」を吸収する仕組みで、流量そのものを決める仕組みではありません。流量は送る側で決め、リトライは安全装置として残す、という役割分けが要点です。指数バックオフとジッターの間隔の決め方そのものは関連記事「指数バックオフとは?リトライ間隔の決め方とジッターの実装」で扱っています。
429 が増えたときの直し方
手元では打ち切りが45件から0件になった
トークンバケットで TPM を守るクライアントの作り方(見積もりと精算)
この節では、③の方式のクライアントの中身と、トークン数の見積もりがずれたときの直し方が分かります。
クライアントの考え方は「送る前に予約し、応答の usage で精算する」です。リクエスト数・入力トークン・出力トークンの3つのバケットを手元に持ち、全部に空きがあるときだけ送ります。測定に使ったコードの要点を抜き出しました(エラー処理と統計は省略)。
class TokenBucket:
def __init__(self, per_min, burst_s, frac=0.95):
self.rate = per_min * frac / 60.0 # 上限の95%の速さで補充
self.cap = self.rate * burst_s # 手元の容量は数秒分だけ
self.level = self.cap; self.t = time.monotonic()
def _refill(self):
now = time.monotonic()
self.level = min(self.cap, self.level + (now - self.t) * self.rate); self.t = now
def need(self, n): # 空くまでの秒数(容量を超える要求は容量で打ち切る)
self._refill(); n = min(n, self.cap)
return 0.0 if self.level >= n else (n - self.level) / self.rate
def take(self, n): self._refill(); self.level -= n
def adjust(self, delta): self._refill(); self.level = min(self.cap, self.level - delta)
# 送る前:見積もりで予約する
cin = math.ceil(calib * 1.05 * est_tokens(text)) # 入力の見積もり(補正係数つき)
cout = 250 # 出力の予定(過去の usage の平均から決めた値)
while (w := max(rpm_b.need(1), in_b.need(cin), out_b.need(cout))) > 0:
await asyncio.sleep(min(w, 0.25))
await sem.acquire() # 同時実行数
rpm_b.take(1); in_b.take(cin); out_b.take(cout)
# 応答の後:usage で精算し、見積もりの補正係数を更新する
u = resp.json()["usage"]
in_b.adjust(u["input_tokens"] - cin) # 見積もりより少なければ戻す
out_b.adjust(u["output_tokens"] - cout)
calib = 0.8 * calib + 0.2 * (u["input_tokens"] / est_tokens(text))
設計で効いた点が3つありました。
- 手元の容量はサーバーより小さくします。サーバーの容量(どれだけ溜めてから一度に送れるか)は公開されていないことが多く、Anthropic の「60 RPM が1秒に1件として適用されることがある」のように短い区間で判定されることもあります。測定では2秒分と5秒分を試し、2秒分は28.1秒、5秒分は24.7秒(どちらも 429 は0回)でした。
- 見積もりは usage で補正します。最初のクライアントは「文字数×1.05」で入力を見積もっていましたが、60件の合計は59,627で、モックが数えた40,294より48%多くなっていました。本文に英字やコードが混ざると、文字数と実際のトークン数の比が変わるためです。過大な見積もりは、429 こそ出ないものの、送れる量を自分で削ります。usage で補正係数を更新するようにすると、同じ条件で24.7秒が24.2秒になりました(応答のたびに精算で見積もりとの差を戻しているため、補正の効果は小さめでした。精算と補正のどちらも無いと、48%の過大見積もりがそのまま予約に残ります)。
- 容量を超える要求を待たせ続けないようにします。見積もりがバケットの容量より大きいと、素朴な実装は永久に待ちます(よくあるエラーの節に原文)。容量で打ち切って送るか、サーバーが 413 などで断るのに任せます。
正確な入力トークン数が必要なら、公式の数え方を使えます。Anthropic には POST /v1/messages/count_tokens があり、無料でメッセージ作成とは別の RPM の上限がかかり、結果は見積もりで実際の値と少しずれることがある、と公式に書かれています。トークンの数え方の考え方は関連記事「トークンとは|生成AIの数え方・日本語の文字数目安と節約方法」で扱っています。並列の部分は asyncio のセマフォとタスクで書いており、TaskGroup とタイムアウトの扱いは関連記事「Python asyncioの使い方|TaskGroupとtimeoutを実測で解説」で扱っています。
複数のプロセスで同じ上限を分けるとき
上限は組織・プロジェクト・デプロイの単位でかかるので、同じ API キーを使うワーカーが複数あると、手元のバケットはプロセスごとにしか効きません。方法は2つです。1つは上限をワーカー数で割って各自に持たせる方法で、簡単ですが、空いているワーカーの枠を使えません。もう1つは Redis などで共有のバケットを持つ方法で、原子的に減らす実装が要ります(Redis と Lua での作り方は関連記事「レート制限の実装アルゴリズム|6方式を同じ条件で動かして比較」で扱っています)。どちらでも、応答の remaining ヘッダーが手元の残りより大きく下回ったら、ほかの誰かが同じ枠を使っている合図として、手元のバケットを減らす補正が効きます。
max_tokens の見積もりが TPM を食う理由(実測)
この節では、受付時に max_tokens を差し引く数え方で、max_tokens の大きさが処理量をどれだけ変えるかが分かります。
OpenAI の公式ドキュメントは「レート制限は、max_tokens と、リクエストの文字数から見積もったトークン数の大きい方で計算する」と書き、max_tokens を予想される応答の大きさにできるだけ近づけることを勧めています。Azure OpenAI の公式ドキュメントも「実際の応答がずっと短くても、見積もりに max_tokens を含める」と書いています。一方、Anthropic の公式ドキュメントは「max_tokens は OTPM の計算に入らないので、大きくしてもレート制限の上での不利はない」と書いています。同じ max_tokens でも、API によって制限への効き方が違うということです。
モックで両方の数え方を作り、同じ40件(入力の合計24,359トークン、実際の出力の合計8,628・最大526)を max_tokens だけ変えて流しました。どの max_tokens でも出力が途中で切れたジョブはありません。
reserve(受付時に 入力+max_tokens、TPM 120,000)
max_tokens 4096: makespan 103.42s charged 188,199 tok/s 319
max_tokens 1024: makespan 39.76s charged 65,319 tok/s 830
max_tokens 600: makespan 30.87s charged 48,359 tok/s 1069
actual(入力は受付時、出力は生成した分、ITPM 120,000・OTPM 24,000)
max_tokens 4096: makespan 23.50s charged 24,359 tok/s 1403
max_tokens 600: makespan 23.48s charged 24,359 tok/s 1405
| 数え方 | max_tokens | 差し引かれたトークン | 全件の完了 |
|---|---|---|---|
| 受付時に入力+max_tokens | 4096 | 188,199(実際に使った入出力32,987の5.7倍) | 103.4秒 |
| 受付時に入力+max_tokens | 1024 | 65,319 | 39.8秒 |
| 受付時に入力+max_tokens | 600 | 48,359 | 30.9秒 |
| 出力は生成した分だけ | 4096 | 入力24,359+実際の出力 | 23.5秒 |
| 出力は生成した分だけ | 600 | 入力24,359+実際の出力 | 23.5秒 |
受付時に max_tokens を差し引く数え方では、使わない予約がバケットを埋めるため、4096 では 600 の3.4倍の時間がかかりました。出力を実際に生成した分で数える方では、max_tokens を変えても時間は同じです。
max_tokens を小さくしすぎると、応答が途中で切れます(Anthropic なら stop_reason が max_tokens、OpenAI の Chat Completions なら finish_reason が length)。決め方は「用途ごとに実際の出力トークン数の分布を取り、上の方の分位点に余裕を足す」が現実的です。この40件では、99%点が440、最大が526でした。用途ごとに max_tokens を分け、切れたときは続きを頼むか max_tokens を上げて1回だけ送り直す、という形にすると、予約の無駄と途中で切れる危険の両方を抑えられます。
優先度つきキューで対話の待ち時間を守る
この節では、夜間のバッチと画面からの対話が同じ上限を分け合うとき、優先度つきキューで対話の待ち時間がどれだけ変わるかが分かります。
トークンバケットで流量を絞ると、待ち行列ができます。その行列が先着順(FIFO)だと、後から来た対話のリクエストは、先に積まれたバッチの後ろに並びます。60件のうち48件をバッチとして最初に積み、12件を対話として1秒目から2秒おきに到着させ、③(先着順)と④(対話を先に出す優先度つき)で比べました。
{"strategy": "bucket", "makespan": 24.21, "r429": 0, "hi_p50": 10.58, "hi_p95": 16.51, "lo_max": 18.33}
{"strategy": "priority", "makespan": 23.99, "r429": 0, "hi_p50": 1.07, "hi_p95": 1.39, "lo_max": 23.6}
| 方式 | 対話の待ち(中央値/95%) | バッチの最後の完了 | 全件の完了 |
|---|---|---|---|
| ③ 先着順 | 10.58秒/16.51秒 | 18.33秒 | 24.21秒 |
| ④ 優先度つき | 1.07秒/1.39秒 | 23.60秒 | 23.99秒 |
全体の処理量(全件の完了)はほとんど変わらず、対話の待ちの中央値が10.6秒から1.1秒になりました。代わりにバッチの最後は18.3秒から23.6秒へ遅れています。優先度つきキューは処理量を増やすのではなく、同じ処理量の中で誰を待たせるかを決める仕組みです。
作るときの注意は2つです。1つは、優先度の低いジョブが永久に後回しにならないよう、待ち時間に応じて優先度を上げる(エージング)か、低い優先度にも一定の割合を残すことです。もう1つは、バケットの空きを待っている間に高い優先度のジョブが届いたら、待っていたジョブより先に出すことです。測定のコードでは、空きを待つ0.25秒ごとに行列の先頭を見直しています。利用者ごとの公平さが要るサービスでは、優先度の代わりに利用者ごとの小さなバケットを持たせる形もあります。
送る量そのものを減らす:まとめ送り・プロンプトキャッシュ・バッチ API
この節では、流量を絞るのではなく、上限に数えられる量を減らす3つの方法と、その効き方の実測が分かります。
小さな仕事をまとめて送る(RPM に当たるとき)
OpenAI の公式ドキュメントは、RPM に当たっていて TPM に余裕があるときは、複数の仕事を1つのリクエストにまとめる方法を挙げています。問い合わせの分類のような小さな仕事240件(1件40文字程度)を、1件ずつと10件ずつで送りました(RPM 600)。
pack1 : jobs 240, sent 240, makespan 23.32s, charged 8,948
pack10: jobs 24, sent 24, makespan 0.93s, charged 8,869
上限に数えられたトークンはほとんど同じ(8,948と8,869)で、リクエスト数が10分の1になり、完了は23.3秒から0.9秒になりました。代わりに、1つの応答に10件分の答えが入るので、応答を分けて解釈する処理と、1件分だけ壊れていたときの扱い(その分だけ送り直すか、全体を送り直すか)が要ります。応答の質がまとめ方で変わるかどうかは、この実験では測っていません。
プロンプトキャッシュ(共通の長い前置きがあるとき)
Anthropic の公式ドキュメントは、多くのモデルで、キャッシュから読み出した入力トークン(cache_read_input_tokens)は ITPM に数えず、input_tokens と cache_creation_input_tokens だけを数えると書いています(Claude Haiku 3.5 は例外)。共通の前置き3,000文字+各ジョブ固有の150〜300文字のジョブ30件を、キャッシュ読み出しを数える条件と数えない条件で流しました。
cache_free=False: makespan 26.51s, charged 72,531
cache_free=True : makespan 9.85s, charged 4,281
上限に数えられた入力が72,531から4,281になり、完了は26.5秒から9.9秒になりました。キャッシュの書き込み(初回)の扱いや、キャッシュが効く条件・料金は API ごとに違うため、使う API の公式ドキュメントで確かめます。ここでのモックは「読み出しを数えない」という1点だけを再現したもので、各社のキャッシュの仕組みそのものではありません。
バッチ API(すぐに答えが要らないとき)
すぐに答えが要らない仕事は、同期の API ではなくバッチ API に回せます。OpenAI はバッチの待ち行列の上限を「モデルごとに待ち行列に入っている入力トークンの合計」で数え、処理中のバッチのトークンもこの上限に数えると書いています。Anthropic の Message Batches API は、処理待ちのバッチリクエスト数(Start ティアで200,000)と1バッチの件数(100,000)に上限があり、全モデルで共有されます。Gemini API のバッチも、モデルごとに待ち行列に入れられるトークン数(Tier 1 の Gemini 2.5 Flash で3,000,000)に上限があります。どれも同期の API とは別の上限なので、夜間の大量処理をバッチに移すと、昼の対話が使える同期の枠が空きます。
リトライとストリーミング・冪等性の扱い
この節では、トークンバケットで絞った後も残すリトライの決まりと、LLM の API でのリトライの危険な場面が分かります。
流量を送る側で決めても、短い区間の判定・ほかのワーカーの利用・急な利用増による制限などで 429 はゼロになりません。リトライは次の決まりで残します。
- 1待っても直らない 429 か見分ける支出上限・残高切れはリトライせず人に知らせる
- 2Retry-After があれば最小値として待つ秒数と HTTP の日時の両方を読める実装にする
- 3ジッターを足す同時に断られたジョブが同時に戻らないように
- 4回数と合計時間に上限を付ける上限に達したら利用者に状況を伝える
- 5SDK の自動リトライと重ねない自前で待つなら SDK 側を0回にする
- 6429 が続くなら流量の設定を下げるリトライではなくバケットの速さを直す
LLM の API に特有の危険は、同じリクエストを送り直すと、同じ料金とトークンをもう一度使うことです。ストリーミングの途中で切れた応答を最初から送り直すと、届いた分のトークンも重ねて使います。ツール呼び出しで外部の処理(メール送信・決済など)が動くエージェントでは、送り直しで処理が二重に走らないよう、ツール側に冪等キーを持たせます。冪等キーの作り方は関連記事「冪等性とは?二重課金を防ぐ冪等キーとリトライ設計」で扱っています。
急な利用増にも注意します。OpenAI の公式ドキュメントは、入力が毎分100万トークンに達したら、15分ごとに50%を超えて増やさないよう勧めています。Anthropic の公式ドキュメントも、急に利用が増えると acceleration limits で 429 になり得るので、少しずつ増やして一定の使い方を保つよう書いています。リリース直後やバッチの開始時に、上限いっぱいの速さで一気に始めない設計にします。
よくあるエラー/つまずき
この節では、429 対策のクライアントを書くときに出たエラーの原文と、原因・直し方が分かります。
>>> int('Wed, 21 Oct 2015 07:28:00 GMT')
ValueError: invalid literal for int() with base 10: 'Wed, 21 Oct 2015 07:28:00 GMT'
>>> float('6m0s')
ValueError: could not convert string to float: '6m0s'
>>> asyncio.wait_for(naive_wait(level=0, cap=3000, rate=500, need=5000), 3)
TimeoutError (bucket never reaches 5000 with cap 3000)
| 症状・原文 | 原因 | 直し方 |
|---|---|---|
| ValueError: invalid literal for int() with base 10: 'Wed, 21 Oct …' | Retry-After を秒数だと決めつけた。仕様では HTTP の日時の形もある | 数字なら秒、そうでなければ email.utils.parsedate_to_datetime で日時として読む |
| ValueError: could not convert string to float: '6m0s' | x-ratelimit-reset-* を数値として読んだ。値は「974ms」「6m0s」のような時間の文字列 | ms・s・m・h の単位ごとに数字を取り出して秒に直す(手元の関数で 6m0s→360.0、974ms→0.974) |
| 見積もりがバケットの容量より大きいジョブが永久に待つ | 容量3,000のバケットに5,000を要求すると、補充しても届かない | 要求を容量で打ち切る。大きすぎる入力は分割するか、サーバーの 413 などに任せる |
| Azure OpenAI で待ち時間が1000倍になる | retry-after-ms(ミリ秒)を秒として読んだ | ヘッダー名で単位を分けて読む |
| remaining が 0 なのに送ると成功する | anthropic-ratelimit-*-remaining は千の位に丸めた値 | 送る量は手元の見積もりで決め、ヘッダーは補正に使う |
| 上限の10分の1の RPM なのに 429 が出る | TPM か同時実行数で止まっている。短い区間で判定されている | 429 の本文とヘッダーでどの制限か確かめ、トークンと同時実行も絞る |
| リトライを増やすほど成功が減る | 失敗も上限に数える仕様で、リトライが枠を食う | 送る前に絞る。リトライは回数と合計時間に上限を付ける |
| リトライしても何時間も 429 が続く | 支出上限・残高切れの 429 | リトライを止め、通知する。Anthropic では retry-after が無いのが手がかり |
LLM API の 429 対策の判断表
この節では、自分のシステムの状況から、どの対策を先に入れるかを決める表が分かります。
| 状況 | 先に入れる対策 | 理由(この記事の実測) |
|---|---|---|
| たまに 429 が出るだけ(並列が少ない) | Retry-After を尊重するジッター付きのリトライ | 流量が上限より十分小さければ、リトライで吸収できる |
| 並列で大量に流すと 429 が続く | 送る前にトークンバケットで絞る | ①の429が379回、③は0回 |
| リトライを増やしたら悪くなった | トークンバケット+リトライの上限 | 失敗も数える条件で①②は45件・44件が打ち切り |
| 使用量は少ないのに TPM の 429 | max_tokens を用途ごとの実際の出力に合わせる | 受付時に数える条件で4096→600で103秒→31秒 |
| 短い仕事が多く RPM に当たる | 小さな仕事をまとめて送る | 240回→24回で23.3秒→0.9秒 |
| 長い共通の前置きを毎回送る | プロンプトキャッシュ | 読み出しを数えない条件で72,531→4,281 |
| 対話とバッチが同じ枠を取り合う | 優先度つきキュー、バッチ API への移行 | 対話の待ちの中央値10.6秒→1.1秒 |
| ワーカーが複数ある | 共有のバケット、または上限をワーカー数で割る | 上限は組織・プロジェクト・デプロイ単位でかかる |
よくある質問
この節では、LLM API のレート制限についてよく出る疑問への答えが分かります。
Q. TPM と RPM はどちらを先に気にすればよいですか。
入力が長い用途(RAG・要約・エージェント)では TPM が先に当たることが多く、短い問い合わせを大量に送る用途では RPM が先に当たります。どちらで止まったかは 429 の本文とヘッダーで分かるので、まず数日分の 429 を制限の種類ごとに数えます。
Q. 429 の Retry-After はどのくらい信じてよいですか。
OpenAI は最小値として扱い小さなランダムを足すよう、Anthropic はそれより早いリトライは失敗すると書いています。待ち時間の下限として使い、ジッターを足すのが公式の記述に沿った使い方です。付かない 429 もあるので、無いときの指数バックオフも用意します。
Q. SDK の自動リトライがあれば、自前の対策は要りませんか。
SDK の自動リトライは、たまに起きる 429 を吸収するものです。並列で大量に流すときの流量は決めないので、この記事の実測のように 429 が続く状況は残ります。SDK の既定の回数や、自前のリトライと重ねたときの回数の掛け算は、関連記事のエラー対処の記事で測っています。
Q. トークン数の見積もりはどのくらい正確である必要がありますか。
429 を避けるために多めに見積もるのは安全側ですが、多すぎると送れる量を自分で削ります。手元では文字数×1.05 の見積もりが実際より48%多く、流量が落ちました。最初は公式のトークン数の数え方(Anthropic の count_tokens など)か保守的な係数で始め、応答の usage で係数を補正していく形が安定します。
Q. max_tokens はいくつにすればよいですか。
使う API が max_tokens を上限に数えるかどうかで変わります。OpenAI と Azure OpenAI は受付時の計算に max_tokens を含めると書いており、Anthropic は OTPM に含めないと書いています。含める API では、用途ごとの実際の出力トークン数の分布を取り、上の方の分位点に余裕を足した値にします。
Q. 同時実行数はいくつにすればよいですか。
目安は「1秒あたりに流したいリクエスト数×1件の平均の処理秒数」です。手元の条件(毎秒2.5件前後・1件1秒前後)では8で余裕がありました。生成が長い用途ほど同時実行数が効くので、平均の処理秒数を実測してから決めます。
Q. 複数のモデルを使い分けると上限はどうなりますか。
上限は基本的にモデル(またはモデルの区分)ごとですが、OpenAI は一部のモデル群で上限を共有すると書いており、Anthropic も Sonnet 4.x のように複数の版をまとめた上限を示しています。上限の表の注記で、共有されるモデルを確かめます。
Q. 429 対策をテストするにはどうすればよいですか。
実際の API では上限を自由に変えられず料金もかかるので、この記事のように上限とヘッダーを再現するモックサーバーを 127.0.0.1 に立てるのが手軽です。Microsoft の Dev Proxy の公式ドキュメントにも、LLM のトークン数で制限して 429 を返すプラグイン(LanguageModelRateLimitingPlugin)が載っています。
まとめ
この節では、記事全体の要点が分かります。
- LLM API の 429 は RPM だけでなく TPM と同時実行数で起きる。手元では 600 RPM に対して60件しか送らなくても、入力トークン・出力トークン・同時実行で379回断られた。
- 流量は送る側でトークン数を見積もって決める。トークンバケットで絞る方式は60回の送信で429が0回、理論上の最短の約1.15倍で終わった。
- リトライは安全装置で、流量を決める仕組みではない。失敗も上限に数える条件では、素朴な並列と指数バックオフの60件中45件・44件が打ち切りになった。
- 上限に数えられる量を減らす。受付時に max_tokens を数える条件では 4096→600 で103秒→31秒、キャッシュ読み出しを数えない条件では入力が72,531→4,281、まとめ送りで240回→24回になった。
- 同じ処理量の中で誰を待たせるかは優先度で決める。対話の待ちの中央値が10.6秒から1.1秒になり、代わりにバッチの最後が5秒遅れた。
参考・出典
この節では、この記事で根拠にした公式ドキュメントと仕様が分かります(確認日 2026年11月4日)。
- OpenAI「Rate limits」:https://developers.openai.com/api/docs/guides/rate-limits
- Anthropic「Rate limits」:https://platform.claude.com/docs/en/api/rate-limits
- Anthropic「Token counting」:https://platform.claude.com/docs/en/build-with-claude/token-counting
- Google「Rate limits(Gemini API)」:https://ai.google.dev/gemini-api/docs/rate-limits
- Microsoft Learn「Azure OpenAI のクォータの管理」:https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/quota
- Microsoft Learn「LLM レート制限(Dev Proxy)」:https://learn.microsoft.com/ja-jp/microsoft-cloud/dev/dev-proxy/concepts/llm-rate-limits
- RFC 6585「Additional HTTP Status Codes」4節 429 Too Many Requests:https://www.rfc-editor.org/rfc/rfc6585.html
- RFC 9110「HTTP Semantics」Retry-After:https://httpwg.org/specs/rfc9110.html#field.retry-after
- MDN「Retry-After」:https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「LLM APIの429対策|TPM・RPMをトークンバケットで守る設計と実測」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。