GEMINI LABEN
ROBOTICS — Gemini Robotics ER 2 が公開プレビューになりました。空間推論・エージェント的なコード実行・複数ロボットの協調に対応しますSTREAMING — gemini-robotics-er-2-streaming-preview は Live API による実時間ストリーミング向けで、音声と映像の双方向入力を扱えますSUNSET — 停止日が近づいています。8月17日に Imagen 系(移行先は gemini-3.1-flash-image)、20日に Grok 4.1 ファミリー、31日に gemini-robotics-er-1.6-preview ですSAMPLING — temperature・top_p・top_k が非推奨になりました。これらを設定として公開しているアプリケーションは、扱いの見直しが要りますLOGS — Interactions API の開発者ログに対応し、AI Studio のダッシュボードから閲覧できるようになりましたMODELS — Gemini 3.1 Pro は依然プレビューです。3.6 Flash は7月21日に GA、3.5 Flash-Lite は大量処理のサブエージェント用途に向きますROBOTICS — Gemini Robotics ER 2 が公開プレビューになりました。空間推論・エージェント的なコード実行・複数ロボットの協調に対応しますSTREAMING — gemini-robotics-er-2-streaming-preview は Live API による実時間ストリーミング向けで、音声と映像の双方向入力を扱えますSUNSET — 停止日が近づいています。8月17日に Imagen 系(移行先は gemini-3.1-flash-image)、20日に Grok 4.1 ファミリー、31日に gemini-robotics-er-1.6-preview ですSAMPLING — temperature・top_p・top_k が非推奨になりました。これらを設定として公開しているアプリケーションは、扱いの見直しが要りますLOGS — Interactions API の開発者ログに対応し、AI Studio のダッシュボードから閲覧できるようになりましたMODELS — Gemini 3.1 Pro は依然プレビューです。3.6 Flash は7月21日に GA、3.5 Flash-Lite は大量処理のサブエージェント用途に向きます
記事一覧/API / SDK
API / SDK/2026-06-23上級

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

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

Gemini API206テールレイテンシp95SLO2ストリーミング12

プレミアム記事

平均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-08-07
ストリーミング応答の日本語が静かに欠ける — バイト境界とイベント境界を実測で切り分ける
ストリーミング表示の日本語だけが欠ける現象を、ローカルの疑似SSEサーバで実測しました。壊れていた層は二つあり、片方は例外にすらならず、英語のテストでは検出率がゼロでした。
API / SDK2026-07-17
途中で切れた Gemini のストリーミングを、引き直すか続きから書かせるか — 二重に払わないための分岐設計
モバイル回線でストリーミングが切れたあと、無条件に全部引き直していませんか。受信済み出力を続きに使う方式との費用差を式で出し、継ぎ目の壊れ方まで含めて分岐条件を決める設計をまとめます。
API / SDK2026-07-14
Batch ジョブが『まだ RUNNING です』のまま半日溶けていたとき — 状態別の滞留予算とレコード突合で詰まりを早期に名指しする運用メモ
Gemini Batch API のジョブが 24 時間 SLA の陰で静かに滞留していたとき、状態別の滞留時間予算とレコード数の突合で詰まりと取りこぼしを早期に検知するための計測手順を、運用ログの実測とともに整理します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →