連載の現在地:Phase 2「費用と安定」(2 / 4本目) — 使われ始めたら、財布と稼働を守る
1. 公開 → 2. 費用と安定 → 3. 安全 → 4. 品質 → 5. 自動化
こんな悩みの人へ
ユーザーが増える時間帯に「429 Too Many Requests」が出て、AI機能が使えなくなる。プロバイダー側の障害で、自分のサービスまで止まってしまった。エラーのときに、画面がずっと読み込み中のまま固まる。そんな経験がある方に向けた記事です。
結論
止まりにくくするには、3つの対策を組み合わせます。一時的な混雑には、少し待ってから再試行します。障害が長引いたときは、別のモデルやプロバイダーに切り替えます。そして、利用者を待たせ続けないように、待ち時間に上限を設け、失敗したときの表示を決めておきます。
1回のリクエストは、次のような順で処理されます。
リクエスト
-> OpenAI 失敗(5xx)なら、少し待って再試行(1秒、2秒 + ばらつき)
-> Anthropic それでも失敗したら、別のプロバイダーへ切り替える
-> 打ち切り 全体で30秒を超えたら止め、「混み合っています」と表示する再試行と切り替えは、アプリのコードでも書けます。複数のアプリで同じ仕組みを使うなら、Cloudflare AI Gatewayを間に置いて、再試行や切り替えをまとめて設定すると管理が楽になります。
なぜAIの機能は止まりやすいのか
AIの機能が止まりやすいのは、まず、プロバイダー側の上限をこちらで変えられないからです。1分あたりのリクエスト数やトークン数には上限があり、超えると429が返ります。
1社に頼り切っていることも原因です。そのプロバイダーで障害が起きると、代わりがありません。
さらに、再試行の書き方を間違えると、かえって悪化します。失敗してすぐに何度も送り直すと、混雑をさらに強めてしまいます。
解決策の比較
方法 | 費用 | 手間 | 向いている状況 |
|---|---|---|---|
AI Gatewayの再試行・切り替え | 中心機能は無料 | 少ない | 複数のアプリや機能で、同じ方針を使いたい |
SDKの再試行設定 | 無料 | 少ない | 1つのプロバイダーで、一時的な429を吸収したい |
アプリに自前で書く | 開発の工数 | 中くらい | 切り替えの条件を細かく制御したい |
有料の上位プランで上限を上げる | プロバイダーの料金 | 少ない | 恒常的に上限に当たっている |
恒常的に上限に当たっているなら、再試行では解決しません。プロバイダーの利用枠(Tier)の引き上げや、キャッシュで呼び出し自体を減らすことを先に検討します。
どれを選ぶか
たまに429が出る程度なら、SDKの再試行設定で足ります。プロバイダーの障害でも止めたくない場合は、AI Gatewayか自前の実装で、別のプロバイダーへの切り替えを入れます。毎日同じ時間帯に上限に当たっているなら、再試行より先に、利用枠の引き上げとキャッシュを検討してください。
手順
1. エラーの種類を分ける
エラー | 意味 | 対応 |
|---|---|---|
429 | 一時的な回数制限、予算上限、クレジット不足など | エラー内容を確認。一時的な制限だけ、指定された時間を待つ |
500・502・503 | プロバイダー側の不調 | 再試行し、続けば切り替え |
タイムアウト | 応答が遅い | 切り替え |
400・401 | リクエストやキーの誤り | 再試行しない。直す |
400・401・403など、入力や認証を直す必要があるエラーは通常再試行しません。408や409のように再試行できる場合もあるため、採用したAPIの仕様で対象を決めます。429でも、支出上限やクレジット不足は待つだけでは解決しません。
2. 待ち時間を延ばしながら再試行する
再試行を1秒後、2秒後のように遅らせ、ばらつき(ジッター)を加えます。Retry-After が返る場合は、その指定も尊重します。以下は対象を一時的な5xxに絞り、最大2回の再試行、1試行10秒、全体30秒の期限を設ける例です。429を再試行する処理は、原因を判別してから別途追加します。
const transientStatuses = new Set([500, 502, 503, 504]);
async function callWithRetry(
fn: (signal: AbortSignal) => Promise<Response>,
maxRetries = 2,
deadline = Date.now() + 30_000,
) {
for (let i = 0; i <= maxRetries; i++) {
const remaining = deadline - Date.now();
if (remaining <= 0) throw new DOMException("Deadline exceeded", "TimeoutError");
const res = await fn(AbortSignal.timeout(Math.min(remaining, 10_000)));
if (!transientStatuses.has(res.status) || i === maxRetries) return res;
const value = res.headers.get("Retry-After");
let retryAfter = 0;
if (value) {
const seconds = Number(value);
retryAfter = Number.isFinite(seconds)
? Math.max(0, seconds * 1000)
: Math.max(0, (Date.parse(value) || 0) - Date.now());
}
const wait = Math.max(1000 * 2 ** i + Math.random() * 300, retryAfter);
if (Date.now() + wait >= deadline) return res;
await res.body?.cancel();
await new Promise((r) => setTimeout(r, wait));
}
throw new Error("unreachable");
}このコードには、意図して書いている箇所が2つあります。
1つは await res.body?.cancel() です。再試行や切り替えの前に、使わないレスポンスの本文を明示的に閉じています。読まずに放置すると、Workersの中で接続とメモリが使われたままになります。Workersには、1回の処理で同時に開ける接続数に上限があるため、再試行を重ねるうちに新しいリクエストを送れなくなるのを防いでいるわけです。
もう1つは、deadline を引数で受け渡している点です。再試行と、次の手順で行う切り替えとで、同じ期限を共有するためです。関数ごとに別々の待ち時間を持たせると、合計の待ち時間が30秒を大きく超えてしまいます。
3. 失敗が続いたら、別のモデルに切り替える
const providers = [
(signal: AbortSignal) => callOpenAI(messages, { signal }),
(signal: AbortSignal) => callAnthropic(messages, { signal }),
];
const deadline = Date.now() + 30_000;
for (const call of providers) {
if (Date.now() >= deadline) break;
try {
const res = await callWithRetry(call, 2, deadline);
if (res.ok) return res;
const status = res.status;
await res.body?.cancel();
if (!transientStatuses.has(status)) {
return new Response("利用上限またはリクエスト設定を確認してください。", {
status: status === 429 ? 429 : 502,
});
}
} catch (error) {
if (error instanceof DOMException && error.name === "TimeoutError") continue;
if (!(error instanceof TypeError)) throw error;
// 通信エラーなら、期限内で次のプロバイダーを試す
}
}
return new Response("ただいま混み合っています。時間をおいてお試しください。", { status: 503 });上の callOpenAI() と callAnthropic() は、HTTPの Response を返すアプリ側の補助関数です。受け取った signal を実際の fetch() に渡す必要があります。SDKを使う場合は、SDKの例外や再試行設定に合わせて実装し、SDK・アプリ・ゲートウェイの再試行が重複しないようにします。400・401・429などでは、自動で別モデルへ切り替えません。
AI Gatewayで再試行をまとめる場合は、ゲートウェイの「Settings」→「Retry Requests」を有効にし、回数・待ち時間・バックオフ方式を設定します。別モデルへの切り替えは、ベータ機能のDynamic Routingで構成できます。Request handlingでは、リクエストごとにタイムアウトや再試行を指定するヘッダーも説明されています。
4. 待ち時間の上限と、失敗時の表示を決める
応答を待つのは、たとえば30秒までと決め、それを超えたら打ち切ります。画面には「混み合っています。もう一度お試しください」と表示し、再送ボタンを出します。このとき、エラーの中身(APIの応答)を、そのまま画面に出さないようにします。
5. ログで傾向を見る
AI Gatewayのログや分析で、エラーがいつ、どのモデルで多いかを確認します。特定の時間帯に集中するなら、利用枠の引き上げやキャッシュを検討します。
更新日:2026-11-03
つまずきやすい点
再試行によって、二重に課金されることがあります。タイムアウトで打ち切っても、プロバイダー側では処理が終わっていて、課金される場合があるためです。
切り替え先で、答えの形式が変わることもあります。モデルによって出力の長さや書き方の癖が違うので、JSONで受け取る場合は、切り替え先でも同じ形になるか確認してください。
ストリーミング中の失敗も、扱いを決めておく必要があります。途中まで表示してから失敗したときに、続きを再生成するのか、最初からやり直すのかを決めておきます。
最後に、再試行の回数を増やしすぎないことです。回数・待ち時間・費用をあわせて上限を決めます。適切な回数は用途によって異なり、ストリーミング中の失敗は別に扱います。


