GEMINI LABEN
MODEL — Gemini 3.5 Flashが一般提供。エージェント・コーディング向けの最有力モデルとして公開されましたAGENT — Managed AgentsがGemini APIで公開プレビュー。隔離されたLinuxサンドボックスで自律エージェントを実行できますWEBHOOK — Event-driven WebhooksがBatch APIと長時間処理に対応し、ポーリングが不要になりましたSECURITY — 6/19以降、未制限APIキーからのリクエストが遮断されます。キーの制限設定を見直しましょうDEPRECATED — 画像プレビュー2モデルが6/25で停止します。preview依存のフローは早めに移行をCODEASSIST — 6/18より個人向けCode Assist拡張とCLIが、AI Pro/Ultra層への提供を停止しましたMODEL — Gemini 3.5 Flashが一般提供。エージェント・コーディング向けの最有力モデルとして公開されましたAGENT — Managed AgentsがGemini APIで公開プレビュー。隔離されたLinuxサンドボックスで自律エージェントを実行できますWEBHOOK — Event-driven WebhooksがBatch APIと長時間処理に対応し、ポーリングが不要になりましたSECURITY — 6/19以降、未制限APIキーからのリクエストが遮断されます。キーの制限設定を見直しましょうDEPRECATED — 画像プレビュー2モデルが6/25で停止します。preview依存のフローは早めに移行をCODEASSIST — 6/18より個人向けCode Assist拡張とCLIが、AI Pro/Ultra層への提供を停止しました
記事一覧/API / SDK
API / SDK/2026-06-29上級

Gemini API の自動パイプラインで、静かに消える失敗ジョブを取りこぼさない設計

無人で回る Gemini API のバッチが1件だけ静かに落ちて気づかない——その取りこぼしを止めるデッドレター保存と安全な再投入の最小実装を、コピペで動くコードと運用判断つきでまとめました。

gemini91gemini-api256batch2reliability4automation31

プレミアム記事

深夜2時に走らせているバッチ処理のログを翌朝に眺めていたとき、50件の入力のうち1件だけ結果が欠けていることに気づきました。例外はどこにも投げられておらず、リトライも静かに尽きていて、そのジョブは結果が出ないまま、どの保存先にも残らず消えていました。3日経つまで気づけなかったのは、失敗が「エラー」ではなく「無」として処理されていたからです。

個人開発で複数のブログと壁紙アプリの自動処理を回していると、こうした「静かな取りこぼし」が一番こわい不具合だと感じます。落ちたことが画面に出る障害はまだ直せます。落ちたことすら残らない失敗は、気づくきっかけ自体がありません。今回は、無人で回る Gemini API パイプラインで失敗ジョブを一件も捨てないための、デッドレター(dead-letter)保存と再投入の作り方を、私自身が運用に入れている形でまとめます。

リトライを足しても「無言の消失」は止まらない

失敗対策というとまずリトライを思い浮かべますが、リトライは取りこぼしの半分しか解きません。問題は、リトライが尽きたあとのジョブの行き先です。多くのコードは max_attempts を超えると return Nonecontinue で先へ進みます。ループは正常に最後まで回り、終了コードは 0 になり、失敗は記録されません。

もう一つの落とし穴は、そもそも再試行しても無駄な失敗です。入力 JSON の構造が壊れている、安全フィルタに恒久的に弾かれている、INVALID_ARGUMENT が返る——これらは何度投げても同じ結果になります。ここでリトライを回すと、API 費用と時間を溶かしながら、最後はやはり静かに消えます。

つまり必要なのは、リトライの追加ではなく「失敗の終着点」を用意することです。再試行で回復し得るものは時間を置いて試し、回復しないものは捨てずに退避させ、あとから人の判断で扱えるようにする。この退避先がデッドレターです。

デッドレターに何を保存するか

デッドレターは特別なミドルウェアを入れなくても、SQLite のテーブル1枚で十分に始められます。私はまず手元の SQLite で運用を確かめてから、必要に応じて Firestore などへ移すようにしています。大切なのは置き場所より「あとで再投入できるだけの情報を全部持つこと」です。

保存すべきは、ジョブを一意に識別する ID、入力そのもの(再投入に必須)、入力の指紋(重複検知用)、使ったモデル、失敗の分類、最後のエラーメッセージ、試行回数、初回と直近の時刻、そして状態です。

import sqlite3
import json
import time
import hashlib
 
DLQ_PATH = "dead_letter.db"
 
 
def init_dlq(path: str = DLQ_PATH) -> None:
    """デッドレター用の最小テーブルを用意します。"""
    con = sqlite3.connect(path)
    con.execute(
        """
        CREATE TABLE IF NOT EXISTS dead_letter (
            job_id        TEXT PRIMARY KEY,
            payload_hash  TEXT NOT NULL,
            payload_json  TEXT NOT NULL,
            model         TEXT,
            error_class   TEXT NOT NULL,
            error_message TEXT,
            attempts      INTEGER NOT NULL,
            first_seen    REAL NOT NULL,
            last_seen     REAL NOT NULL,
            status        TEXT NOT NULL DEFAULT 'parked'
        )
        """
    )
    con.commit()
    con.close()
 
 
def payload_fingerprint(payload: dict) -> str:
    """同じ入力を一意に識別するための指紋を作ります。"""
    canonical = json.dumps(payload, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
 
 
def park(job_id: str, payload: dict, model: str,
         error_class: str, error_message: str, attempts: int,
         path: str = DLQ_PATH) -> None:
    """恒久失敗ジョブをデッドレターへ退避します(既存なら更新)。"""
    now = time.time()
    con = sqlite3.connect(path)
    con.execute(
        """
        INSERT INTO dead_letter
            (job_id, payload_hash, payload_json, model,
             error_class, error_message, attempts, first_seen, last_seen, status)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'parked')
        ON CONFLICT(job_id) DO UPDATE SET
            last_seen     = excluded.last_seen,
            attempts      = excluded.attempts,
            error_class   = excluded.error_class,
            error_message = excluded.error_message
        """,
        (job_id, payload_fingerprint(payload),
         json.dumps(payload, ensure_ascii=False), model,
         error_class, error_message, attempts, now, now),
    )
    con.commit()
    con.close()

job_id を主キーにして ON CONFLICT ... DO UPDATE を使うのは、同じジョブが再び落ちたときに行を増やさず last_seen と試行回数だけ更新するためです。これをしないと、同じ恒久失敗がデッドレターを何百行も埋め、本当に見るべき内訳が埋もれます。

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

この記事の続きを読む

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

この記事で得られること
深夜バッチが1件だけ静かに失敗しても取りこぼさない、デッドレター保存の最小実装をコピペで導入できる
「再試行すべき失敗」と「再試行しても無駄な恒久失敗」を機械的に切り分け、無限リトライと無言の消失の両方を止められる
原因を直したあとに恒久失敗ジョブをまとめて安全に再投入(replay)する手順が手に入る
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

API / SDK2026-06-17
Gemini CLI 終了前に、自動化スクリプトを API へ載せ替えた記録
6/18 に Gemini CLI のホスト応答が停止します。シェルから gemini を呼んでいた自動化スクリプトを、google-genai SDK へ安全に載せ替える手順を、構造化出力・リトライ・コスト計測まで含めて実装ベースでまとめました。
API / SDK2026-06-13
Gemini の生成は通ったのに公開が落ちた朝 — 高価な出力を取りこぼさない『生成アウトボックス』の設計
生成は成功したのに、公開処理の直前でプロセスが落ちる。すると高価な出力が消え、もう一度同じ生成に課金することになります。生成結果を先に永続化し、公開を冪等な後続処理に分離する『生成アウトボックス』の実装と、6月の障害下での運用記録をまとめました。
API / SDK2026-03-29
Gemini API で多言語翻訳・ローカライゼーションを自動化する
Gemini API を使って多言語翻訳やアプリのローカライゼーションを自動化する方法を Python コード付きで解説。バッチ処理・用語集管理・品質チェックまで網羅します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →