GEMINI LABEN
OUTAGE — Geminiが過去最大級の障害(error 1076/1099)から回復へ。エンジニアリングチームの緩和策で影響は縮小中DAILY-BRIEF — 新エージェント「Daily Brief」が登場。夜間にinbox・カレンダー・タスクを分析し、朝のパーソナルダイジェストを生成GEMINI-OMNI — Geminiと生成メディアモデルを統合した動画AI「Gemini Omni」。プロンプトから一貫性のある高品質動画を生成ENTERPRISE — Gemini Enterpriseで3.5 Flashが6/8からデフォルト固定に。機能管理トグルは廃止され全ユーザーで有効DEPRECATION — 画像previewモデル(3.1-flash-image/3-pro-image)は6/25に停止。GA版への移行はお早めにFILE-SEARCH — File Searchがマルチモーダル検索に対応。gemini-embedding-2で画像をネイティブに埋め込み・検索OUTAGE — Geminiが過去最大級の障害(error 1076/1099)から回復へ。エンジニアリングチームの緩和策で影響は縮小中DAILY-BRIEF — 新エージェント「Daily Brief」が登場。夜間にinbox・カレンダー・タスクを分析し、朝のパーソナルダイジェストを生成GEMINI-OMNI — Geminiと生成メディアモデルを統合した動画AI「Gemini Omni」。プロンプトから一貫性のある高品質動画を生成ENTERPRISE — Gemini Enterpriseで3.5 Flashが6/8からデフォルト固定に。機能管理トグルは廃止され全ユーザーで有効DEPRECATION — 画像previewモデル(3.1-flash-image/3-pro-image)は6/25に停止。GA版への移行はお早めにFILE-SEARCH — File Searchがマルチモーダル検索に対応。gemini-embedding-2で画像をネイティブに埋め込み・検索
記事一覧/API / SDK
API / SDK/2026-06-12中級

Gemini API の大規模障害で夜間バッチを止めないために組んだ三層の防御

今週の Gemini 広域障害で夜間バッチが3時間分の処理を取りこぼした反省から、リトライ・モデルフォールバック・縮退運転の三層防御に組み替えた記録です。再試行してよい失敗の見分け方、冪等性の確保、復旧後の追い付き処理まで実装コード付きで残します。

Gemini API131障害対応2リトライフォールバック夜間バッチ

今週、Gemini API の広範な障害で、毎晩動かしている集計バッチが3時間分の処理を取りこぼしました。見慣れない番号のエラーが立て続けに返り、リトライも全部同じ顔で落ちていく。朝にダッシュボードを見た瞬間、「これはこちら側では何もできない種類の障害」と分かりましたが、本当の問題はそこではありませんでした。復旧したあと、取りこぼした3時間分を安全に流し直す手段を、私は用意していなかったのです。

幸い実害は軽く済みました。ただ、個人開発のサービスは自分が寝ている間も動き続けます。この機会に夜間バッチの防御を三層構成に組み替えたので、設計の判断と実装をまとめて残しておきます。前提は Node.js と公式 SDK の構成ですが、考え方は他の言語でもそのまま通じるはずです。

失敗を4種類に分けるところから始める

障害対応のコードを書く前に、まず「失敗」を分類しました。すべてを同じリトライ処理に放り込むのが一番危険だからです。

  • 一過性の失敗: 429 のレート超過、503 や今回のような広域障害。時間を置けば直る見込みがあります
  • 恒久的な失敗: 400 系の入力不正や認証エラー。何度送っても直りません
  • ネットワーク層の失敗: タイムアウトや接続断。リクエストが届いたかどうか不明な点が厄介です
  • 品質の失敗: ステータスは 200 でも、出力が壊れていて後段で使えないケース

この分類で大事なのは、機械的に再試行してよいのは1番目と3番目だけという点です。400 を再送し続けるのはクォータの無駄遣いですし、品質の失敗をネットワークと同じ仕組みで再試行すると、同じ壊れた出力を高い確率でもう一度受け取るだけです。品質の失敗には、温度を下げる・プロンプトを変えるといった「条件を変えた再試行」が要ります。

第一層: 再試行してよい失敗だけをリトライする

分類が決まれば、リトライ処理は短く書けます。

import { GoogleGenAI } from "@google/genai";
 
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
 
type FailureKind = "transient" | "permanent" | "network";
 
function classifyError(err: unknown): FailureKind {
  const status = (err as { status?: number }).status;
  if (status === 429 || (status !== undefined && status >= 500)) return "transient";
  if (status !== undefined && status >= 400) return "permanent";
  return "network"; // fetch 失敗・タイムアウトなど status が取れないもの
}
 
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
  let lastError: unknown;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastError = err;
      if (classifyError(err) === "permanent") throw err; // 再送しても直らない
      const base = 1000 * 2 ** attempt;
      const jitter = Math.random() * base * 0.5;
      await new Promise((resolve) => setTimeout(resolve, base + jitter));
    }
  }
  throw lastError;
}

ポイントは2つあります。まず、ジッター(待ち時間の乱数)を省かないこと。障害復旧の瞬間に全クライアントが同時に再送すると、それ自体が二次障害の引き金になります。次に、試行回数は4回程度で潔く諦めること。広域障害は数十分から数時間続くため、その場で粘るより、後述のキューに積んで撤退するほうが全体としては早く片づきます。

第二層: モデルのフォールバックチェーン

リトライで救えないとき、次に試すのはモデルの切り替えです。私の場合は gemini-3.5-flash を主にして、失敗時は gemini-3.1-flash へ落とす二段構えにしています。

const MODEL_CHAIN = ["gemini-3.5-flash", "gemini-3.1-flash"];
 
async function generateWithFallback(
  prompt: string
): Promise<{ text: string; degraded: boolean }> {
  for (const [index, model] of MODEL_CHAIN.entries()) {
    try {
      const res = await withRetry(() =>
        ai.models.generateContent({ model, contents: prompt })
      );
      return { text: res.text ?? "", degraded: index > 0 };
    } catch {
      // チェーンの次のモデルへ
    }
  }
  throw new Error("all models in chain failed");
}

注意したいのは、フォールバック先の出力品質を平時に確認しておくことです。障害の真っ最中に「代替モデルの出力が後段の処理を通らない」と気づくのが最悪のパターンなので、私はこのチェーンを月に一度、わざと主モデルを外して通しています。degraded フラグを結果に残しておくと、あとから品質を点検する対象も絞れます。

ただ、正直に書いておくと、今回のような広域障害ではフォールバック先も一緒に落ちる時間帯がありました。同一基盤の障害に対して、同一ベンダー内の迂回は思ったほど効きません。設計時の想定より厳しい現実でしたが、だからこそ次の第三層が要ります。

第三層: 縮退運転 — 止めないことを優先する

最後の層は「AI なしでも壊れない」ことです。私のバッチで Gemini が担っているのは要約と分類で、それが無くても元データの表示は成立します。そこでチェーン全滅時は、前回実行時の結果をそのまま使い回し、画面には「最新の分析は一時的に更新を停止しています」と小さく出すだけにしました。

縮退の設計で効いたのは、「何が欠けても画面が成立するか」を機能ごとに書き出したことです。一覧にしてみると、絶対に止められない処理は思ったより少ないものでした。AI の出力は多くの場面で「あれば嬉しい付加情報」であって、土台ではありません。土台でない部分の障害でサービス全体を道連れにしない。当たり前のようでいて、書き出すまで私はこの線引きを曖昧にしたままでした。

障害をどう検知するか — アラートの閾値で迷った話

三層の防御を組んだあと、最後に残ったのが検知の問題でした。単発の失敗でアラートを鳴らすと、平時の散発的な 503 で通知が埋まり、本当の障害の日に通知を読まなくなります。私自身、この「アラート疲れ」で重要な通知を見落とした経験が一度あります。

落ち着いた先は、単発の失敗率ではなく「連続失敗数」と「5分窓の失敗率」の二段構えです。連続5件の失敗、または5分間で失敗率が半分を超えたときだけ通知する。この閾値にしてから、平時の誤報はなくなり、今回の障害では開始から数分で通知が届きました。

もうひとつの発見は、自前のエラー分類カウンタのほうが公式のステータスページより早いことです。ステータスページの更新には時間差があります。classifyError が transient を数え始めた時点で異常は確定しているので、検知は自分のメトリクスを一次情報にして、ステータスページは「裏取り」に使う運用へ変えました。

冪等性がないと再試行は事故になる

三層の防御より地味で、しかし今回いちばん反省したのがここです。私自身、リトライ処理は書き慣れたつもりでいましたが、書き込み側の冪等化を後回しにしていました。

タイムアウトした呼び出しは「届いていない」とは限りません。届いて、処理されて、応答だけが落ちた可能性があります。そのため、書き込みを伴う処理には冪等キーを付けました。バッチの各項目に「日付 + 項目ID」のキーを振り、結果テーブルへの書き込みを upsert に変える。これだけで「同じ項目を二度処理して二重に書き込む」事故は構造的に起きなくなります。リトライ機構を足すなら、その前に書き込みの冪等化を済ませる。順番としてはこちらが先だった、というのが今回の学びです。

復旧後の追い付き処理と、結局いちばん効いたもの

障害が明けたあとの再処理にも設計が要ります。今回の私の失敗は、処理に失敗した項目の記録が「エラーログの中」にしかなかったことでした。ログから対象を拾い直すのは、二度とやりたくない作業です。

組み替え後は、失敗した項目を pending_retry テーブルに積み、バッチの冒頭で「積み残しがあれば先に消化する」一手を入れました。これで復旧後は次の定時実行が自然に追い付き処理を兼ねます。専用の復旧スクリプトを書かずに済む構成にしておくと、障害のたびの手作業が消えます。

振り返ると、今回の障害で効いたのは高度な仕組みではなく、「失敗の分類」「冪等キー」「積み残しキュー」という地味な三点でした。夜間バッチを Gemini API に依存させているなら、次の障害が来る前に、失敗した項目がどこに記録されるかだけでも確認しておくと、復旧の朝がずいぶん楽になるはずです。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-06-12
壁紙アセットの画像探しを File Search のマルチモーダル検索に任せてみた記録
数千枚の壁紙アセットから目当ての一枚を探す作業を、File Search のマルチモーダル検索(gemini-embedding-2)に置き換えられるか300枚で検証しました。カテゴリタグ運用との比較、つまずいた点、使い分けの結論を実装コードとともに残します。
API / SDK2026-06-12
App Store 審査リジェクト対応に Gemini API を組み込む — 通知の構造化から Resolution Center 返信までの運用記録
App Storeの審査リジェクト通知をGemini APIで3層のJSONに構造化し、ガイドライン照合・Resolution Center返信ドラフト・提出前セルフチェックへつなげた個人開発の運用記録です。
API / SDK2026-06-11
Gemini 3.2 API 実装ガイド — 正しいモデルID・3.1 からの移行と本番チェックポイント
Gemini 3.2 を API から呼び出すための正しいモデルID、Gemini 3.1 からの移行時の変更点、Python・TypeScript の実装例、本番環境への移行チェックリストをまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →