Nginx バッファチューニング: `client_body_buffer_size` と `proxy_buffer_size` の最適化

メモリを無駄にせず、不必要なディスクバッファリングを避けながら、Nginx のリクエストバッファとプロキシバッファをチューニングします。

Nginx バッファチューニング: client_body_buffer_sizeproxy_buffer_size の最適化

Nginx のバッファチューニングは、すべてのバッファを大きくすることではありません。どのデータをメモリに置き、どのデータを安全にディスクに溢れさせ、どのレスポンスを完全にバッファリングせずにストリーミングするかを決定することです。

混乱する点は、いくつかのディレクティブが似たように聞こえることです。client_body_buffer_size はクライアントからのリクエストボディに適用されます。proxy_buffer_sizeproxy_buffers は、Nginx がリバースプロキシとして動作する際にアップストリームサーバーから返されるレスポンスに適用されます。FastCGI には独自の対応するディレクティブがあります。間違った側をチューニングしても、何も改善されません。

良いチューニングセッションは症状から始まります:

  • Nginx のエラーログが一時ファイルに言及している。
  • アップロードや大きな POST リクエストが遅く感じられる。
  • リバースプロキシされた API レスポンスが負荷時に一時停止する。
  • ワーカープロセスが予想よりも多くのメモリを使用している。
  • 大きなレスポンスヘッダーがアップストリームエラーを引き起こす。

これらの症状はすべて同じ修正方法があるわけではありません。バッファを大きくするとディスク I/O を減らせますが、リクエストごとのメモリプレッシャーも増加します。忙しいサイトでは、リクエストごとに小さく見える設定でも、数千の同時接続が掛け合わされると大きくなります。

リクエストボディのバッファリング: client_body_buffer_size

client_body_buffer_size は、Nginx がクライアントリクエストのボディを読み取る際に使用するバッファを制御します。フォーム送信、JSON POST ボディ、アップロードなどを考えてください。リクエストボディがこのバッファより大きい場合、Nginx はその一部を client_body_temp_path の下の一時ファイルに書き込む可能性があります。

基本的な設定は次のようになります:

http {
    client_body_buffer_size 128k;
}

これは最大許容アップロードサイズを変更しません。それは client_max_body_size によって制御されます:

server {
    client_max_body_size 20m;
    client_body_buffer_size 128k;
}

これら二つのディレクティブは異なる質問に答えます。client_max_body_size は「Nginx がリクエストを拒否する前に、リクエストはどのくらいの大きさまで許容されるか?」と言います。client_body_buffer_size は「Nginx が一時ファイルを使用する前に、リクエストボディのどのくらいをメモリに保持すべきか?」と言います。

ほとんどのリクエストボディが 32 KB 未満で、一部が 200 KB である JSON API の場合、128k のバッファは過剰にならずに一時ファイルへの書き込みを減らすことができます。50 MB の動画を受け入れるファイルアップロードサービスの場合、client_body_buffer_size 50m をグローバルに設定するのは悪いトレードオフです。ディスクバッファリングが許容される可能性があるワークロードに対して、あまりにも多くのメモリを予約することになります。

エラーログで次のような手がかりを確認してください:

[warn] a client request body is buffered to a temporary file

その警告は自動的に失敗を意味するわけではありません。Nginx がボディがインメモリバッファを超えたことを伝えています。大きなアップロード中にたまに発生するのであれば問題ありません。通常の API リクエストで常に発生する場合は、バッファをチューニングするか、大きなペイロードを送信するクライアントを修正してください。

プロキシレスポンスのバッファリング: proxy_buffer_sizeproxy_buffers

Nginx がアップストリームアプリにプロキシする場合、レスポンスバッファリングは通常デフォルトで有効になっています。Nginx はアップストリームのレスポンスをすばやく読み取り、バッファに保存し、クライアントに送信します。レスポンスがメモリバッファに収まらない場合、その一部が一時ファイルに書き込まれる可能性があります。公式の Nginx プロキシモジュールのドキュメントでは、この動作を説明し、一時ファイルの書き込みを proxy_max_temp_file_sizeproxy_temp_file_write_size に関連付けています。

最初のバッファは proxy_buffer_size によって制御されます:

proxy_buffer_size 16k;

このバッファはレスポンスヘッダーとレスポンスの最初の部分を処理します。アップストリームが多くの Cookie、大きな認証ヘッダー、または過度に大きなトレーシングメタデータなど、大きなヘッダーを送信する場合、「upstream sent too big header」のようなエラーが表示されることがあります。その場合、proxy_buffer_size がしばしば確認すべきディレクティブです。

残りのレスポンスバッファは proxy_buffers によって制御されます:

proxy_buffers 8 32k;

これは、プロキシされたレスポンスデータ用に 32 KB のバッファが 8 つあることを意味します。すべてのリクエストが常に最初から最後まで全容量を消費するわけではありませんが、負荷がかかった状態ではこれらをリクエストごとのメモリ設定として扱う必要があります。

一般的なリバースプロキシブロックは次のようになります:

location /api/ {
    proxy_pass http://app_backend;

    proxy_buffering on;
    proxy_buffer_size 16k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;
}

これを盲目的にコピーしないでください。小さな HTML サイト、JSON API、ファイルダウンロードサービスでは、それぞれ異なるニーズがあります。

一時ファイルと proxy_max_temp_file_size

プロキシされたレスポンスのバッファリングが有効で、レスポンスが設定されたバッファより大きい場合、Nginx はその一部をディスクに書き込む可能性があります。proxy_max_temp_file_size はその一時ファイルのサイズを制限します。Nginx のデフォルトは一般的に 1024m として文書化されており、0 に設定するとプロキシされたレスポンスの一時ファイルへのバッファリングが無効になります。

location /api/ {
    proxy_pass http://app_backend;
    proxy_buffering on;
    proxy_max_temp_file_size 50m;
}

0 は注意して使用してください:

proxy_max_temp_file_size 0;

これは大きなレスポンスが無料になるという意味ではありません。Nginx がバッファリングされたプロキシレスポンスに一時ファイルを使用しないことを意味します。レスポンス、クライアントの速度、バッファリング動作によっては、より多くのメモリ、異なるバッファリング設定、またはストリーミング設計が必要になる場合があります。

Nginx のドキュメントからの重要なニュアンスの一つ:proxy_max_temp_file_size の制限は、キャッシュされるかディスクに保存されるレスポンスには適用されません。proxy_cache または proxy_store を使用する場合は、それらの設定を別途確認してください。

proxy_buffering off がより良い答えである場合

時にはレスポンスをまったくバッファリングすべきでない場合もあります。ストリーミングエンドポイント、サーバー送信イベント、大きなダウンロード、長時間実行される出力は、多くの場合プロキシバッファリングを無効にすることでより良く動作します:

location /events/ {
    proxy_pass http://app_backend;
    proxy_buffering off;
}

バッファリングをオフにすると、Nginx は完全なレスポンスを収集しようとする代わりに、受信した時点でアップストリームのレスポンスをクライアントに渡します。proxy_buffer_size は依然として重要です。Nginx がアップストリームから受信したデータ用のバッファを必要とするためですが、proxy_buffers と一時ファイルの動作は中心ではなくなります。

トレードオフを理解せずにグローバルにバッファリングをオフにしないでください。バッファリングはアップストリームサーバーを遅いクライアントから保護します。クライアントがゆっくりダウンロードし、バッファリングがオフの場合、アップストリーム接続がより長く占有される可能性があります。

PHP および類似のセットアップ用の FastCGI バッファ

Nginx が PHP-FPM と通信する場合、proxy_* ディレクティブは必要ありません。FastCGI は一致する名前を使用します:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    fastcgi_buffer_size 16k;
    fastcgi_buffers 8 32k;
}

同じロジックが適用されます:最初のバッファはヘッダーと初期データを処理し、バッファ配列はより多くのレスポンスコンテンツを処理します。PHP が大きなヘッダーを送信する場合、fastcgi_buffer_size に注意が必要かもしれません。PHP が生成した大きなページがディスクにあふれる場合は、fastcgi_buffers とワークロードを確認してください。

推測せずにチューニングする方法

まずログを確認します:

sudo tail -f /var/log/nginx/error.log

リクエストボディやアップストリームのレスポンスが一時ファイルにバッファリングされているという警告を探します。次に、実際のサイズを測定します。API の場合、アクセスログ、アプリケーションログ、または監視システムからリクエストとレスポンスのサイズをサンプリングします。アップロードの場合、メタデータリクエストをファイルアップロードエンドポイントから分離します。それらは同じ前提を共有すべきではありません。

最も狭いコンテキストでチューニングします。次のようにする代わりに:

http {
    client_body_buffer_size 10m;
}

アップロードエンドポイントのみに対して次のようなものを優先します:

location /upload/ {
    client_max_body_size 50m;
    client_body_buffer_size 512k;
    proxy_pass http://upload_backend;
}

たまに 1 MB のレスポンスがある JSON API の場合、その API ロケーションのみをチューニングするかもしれません:

location /reports/ {
    proxy_pass http://report_backend;
    proxy_buffering on;
    proxy_buffer_size 32k;
    proxy_buffers 16 64k;
    proxy_max_temp_file_size 20m;
}

次に、最悪のケースを計算します。1,000 の同時リクエストがそれぞれ数百 KB のレスポンスバッファを使用できる場合、それは実際のメモリです。ワーカープロセス、TLS、アップストリーム接続、キャッシュ、OS ページキャッシュ、および他のサービスのための余地を残してください。

設定を検証し、システムを監視する

変更のたびに:

sudo nginx -t
sudo systemctl reload nginx

その後、メモリ、ディスク I/O、およびエラーログを監視します。構文テストが成功しても、ファイルが解析されることだけが証明されます。値が適切であることは証明されません。

有用なチェックには以下が含まれます:

free -h
iostat -xz 1
sudo tail -f /var/log/nginx/error.log

一時ファイルの警告が消えたがメモリプレッシャーが上昇した場合、やり過ぎです。メモリが健全で、影響を受けるワークロード中にディスク I/O が低下した場合、変更はおそらく役に立ちました。

Nginx のバッファチューニングは、ローカルで、測定され、実際のトラフィックに関連付けられている場合に最も効果的です。レイテンシを節約できる場合は通常のリクエストをメモリに保持し、本当に大きなアップロードやダウンロードは適切なパスを使用させ、すべての接続が最大のエッジケースに対して支払いをするグローバル設定を避けてください。