次に出やすい悩み
止まりにくくなったら、次は「そもそも全部同じモデルでよいのか」「使いすぎる人をどう抑えるか」が見えてきます。切り替え先のモデルを費用と品質で選び直したいなら「複数のAIモデルを使い分けたい」を、再試行で費用が増えていないか確かめたいなら「LLM APIの請求額が読めない」を参考にしてください。特定のユーザーの連打で上限に当たっている場合は、「ユーザーごとにAIの利用量を制限したい」で対策を紹介しています。
「障害のたびにAI機能が止まって困っている」という状況なら、まずは現在の構成を見せてください。止まりにくいAI機能の構成はCloudflareインフラ構築でご相談いただけます。
よくある質問
Q. 429などのエラーは、すべて待って再試行すれば直りますか?
まず原因を分けます。429でも、支出上限やクレジット不足は待つだけでは解決しません。入力や認証の誤りは修正し、一時的な制限だけを再試行の対象にします。予算による429はSpend Limitsの仕様でも確認できます。
Q. 再試行は、成功するまで続けてもよいですか?
回数、1回のタイムアウト、処理全体の待ち時間、費用に上限を設けます。再試行の間隔を延ばし、ばらつきを加え、Retry-Afterがある場合はその指定も考慮します。恒常的に利用枠を使い切っている場合は、再試行より先に利用枠やキャッシュを見直してください。
Q. 別のモデルへ切り替えれば、利用者は違いを意識せず使えますか?
出力の形式、回答の品質、対応機能が変わる場合があります。JSONなどの形式と業務上の条件を、切り替え先でも検証します。ストリーミングの途中で失敗した場合は、途中の表示をどう扱い、最初からやり直すかも別に決めておきます。
Q. タイムアウトで打ち切った処理には、課金されませんか?
画面やアプリ側が待つのをやめても、提供元では処理が終わり、課金される場合があります。再試行や切り替えで追加の費用が発生する可能性も含め、回数と予算を決めます。タイムアウトを、そのまま処理の取り消しや無課金の保証として扱わないでください。
執筆者情報
IT顧問、AI・業務アプリ開発、SESを通じて、企業のIT活用を支援しています。代表の藤澤 昌隆は、2007年からシステム開発・運用に携わり、アクセンチュアでの業務ITコンサルティングを経て、2019年にパブロフ株式会社を設立しました。
AI活用の支援事例として、営業工程を支える7つの業務別AIアプリの構築を公開しています。アプリの開発に加え、入力・出力の形式、評価基準、テストケースの整備にも取り組んでいます。
記事の内容に関するご質問や誤りのご指摘は、お問い合わせ窓口へお寄せください。
