GEMINI LABEN
MODEL — Gemini 3.5 Flashが一般提供開始。3.1 Proをほぼ全ベンチで上回りつつ4倍高速に動作しますAGENTS — Managed AgentsがGemini APIでパブリックプレビュー。Googleホストの隔離Linuxサンドボックスで自律エージェントを動かせますSEARCH — File Searchがマルチモーダル検索に対応。gemini-embedding-2で画像をネイティブに埋め込み・検索できますAPI — Batch APIや長時間処理向けにイベント駆動Webhooksが追加され、ポーリングを置き換えられますSTUDIO — Google AI Studioが自然言語からAndroidアプリを生成。Nano Bananaで画像も自動生成しますMIGRATION — Gemini CLIは6/18でEOL。Agentic 2.0 CLIへの移行が必要です(画像プレビュー2種は6/25停止)MODEL — Gemini 3.5 Flashが一般提供開始。3.1 Proをほぼ全ベンチで上回りつつ4倍高速に動作しますAGENTS — Managed AgentsがGemini APIでパブリックプレビュー。Googleホストの隔離Linuxサンドボックスで自律エージェントを動かせますSEARCH — File Searchがマルチモーダル検索に対応。gemini-embedding-2で画像をネイティブに埋め込み・検索できますAPI — Batch APIや長時間処理向けにイベント駆動Webhooksが追加され、ポーリングを置き換えられますSTUDIO — Google AI Studioが自然言語からAndroidアプリを生成。Nano Bananaで画像も自動生成しますMIGRATION — Gemini CLIは6/18でEOL。Agentic 2.0 CLIへの移行が必要です(画像プレビュー2種は6/25停止)
記事一覧/API / SDK
API / SDK/2026-06-23上級

Gemini API の平均レイテンシは速いのに、たまに固まる — p95/p99 を守る運用メモ

平均TTFTは速いのに、一定割合のユーザーだけ応答が固まる — これはテールレイテンシ(p95/p99)の問題です。計測の取り方からモデルルーティング・ストリーミング予算・キャッシュ会計・リトライ設計まで、本番で効いた防衛策をコード付きで共有します。

Gemini API144テールレイテンシp95SLOストリーミング8

プレミアム記事

平均TTFTが520msと出ているダッシュボードを眺めながら、サポートには「たまに固まる」という報告が届き続ける — この食い違いに半年ほど悩まされました。個人開発のチャット機能で Gemini API を本番に載せてしばらく経った頃のことです。平均は嘘をつきませんが、平均は「全員が体験している速さ」を表してはいません。実際にユーザーが文句を言うのは、100回に数回だけ訪れる遅い応答です。

この記事は「Gemini を速くする一般論」ではありません。平均はもう十分速い、けれど末尾(テール)の遅さがユーザー体験を壊しているという、一段階先の状況に向けた運用メモです。p95/p99 という指標を軸に、計測・ルーティング・タイムアウト・リトライ・キャッシュ会計をどう組み直したかを、実際に効いた順に共有します。

なぜ平均TTFTを見ても問題が見えないのか

レイテンシの分布は正規分布ではなく、右に長い裾を引きます。多くのリクエストは速く返り、ごく一部が極端に遅い。この形のとき、平均値は「速い側の塊」に引っ張られて低く出ます。つまり平均が520msでも、p99(上位1%の遅さ)が4,000msということは普通に起こります。

体感を決めるのは平均ではなく、この裾の厚さです。チャットUIでは、ひとりのユーザーが1セッションで10〜20回リクエストを投げます。1リクエストあたりp99が1%なら、20回のうち少なくとも1回が「固まる」確率は約18%です。ほぼ5人に1人が、1セッション中に一度はもっさりを体験する計算になります。平均だけ見ていると、この体感を永遠に取りこぼします。

まず必要なのは、平均ではなく分位点(パーセンタイル)で記録するテレメトリです。1リクエストごとに数値を吐き、後からヒストグラムに畳む形にしておきます。

# pip install google-genai
import time, json, math
from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def timed_stream(model: str, prompt: str, request_id: str):
    """1リクエストのレイテンシ内訳を構造化ログとして1行吐く。
    後段でp50/p95/p99に畳むことを前提に、生の数値だけを残す。"""
    t0 = time.perf_counter()
    t_first = None
    out_tokens = 0
    status = "ok"
    try:
        stream = client.models.generate_content_stream(model=model, contents=prompt)
        for chunk in stream:
            if t_first is None:
                t_first = time.perf_counter()
            if chunk.text:
                out_tokens += len(chunk.text)
    except Exception as e:
        status = type(e).__name__
    t_end = time.perf_counter()
 
    rec = {
        "request_id": request_id,
        "model": model,
        "ttft_ms": round((t_first - t0) * 1000) if t_first else None,
        "e2e_ms": round((t_end - t0) * 1000),
        "out_chars": out_tokens,
        "status": status,
    }
    print(json.dumps(rec, ensure_ascii=False))  # 構造化ログ基盤へ
    return rec

ポイントは、アプリ内で平均を計算しないことです。平均を先に出してしまうと、後から「p95だけ見たい」と思っても元の分布が戻ってきません。生の ttft_ms を残し、集計はログ基盤(BigQuery でも、手元の Python でも)側で分位点として行います。

def percentiles(values, ps=(50, 95, 99)):
    """ソート済み配列から分位点を線形補間で求める。
    依存を増やさず、ログを流し込んだ直後の点検に使う。"""
    xs = sorted(v for v in values if v is not None)
    if not xs:
        return {}
    out = {}
    for p in ps:
        k = (len(xs) - 1) * (p / 100)
        lo, hi = math.floor(k), math.ceil(k)
        out[f"p{p}"] = round(xs[lo] + (xs[hi] - xs[lo]) * (k - lo))
    return out
 
# 例: ttfts = [行ログから集めた ttft_ms のリスト]
# print(percentiles(ttfts))  ->  {'p50': 480, 'p95': 1700, 'p99': 4200}

この p50p99 の比(テール比)が私の最重要メトリクスです。p99/p50 が3を超えると、平均をいくら下げても体感は改善しません。裾を直接叩く必要があります。

テール時間予算を起点に設計を逆算する

裾を叩くうえで一番効いたのは、テクニックの足し算ではなく、1つの数字を先に決めることでした。それが「テール時間予算」です。

具体的には「このリクエストは何msまでに最初のトークンを返せなければ、待たせるより打ち切って手を打つべきか」を決めます。私のチャットUIでは、TTFTの予算を1,200msに置きました。p50が480msなので普段は余裕がありますが、この1,200msがすべての設計判断の起点になります。

予算が決まると、各レイヤーの上限が自動的に決まります。

レイヤー予算配分超過時の打ち手
クライアント→エッジ~150msリージョン同居・接続再利用
入力処理(TTFT)~900msキャッシュ・Thinking予算0・モデル格下げ
打ち切り判定の余白~150msタイムアウト発火 → フォールバック

重要なのは、予算を超えたときに「ただ待つ」のではなく明示的に打ち切ることです。Gemini クライアントの呼び出しを asyncio.wait_for で包み、TTFT予算を超えたら速い構成へ切り替えます。

import asyncio
from google import genai
 
aclient = genai.Client(api_key="YOUR_GEMINI_API_KEY").aio
 
async def first_token_within(model, prompt, budget_s):
    """budget_s 以内に最初のトークンが来たらそのストリームを返す。
    来なければ TimeoutError を投げ、呼び出し側でフォールバックさせる。"""
    stream = await aclient.models.generate_content_stream(model=model, contents=prompt)
    agen = stream.__aiter__()
    first = await asyncio.wait_for(agen.__anext__(), timeout=budget_s)
    return first, agen
 
async def answer(prompt):
    try:
        first, rest = await first_token_within("gemini-2.5-flash", prompt, 1.2)
    except (asyncio.TimeoutError, StopAsyncIteration):
        # 予算超過: 速い構成に逃がす(Thinking無効 + 軽量モデル)
        first, rest = await first_token_within("gemini-2.5-flash-lite", prompt, 2.0)
    yield first.text
    async for chunk in rest:
        if chunk.text:
            yield chunk.text

この「予算を超えたら格下げして再挑戦」というパターンは、平均を多少犠牲にしてでも p99 を劇的に縮めます。私の環境では、フォールバックを入れる前後で p99 TTFT が4,200msから1,900msまで下がりました。フォールバックが発火するのは全体の2%程度なので、平均はほとんど動きません。裾だけを選んで叩けていることが数字で確認できます。

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

この記事の続きを読む

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

この記事で得られること
平均ではなくp95/p99で計測するための最小テレメトリ実装と、ヒストグラムの読み方
テール時間予算(タイムアウト)を起点に、モデルルーティングとリトライを逆算する設計
キャッシュヒット率・Thinking予算・接続再利用が p99 に与える実測インパクトの切り分け方
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

API / SDK2026-05-26
iOS の URLSession で Gemini API のストリーミングがバックグラウンド復帰時に途切れる原因と対処
iOS アプリで Gemini API のストリーミング応答を受け取っている最中にアプリを一旦バックグラウンドに送って戻ると、接続が切れて続きが返ってこない――そんな現象に遭遇したときの原因切り分けと、URLSession 周りの最小修正をまとめます。
API / SDK2026-04-27
Gemini API のストリーミング応答をユーザー操作で中止する — AbortController と asyncio の正しい使い方
チャットUIの『停止』ボタンを押したのに Gemini API のストリーミングが裏で動き続けている — そんな見落としを防ぐための、AbortController と asyncio.CancelledError を使った安全な中止処理の実装方法をまとめます。
API / SDK2026-03-19
Unity × Gemini マルチモーダル AI 実装 完全コード集【上級編】
Unity に Gemini API のマルチモーダル機能(テキスト+画像+音声)を統合する完全実装ガイド。ストリーミング応答、画像認識NPC、音声対話システム、コンテキスト管理まで本番レベルのC#コードを公開。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →