GEMINI LABEN
SUNSET — gemini-robotics-er-1.6-preview は8月31日に停止します。残り13日となり、移行の実作業に取りかかる時期ですMIGRATION — 移行先は7月30日にパブリックプレビューとなった gemini-robotics-er-2-preview と gemini-robotics-er-2-streaming-preview です。テキスト・画像・動画・音声を入力できますPRICING — 8月13日に一般提供となった Gemini 3.7 Flash の導入価格は2026年12月31日までです。年をまたぐワークロードは価格改定を織り込んだ試算が要りますDEPRECATION — サンプリングパラメータの temperature・top_p・top_k が非推奨になりました。明示指定しているコードは、指定を外した場合の出力差分を今のうちに測っておくと安全ですVIDEO — 動画の生成と編集に対応する Gemini Omni Flash が Google AI Studio と Gemini API から利用できますLOGS — Interactions API が開発者ログに対応し、対象となる API 呼び出しのログを AI Studio のダッシュボードで確認できるようになりましたSUNSET — gemini-robotics-er-1.6-preview は8月31日に停止します。残り13日となり、移行の実作業に取りかかる時期ですMIGRATION — 移行先は7月30日にパブリックプレビューとなった gemini-robotics-er-2-preview と gemini-robotics-er-2-streaming-preview です。テキスト・画像・動画・音声を入力できますPRICING — 8月13日に一般提供となった Gemini 3.7 Flash の導入価格は2026年12月31日までです。年をまたぐワークロードは価格改定を織り込んだ試算が要りますDEPRECATION — サンプリングパラメータの temperature・top_p・top_k が非推奨になりました。明示指定しているコードは、指定を外した場合の出力差分を今のうちに測っておくと安全ですVIDEO — 動画の生成と編集に対応する Gemini Omni Flash が Google AI Studio と Gemini API から利用できますLOGS — Interactions API が開発者ログに対応し、対象となる API 呼び出しのログを AI Studio のダッシュボードで確認できるようになりました
記事一覧/API / SDK
API / SDK/2026-07-04上級

一晩のバッチで静かに落ちた数十件をどう拾うか — Gemini Batch API の行単位リトライ台帳

Batch API の「完了」は「全件成功」ではありません。個人開発で夜間バッチを回し続けるなかで見えた、行単位の結果台帳・一時失敗と恒久失敗の切り分け・選択的リトライ・恒久失敗の無限リトライ防止を、SQLite で組んだ状態機械の動くコードとともに残します。

gemini-api281batch-api3リトライ設計3冪等性6個人開発101運用設計14

プレミアム記事

朝、バッチジョブのステータスが SUCCEEDED になっていました。安心して結果を書き戻し、その日は別の作業に移りました。

数日後、分類結果を眺めていて気づきます。ある一群のレビューだけ、カテゴリが空のまま Firestore に入っていました。件数にして数十件。ジョブ全体は「成功」でも、その中の一部の行は静かに落ちていたのです。

個人開発で複数のアプリと4つのサイトを回していると、夜間バッチは「動かしてから寝る」道具になります。App Store Connect と Google Play Console に溜まったレビューを分類する私自身の運用でも、翌朝に全件が揃っている前提で次の処理を積み上げていました。だからこそ、この「一部だけ落ちる」取りこぼしは、後工程まで汚染してしまいます。

この記事は、Batch API の完了を「全件成功」と読んでしまう油断をやめ、行単位で結果を台帳に記録し、落ちた行だけを拾い直す設計をまとめたものです。夜間処理の初回実装ではなく、その後に必ず訪れる「失敗した数十件をどう回収するか」に焦点を当てています。

「完了」と「全件成功」は違う

Batch API のジョブ状態と、ジョブに含まれる個々のリクエストの成否は、別の層の話です。ジョブは無事に終わっても、出力JSONL の各行には成功したレスポンスと失敗したエラーが混在します。

出力の1行は、おおよそ次のどちらかの形をしています。SDK のバージョンによってキー名は前後します。この場合は、実際の出力を一度 head で覗いてから実装に落とすことを推奨します。

{"key": "review-000512", "response": {"candidates": [ ... ]}}
{"key": "review-000513", "error": {"code": 400, "message": "..."}}

つまり、ジョブが SUCCEEDED でも、error を持つ行は普通に混ざります。私が取りこぼしたのは、safety フィルタに引っかかった攻撃的なレビュー本文と、絵文字だけで構成された本文でスキーマ抽出に失敗した行でした。

私自身の実測では、8,000件規模のバッチで error を持つ行はおおむね全体の1〜2%、件数にして数十件から百数十件でした。通常APIの約50%というコストで回せる代わりに、この数%を静かに取りこぼすと、後工程がじわじわ狂います。

ここで大切な考え方を1つ。ジョブの成否と、行の成否を、別々に記録すること。この2層を混ぜて「ジョブが成功したから全部入れる」とすると、必ず穴が空きます。

行単位の台帳を持つ

そこで、投入するリクエスト1件ごとに状態を持つ台帳を用意します。キーは custom_id(ここでは key)です。状態は次の4つに絞りました。

状態意味次にすること
pendingまだ結果を受け取っていない次のバッチに含める
succeeded正常な構造化出力が得られた何もしない(確定)
retryable一時的な失敗(429 / 503 等)回数上限まで再投入する
permanent入力起因・safety 等で再試行しても直らない再投入せず、別扱いで人間が見る

SQLite にしたのは、個人開発の夜間バッチで「1ファイルで持ち運べて、途中で落ちても残る」ことが何より効くからです。台帳のスキーマと初期化はこれだけです。

import sqlite3
import time
 
def open_ledger(path: str = "batch_ledger.db") -> sqlite3.Connection:
    conn = sqlite3.connect(path)
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS rows (
            key           TEXT PRIMARY KEY,
            payload       TEXT NOT NULL,   -- 入力リクエスト(JSON文字列)
            status        TEXT NOT NULL DEFAULT 'pending',
            attempts      INTEGER NOT NULL DEFAULT 0,
            last_error    TEXT,
            result        TEXT,            -- 成功時の抽出結果(JSON文字列)
            updated_at    REAL NOT NULL DEFAULT 0
        )
        """
    )
    conn.commit()
    return conn
 
 
def enroll(conn: sqlite3.Connection, key: str, payload: str) -> None:
    """初回投入時に pending として登録する。再投入では触らない。"""
    conn.execute(
        "INSERT OR IGNORE INTO rows(key, payload, updated_at) VALUES (?, ?, ?)",
        (key, payload, time.time()),
    )
    conn.commit()

INSERT OR IGNORE にしているのが要点です。2晩目に同じ key で再投入しても、succeeded 行を pending に巻き戻さない。台帳は「一度確定した成功を守る」ためにあります。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
Batch の出力JSONLを custom_id で照合し pending / succeeded / retryable / permanent の4状態を持つ SQLite 台帳を組む実装を、コピペで動く形で手に入れる
429 や 503 のような一時失敗と、safety ブロックや不正入力のような恒久失敗を分類し、恒久失敗を無限にリトライしない停止条件の作り方がわかる
2晩目・3晩目に「失敗した行だけ」を再投入する選択的リトライと、試行をまたいだコスト計上のズレを台帳で吸収する運用手順を学べる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

API / SDK2026-06-21
Gemini Batch API でアプリレビュー8,000件を一晩で分類し、ポーリングを Webhooks に寄せるまで
個人開発の6アプリで溜まった約8,000件のレビューを Gemini Batch API で一晩のうちに分類した実装メモに、2026年6月のイベント駆動 Webhooks で翌朝のポーリングを置き換える設計を加えました。コスト・所要時間の実数値、複合キー設計、ハングジョブの見切り、期限つき非推奨の管理まで、動くコード付きで残します。
API / SDK2026-07-18
Managed Agent の長時間走行がサンドボックス再生成で消える前に — チェックポイントと冪等リジュームの設計
Managed Agents のサンドボックスは再生成されます。40分走った処理が振り出しに戻る前に、進捗を外部へ逃がすチェックポイントと、副作用を二度実行しない冪等リジュームを設計します。SQLite で動く実装つき。
API / SDK2026-07-14
並行して回す実験が共有予算を食い尽くす前に — AI Studio 費用上限をプロジェクト隔離の境界にする設計
個人開発で複数の Gemini 実験を同じ請求アカウントで並行させると、ひとつの暴走が他の全部を巻き添えにします。AI Studio に入ったプロジェクト単位の費用上限を隔離境界として使い、クライアント側のソフト天井と月次照合まで含めた設計を実装込みでまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →