Nginx リバースプロキシの QPS が 15 分の 1 に低下:upstream keepalive の欠落と -O0 ビルドが原因だった
弊社の Nginx リバースプロキシは 94,706 QPS から 6,301 QPS へと 15 分の 1 に低下しましたが、エラーログは一件もありませんでした。OpenResty XRay のフレームグラフが特定した根本原因は 2 つ。upstream ブロックにおける keepalive ディレクティブの欠落(リクエストごとに TCP 接続を新規作成)と、放置されていた -O0 デバッグビルド(さらに 10% の性能を消費)です。両者を修正した結果、性能は完全に回復しました。
本稿では、フレームグラフを 1 枚ずつたどりながら診断の全過程を解説します。
Nginx リバースプロキシが 15 分の 1 に低速化、それでもエラーログはゼロ
成熟したエンジニアリングチームにおいて、性能検証は通常、明確で体系化されたプロセスです。特にゲートウェイのような基盤コンポーネントの場合、そのアーキテクチャ(Client → OpenResty Gateway → Upstream)は、すでに十分に検証された確立されたパターンです。理論上、軽量な転送のみを行うゲートウェイであれば、その性能オーバーヘッドは予測可能で極めて低いものであるはずです。
しかし、新バージョンのゲートウェイのベンチマークテストを実施した際、驚くべき性能劣化が確認されました。
- アップストリームサービスのベースライン (直接接続テスト): 94,706 QPS
- 新バージョンのゲートウェイ (リバースプロキシ): 6,301 QPS
性能が 93% も消失したのです。単純なリバースプロキシが、15 倍近くもの性能差を引き起こしていました。システムは一見すると正常で、エラーログもなく、設定も一般的な認識に合致していました。問題は明らかに、通常の観測範囲外に潜んでいたのです。
フレームグラフが暴いた接続ストーム:upstream keepalive の欠落
OpenResty XRay を使用してゲートウェイの CPU パフォーマンス分析を実施しました。その際、lj-c-on-cpu ツールを実行してフレームグラフを生成しました。
CPU フレームグラフでは、connect() と close() システムコールという、異常に幅の広い 2 つの領域が目立っていました。
高性能なネットワークサービスでは、これら 2 つは通常、ほとんど目立たない「バックグラウンドノイズ」であるべきです。それらが異常に顕著に現れていることは、致命的な問題を示唆しています。それは、アップストリーム接続が再利用されていないという点です。入ってくる各リクエストが、新しい TCP ハンドシェイクと切断をトリガーしているのです。同時接続数の多い環境下では、これはまさに「接続ストーム」に他なりません。カーネルは、実際のデータ処理ではなく、接続の作成と破棄に膨大な時間を費やしてしまいます。
OpenResty XRay が提供する完全な呼び出しスタックにより、問題の根本原因を迅速に特定できました。
- Connect パス:
ngx_http_proxy_handler→ngx_http_upstream_connect→connect() - Close パス:
ngx_http_upstream_finalize_request→ngx_close_connection→close()
診断は明確でした。upstream 設定ブロックに keepalive ディレクティブが欠落していたのです。これは一見些細に見えますが、本番環境で連鎖的な障害を引き起こしかねない設定上の不備でした。
直ちにアップストリームの keepalive を有効にしたところ、効果は劇的でした。パフォーマンスは 6,301 QPS から 21,923 QPS へと、3.48 倍に向上しました!
connect() と close() のスパイクは完全に解消されました。最初の性能ボトルネックは解決しましたが、最適化はまだ続きます。
残り 10% の差:ベースライン比較がコンパイラを指し示す
性能は大幅に回復したものの、ベテランエンジニアの直感は、事態がそれほど単純ではないことを示唆していました。現在のバージョンの性能(21,923 QPS)を以前の安定版(24,115 QPS)と比較したところ、依然として約 10% の性能差があることが確認されました。
この 10% の差はどこに起因するのでしょうか?これは 15 倍もの大きな差ほど衝撃的ではありませんが、究極の性能を追求するインフラストラクチャにとって、原因不明の性能劣化は決して許容できるものではありません。
- 現在のバージョンのフレームグラフは「より深く」なっている:呼び出しスタックの深さが安定版よりも明らかに増えています。
- 特定の関数呼び出しが増加している:例えば、
ngx_http_gunzip_body_filterのような関数がコールチェーンで頻繁に出現しますが、安定版ではほとんど見られません。
ngx_http_gunzip_body_filter のコードを確認します。
static ngx_int_t
ngx_http_gunzip_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
int rc;
ngx_uint_t flush;
ngx_chain_t *cl;
ngx_http_gunzip_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_gunzip_filter_module);
if (ctx == NULL || ctx->done) {
return ngx_http_next_body_filter(r, in);
}
}
理論的には、Nginx/OpenResty のフィルタチェーンは末尾呼び出し(tail-call)を多用しており、最適化コンパイル下では、これらの呼び出しはコンパイラによってフラット化され、ほとんど追加のオーバーヘッドがないはずです。しかし、現在のフレームグラフの形状は、コンパイラの最適化が無効になっている可能性を示唆しています。
-O0 デバッグビルドが 10% の性能を奪っていた
この手がかりをたどって、2 つのバージョンのビルドスクリプトを確認したところ、以下の事実が判明しました。
現在の開発バージョンは -O0 コンパイルオプションを使用しています。
その理由は、開発プロセスにおいて、ある問題のデバッグを容易にするため、一時的にコンパイル最適化を無効化したためです。しかし、機能開発完了後、最適化コンパイルオプションへの復元を失念していました。
-O0 は次のことを意味します:
- 関数インライン化の無効化 - 小さな関数呼び出しがインライン化されない
- 末尾呼び出し最適化の無効化 - 呼び出しスタックが深くなる
- 冗長コードの未削除 - より多くの不要な命令
- レジスタ使用効率の低下 - より多くのメモリアクセス
これら一見些細なコンパイラの動作の違いが積み重なることで、結果として、性能を 10% 削り取っていました。
コンパイルオプションを標準の -O2 に復元し、再ビルドしてデプロイしました。
その結果、性能は完全に回復し、安定バージョンと同水準に達しました。これにより、2 つの性能問題は完全に解決されました。
15 倍 QPS の乖離から学ぶ技術的振り返り
今回、94K QPS から 6K QPS への性能低下を調査した結果、現代の複雑なシステムにおける 2 つの深刻な課題が浮き彫りになりました。
「隠れたコンテキスト」のドリフト:性能はコードロジックのみならず、ビルド時および実行時環境の「隠れたコンテキスト」——例えばコンパイルオプション、カーネルパラメータ、ランタイム設定——にも大きく依存します。これらのコンテキストの変化はコードレビューでは見過ごされがちですが、壊滅的な結果につながる可能性があります。今回の
keepaliveの欠如と-O0コンパイルオプションがその最たる例です。観測の死角を突破する:なぜ動的トレーシングが不可欠なのか:問題がカーネルやコンパイラのレベルにまで深く関わるようになると、従来のログやメトリクスからは手がかりが得られないことがしばしばあります。本事例は、システムレベルのボトルネックに直面した際、OpenResty XRay のような深い洞察を提供するツールが必要であることを証明しています。
- エンドツーエンドサンプリング:アプリケーション層からカーネルまでを透過的に分析し、全体的なボトルネック(接続ストームなど)を迅速に特定します。
- 異常パターン認識:フレームグラフ(Flame Graph)を通じて、予期せぬconnect/closeパスを迅速に特定します。
- ベースライン比較:微細な差異(コンパイラの挙動など)を特定します。
性能問題は、明確なバグとして現れるのではなく、白黒つけられない、曖昧な効率の低下として現れることが多いものです。複雑なシステムアーキテクチャにおいて、直感や当てずっぽうに頼るのは非効率です。OpenResty XRay を通じて、アプリケーションロジックからカーネル呼び出しまでの完全な可観測性を構築することで、システム深くに隠された「グレーゾーン」を測定、分析、最適化する能力を獲得し、システムの長期的な安定性と卓越した性能を確保することが可能になります。
追いかけている症状がスループットの低下ではなくテールレイテンシの悪化である場合は、関連記事「OpenResty XRay による P99 レイテンシの診断」をご参照ください。
よくある質問
Nginx リバースプロキシ経由だとバックエンド直接接続よりなぜ遅いのですか?
本事例では、アップストリームサービスへの直接接続テストで 94,706 QPS だったのに対し、リバースプロキシ経由では 6,301 QPS しか出ませんでした。根本原因はアップストリーム接続が再利用されていなかったことです。upstream ブロックに keepalive ディレクティブがないと、リクエストごとに TCP のハンドシェイクと切断が発生します。upstream keepalive を有効化した直後、スループットは 3.48 倍の 21,923 QPS に向上しました。
Nginx リバースプロキシの CPU 消費箇所はどうやって特定できますか?
弊社は OpenResty XRay の lj-c-on-cpu ツールで、稼働中のゲートウェイの CPU フレームグラフを生成しました。異常に幅の広い 2 つの領域——connect() と close() システムコール——が接続ストームを一目で暴き、完全なコールスタック(ngx_http_proxy_handler → ngx_http_upstream_connect → connect())がコードパスを正確に特定しました。アプリケーションへの侵入は一切不要です。
-O0 のようなコンパイルオプションは本当に Nginx の性能に影響しますか?
影響します。本事例では -O0 デバッグビルドが実測で約 10% のスループット(21,923 対 24,115 QPS)を奪っていました。-O0 は関数のインライン化と末尾呼び出し最適化を無効にするため、コールスタックが深くなり冗長な命令が増えます。フレームグラフ上では ngx_http_gunzip_body_filter フレームの頻出として現れました。-O2 で再ビルドした結果、性能は完全に回復しました。
OpenResty XRay について
OpenResty XRay は動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、動作の問題、セキュリティの脆弱性を解決し、実行可能な提案を提供いたします。基盤となる実装において、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、様々な環境下で複数の異なるランタイムをサポートしております。
著者について
章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。
章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しております。彼は中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験があります。「エッジコンピューティング」、「動的トレーシング」、「機械プログラミング」 の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っております。世界中で 4000 万以上のドメイン名を持つユーザーを抱えるオープンソースプロジェクトのリーダーとして、彼は OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立いたしました。同社の主力製品である OpenResty XRay(動的トレーシング技術を利用した非侵入型の障害分析および排除ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ております。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJIT、GDB、SystemTap、LLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しております。
翻訳
英語版の原文と日本語訳版(本文)をご用意しております。読者の皆様による他の言語への翻訳版も歓迎いたします。全文翻訳で省略がなければ、採用を検討させていただきます。心より感謝申し上げます!



















