ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-28上級

ストリーミング表示がローカルでは動くのに本番だと一括表示に戻るとき — 中間プロキシのバッファリングと沈黙する停止を計測する運用メモ

Antigravity 経由の Gemini ストリーミングを SSE で配信すると、ローカルでは1文字ずつ出るのに本番では数秒固まってから一括で出ます。中間プロキシのバッファリング・アイドル切断・再接続による二重生成を、計測してから潰す運用メモです。

antigravity401streaming5ssefastapi2nginxproduction57advanced19

プレミアム記事

ローカルの開発サーバーでは、生成された文字が1文字ずつ気持ちよく流れていました。ところが Cloudflare の後ろにデプロイした途端、ユーザーには「数秒固まってから全文が一気に出る」ように見える。エラーは一切出ていません。ログを見ても 200 が返っている。けれど体験としては、ストリーミングを実装した意味が完全に消えていました。

ストリーミングが本番で壊れるとき、その壊れ方はたいてい「沈黙」です。例外も 5xx も出ないまま、最初のトークンが届くまでの体感だけが悪化する。だからこそ最初にやるべきは設定をいじることではなく、どこで詰まっているかを数字で見ることです。この記事は、Antigravity 経由で Gemini のストリーミングを配信する構成を本番に載せたときに私が踏んだ詰まりと、それを計測してから一つずつ潰していった運用メモです。

最初に疑うべきは「モデル」ではなく「経路」

ストリーミングの体感が悪いとき、人はまずモデルが遅いと考えます。けれど多くの場合、モデルはすでに最初のチャンクを 0.5 秒前後で吐き出していて、それがあなたのブラウザに届くまでの経路で溜め込まれているだけです。

切り分けの軸は2つだけ持てば十分です。

ひとつは TTFB(最初のチャンクがクライアントに届くまでの時間)。もうひとつは チャンク間ギャップ(連続するチャンクの到着間隔) です。サーバー側で「モデルから受け取った時刻」、クライアント側で「画面に届いた時刻」を両方記録すると、詰まりがモデル側か経路側かが一目で分かります。

# server: モデルから各チャンクを受け取った時刻を計測する
from google import genai
import time
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def measure_stream(prompt: str):
    t0 = time.monotonic()
    first = None
    last = t0
    gaps = []
    stream = client.models.generate_content_stream(
        model="gemini-3.5-flash",
        contents=prompt,
    )
    for chunk in stream:
        now = time.monotonic()
        if first is None:
            first = now - t0          # TTFB(モデル→サーバー)
        gaps.append(now - last)        # チャンク間ギャップ
        last = now
    gaps.sort()
    p95 = gaps[int(len(gaps) * 0.95)] if gaps else 0
    print(f"TTFB(model->server)={first*1000:.0f}ms  gap_p95={p95*1000:.0f}ms  chunks={len(gaps)}")

サーバー側で測った TTFB が 400〜700ms なのに、ブラウザの体感 TTFB が 4〜6 秒なら、犯人は確実に経路です。実測では、この経路バッファだけで体感 TTFB が 8〜10 倍に膨らんでいました。モデルは仕事をしています。溜め込んでいるのは間にいる誰かです。

経路のどこでチャンクが溜まるか

「間にいる誰か」は1人ではありません。リクエストが通る各ホップが、それぞれ独立にバッファを持ち得ます。私が実際に遭遇した順に、犯人になりやすい箇所を整理します。

溜め込む箇所症状止め方
レスポンス圧縮(gzip/brotli)圧縮バッファが一定量たまるまで送出しないSSE 応答だけ圧縮を無効化する
Nginx / リバースプロキシproxy_buffering on が全レスポンスを溜める当該ロケーションで proxy_buffering off
CDN(Cloudflare 等)エッジが本文をバッファし一括転送Cache-Control: no-transform と非圧縮、チャンク転送維持
WSGI サーバー(gunicorn 同期ワーカー)ジェネレーターを最後まで回してから返すASGI(uvicorn)+ 非同期ジェネレーターにする
アプリの書き込みflush 不足でOSバッファに残るチャンクごとに明示フラッシュ

重要なのは、これらは AND 条件 だということです。Nginx のバッファだけ切っても、その手前で圧縮が溜めていれば体感は変わりません。だから「1つ直してダメだった」で諦めず、TTFB の数字が改善するまで上から順に潰します。本番運用では、私は次の順で切り分けて回避策を当てています。

  1. SSE 応答に限って圧縮を外し、再計測する
  2. リバースプロキシのバッファリングを当該ロケーションだけ無効化する
  3. CDN を非変換・チャンク転送維持にし、エッジが溜めていないか確認する

この3手で TTFB の数字が動かなければ、原因はアプリ自身のフラッシュ不足か WSGI 同期ワーカーに絞り込めます。順番を守るのは、下の層を直す前に上の層が溜めていると、対処の効果が数字に表れず判断を誤るからです。

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

この記事の続きを読む

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

この記事で得られること
TTFB と チャンク間ギャップ(p95)を計測して、詰まりが『モデル側』か『経路側』かを5分で切り分ける手順
Nginx・Cloudflare・圧縮・WSGI の4箇所でバッファを止める具体設定と、ハートビートでアイドル切断を防ぐ実装
EventSource の自動再接続が二重生成と二重課金を生む仕組みと、冪等キーで止める実コード
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

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

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

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

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

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

関連記事

アプリ開発2026-03-31
Antigravity × gRPC & Protocol Buffers — 高性能マイクロサービスAPI設計の実践ガイド
Antigravity のAIエージェントを活用して gRPC + Protocol Buffers によるマイクロサービスAPIを設計・実装する上級ガイド。スキーマ駆動開発、ストリーミング、認証、エラーハンドリングまで網羅します。
アプリ開発2026-05-03
Antigravity × TypeScript で作る冪等性キーと重複排除ストア本番設計ガイド
Stripe Webhook の二重課金や Temporal ワークフローの再実行で起きる重複処理を、冪等性キーと重複排除ストアで止める本番設計を、Antigravity を伴走者にして TypeScript で組み上げる実践ガイドです。
アプリ開発2026-05-01
Antigravity で実装するゼロダウンタイム DB マイグレーション:Expand-Contract パターンと AI 駆動の安全策
本番DBの型変更・カラム名変更・テーブル分割を、ユーザー影響ゼロで遂行するための Expand-Contract パターンを Antigravity の AI 支援とともに完全実装する本番運用ガイドです。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →