このエントリは nginx Advent Calendar 2014 の7日目です。
1. はじめに
Webサーバを運用する時に気になる設定の1つとして keep-alive の設定があります。 今まで調べようと思ってできていなかったので、nginx Advent Calendar 2014 の勢いに乗って調べてみました。このエントリの概要は以下の通り。
- クライアント <-> Nginx 間の keep-alive の設定とその動作検証
- Nginx <-> 上位サーバ間の keep-alive の設定とその動作検証
検証環境は下記の通りです。
- OS: CentOS 7.0.1406
- Nginx: 1.7.8
- 今回は 1.1.4 よりもバージョンが新しい Nginx を想定してます。
- PHP: 5.4.16
- ブラウザ: FirefoxDeveloperEdition 36.0a2
- network.http.keep-alive.timeout = 115 (デフォルト)
- network.tcp.keepalive.enabled = true(デフォルト)
アプリケーション構成図は以下の通りです。Nginx がリバースプロキシとなり、PHPの処理をバックエンドの2つの PHP-FPM に対して交互に振り分けます。
Nginx の初期設定は以下の通りです。http コンテキストはインストール時の設定のままです。
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
upstream php-fpm {
server 127.0.0.1:9000;
server 127.0.0.1:9001;
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
location ~ .php$ {
fastcgi_pass php-fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
location / {
index index.php;
}
}
2. keep-alive
keep-aliveは、1回のTCP接続で複数のHTTPリクエストを処理する機能です。HTTP 1.1 でサポートされた機能で、Webサーバとの間で確立した接続が転送終了後も維持され、2回目以降のリクエストではこの接続をそのまま使用できるため、ファイルごとにTCP接続を開くためのCPU負荷や時間が節約できることなどがメリットとして挙げられます。
しかし、コネクションを維持することで不要なコネクションの滞留やコネクションプールによるメモリ使用量の増加など、状況によってはデメリットもあります。そのため、keep-alive を有効にするか無効にするか、またそのタイムアウトをどれくらいに設定するかどうかは、システムごとに慎重に設定する必要があります。
2.1 keep-alive コネクションの設定
Nginx において、keep-alive のタイムアウト設定を行うディレクティブは、 keepalive_timeout
です。引数は2つあります。
1つ目は keep-alive コネクションのタイムアウト値です。0 を設定した場合は keep-alive 接続が無効となります。デフォルト値は 75s です。
2つ目は HTTP レスポンスヘッダ内の Keep-Alive フィールドにおけるタイムアウト値です。第2引数はデフォルトでは設定されていません。
また、keepalive_requests
ディレクティブを指定することで、1つの keep-alive コネクションがクローズするまでに発行可能なリクエスト数を指定することができます。
ここから先は keepalive_timeout の値を変化させた時の挙動を調べていきます。具体的には、サイト表示後にレスポンスヘッダとサーバ側のコネクションステータスを確認しました。サイト表示の際は index.php へアクセスをしました。index.php の中身は echo 関数のみです。
2.1.1 keepalive_timeout が 65 の場合
初期インストール時点で、すでに http コンテキストに対して下記の設定がされていたので、まずは設定をいじらずに検証します。
keepalive_timeout 65;
- レスポンスヘッダ
HTTP/1.1 200 OK
Server: nginx/1.7.8
Date: Sat, 06 Dec 2014 14:46:12 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.4.16
ヘッダに Connection: keep-alive
が付与されていました。
※ HTTP 1.0, HTTP 1.1 と keep-alive
HTTP 1.0 では、リクエストヘッダ及びレスポンスヘッダに Connection: keep-alive
が付与されている場合にのみ有効となります。
HTTP 1.1 では、keep-alive が標準で有効となります。サーバ側で無効にする場合は、Connection: close
をヘッダに付与します。
- コネクションステータス
$ while :; do date; ss -natp -o state established -o state time-wait 'sport = :80'; sleep 1;
Sat Dec 6 23:08:24 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:51277 users:(("nginx",10199,3))
...
Sat Dec 6 23:09:28 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:51277 users:(("nginx",10199,3))
Sat Dec 6 23:09:29 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
TIME-WAIT 0 0 192.168.33.10:80 192.168.33.1:51611 timer:(timewait,59sec,0)
65秒間 ESTABLISHED
が継続し、その後コネクションステータスが TIME_WAIT
へ移行しました。設定の意図通りに動作しています。
※ TIME_WAIT
コネクションの滞留時間が 60 秒間なのは、OS 側の設定によるものです。
上記の結果から、想定通りの動作が確認できました。
2.1.2 keepalive_timeout が 0 の場合
- レスポンスヘッダ
HTTP/1.1 200 OK
Server: nginx/1.7.8
Date: Sat, 06 Dec 2014 14:54:51 GMT
Content-Type: text/html
Connection: close
X-Powered-By: PHP/5.4.16
Connection: close
となっている。これにより、HTTP 1.0, HTTP 1.1 の両方で keep-alive 接続が無効になります。
上記の結果から、想定通りの動作が確認できました。
2.1.3 keepalive_timeout が 65 20 の場合
- レスポンスヘッダ
HTTP/1.1 200 OK
Server: nginx/1.7.8
Date: Sat, 06 Dec 2014 15:22:18 GMT
Content-Type: text/html
Connection: keep-alive
Keep-Alive: timeout=20
X-Powered-By: PHP/5.4.16
上記に記載したkeepalive_timeout 65
の場合と比較して、Keep-Alive: timeout=20
が追加されました。
- コネクションステータス
Sun Dec 7 00:22:40 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:52192 users:(("nginx",10199,3))
...
Sun Dec 7 00:22:59 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:52192 users:(("nginx",10199,3))
Sun Dec 7 00:23:00 JST 2014
keepalive_timeout の第2引数の秒数を第1引数よりも短い時間にしたところ、(今回は20秒間で)ESTABLISHED
コネクションが消えました。また、TIME_WAIT
コネクションが残りませんでした。。。なぜ…
上記の結果から、第2引数の秒数が優先され、サーバ側のコネクションが時間経過後消滅することが確認できました。
Nginx のドキュメントにもありますが、Keep-Alive フィールドを認識するかどうかはクライアントによってことなるため、一概に上記の結果になるとは限らないことに注意してください。
The “keep-alive: timeout=time” header field is recognized by Mozilla and Konqueror. MSIE closes keep-alive connections by itself in about 60 seconds.
2.1.4 keepalive_timeout が 20 65 の場合
- レスポンスヘッダ
HTTP/1.1 200 OK
Server: nginx/1.7.8
Date: Sat, 06 Dec 2014 18:48:38 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Keep-Alive: timeout=65
X-Powered-By: PHP/5.4.16
Keep-Alive: timeout=65
が追加されました。
- コネクションステータス
Sun Dec 7 03:45:41 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:53748 users:(("nginx",16495,3))
Sun Dec 7 03:46:01 JST 2014
TIME-WAIT 0 0 192.168.33.10:80 192.168.33.1:53748 timer:(timewait,57sec,0)
20 秒経過後に TIME_WAIT
に移行しました。
よって、第1引数の秒数が優先され、サーバ側のコネクションが時間経過後に TIME_WAIT
となることが確認できました。
2.2 プロキシサーバと上位サーバ間の keep-alive について
これまでの設定だけでは Nginx と PHP-FPM の間において keep-alive 接続が行われません。実際、1.1.1 の検証時に PHP-FPM に関するコネクションを確認したところ、レスポンスを返し終えた直後から TIME_WAIT となっていました。
$ while :; do date; ss -natp -o state established -o state time-wait 'sport = :80 or sport = :9000 or sport = :9001'; sleep 1; done
State Recv-Q Send-Q Local Address:Port Peer Address:Port
TIME-WAIT 0 0 127.0.0.1:9000 127.0.0.1:43393 timer:(timewait,59sec,0)
ESTAB 0 0 192.168.33.10:80 192.168.33.1:52611 users:(("nginx",10199,3))
今回の構成のように、プロキシサーバと上位サーバが共存している場合は、TCP/IP ではなく Unix ドメインソケット通信を用いることで、TIME_WAIT の滞留を防ぐことができます。
しかし、それらが別々のホストの場合はTCP/IPを利用する必要があります。この場合、プロキシサーバと上位サーバ間でも keep-alive を有効にしたくなるケースは十分考えられます。 ということで、ここではプロキシサーバと上位サーバが TCP/IP によって通信を行う場合を想定して keep-alive を設定してみます。
2.2.1 Nginx と上位サーバ間の keep-alive 設定
Nginx と上位サーバ間で keep-alive を有効にするためには、FastCGIではなくHTTPを用いる必要があります。(keep-alive は HTTP の機能なので。) そのため、PHP-FPM の前段にHTTPサーバを立てたり、HTTP を話すことができるアプリケーションサーバを立てたりするが必要です。
今回は、PHP-FPM を Apache + mod_php に切り替えてみました。構成は以下のようになります。
Nginx の設定は以下の通りです。
upstream apache {
server 127.0.0.1:9000;
server 127.0.0.1:9001;
keepalive 32;
}
location ~ .php$ {
proxy_pass http://apache;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
設定のポイントは以下の通りです。
- upstream コンテキストにおいて keepalive ディレクティブを追加
- → 上位サーバとkeep-alive コネクションの最大接続数を設定するため
- fastcgi_pass ではなく proxy_pass を用いる
- → HTTP 通信をするため
- 通信の際、HTTP 1.1 を用いる
- → keep-alive をデフォルトで有効にするため
- リクエストヘッダ送信時、クライアントから渡ってきた Connection フィールドの値を空にする
- → クライアントからのコネクションの属性を上位サーバへプロキシするのを防ぐため
2.2.2 Nginx と上位サーバ間の keep-alive の検証
- コネクションステータス
Sun Dec 7 02:37:12 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:53189 users:(("nginx",9836,3))
ESTAB 0 0 ::ffff:127.0.0.1:9000 ::ffff:127.0.0.1:43476 timer:(keepalive,120min,0) users:(("httpd",9777,11))
...
Sun Dec 7 02:37:17 JST 2014
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 192.168.33.10:80 192.168.33.1:53189 users:(("nginx",9836,3))
TIME-WAIT 0 0 ::ffff:127.0.0.1:9001 ::ffff:127.0.0.1:50878 timer:(timewait,59sec,0)
なぜか 5 秒で上位サーバとのコネクションが TIME_WAIT
になったのですが、よくよく考えれば、Apache の KeepAliveTimeout のデフォルト値は 5 秒なので当然でした。Apache の設定を修正して意図通りの動作を確認しました。
また、keepalive_timeout(Nginx) と KeepAliveTimeout(Apache) の値を変化させて検証しましたがいずれも想定通りの動作をしました。さらに、これらとともに keepalive_timeout の第2引数を指定してみましたが、ESTABLISHED
コネクションが TIME_WAIT
に移行せず消える現象は変わりませんでした。
3 まとめ
3.1 クライアント <-> Nginx 間の keep-alive について
-
Nginx の keepalive_timeout はおおむね意図通りの動作をした
-
引数を2つ設定した場合は、第1引数と第2引数のどちらか小さい値が実際のタイムアウト値となった
- 第1引数より第2引数の方が小さい値の場合、第2引数の秒数後にESTABLISHED のコネクションが TIME_WAIT にならないまま消えた
- 第1引数より第2引数の方が大きい値の場合、第1引数の秒数後にESTABLISHED のコネクションが TIME_WAIT になった
-
3.2 Nginx <-> 上位サーバ間の keep-alive について
-
keepalive_timeout を設定しただけでは、上位サーバとの keep-alive 設定はされない
-
上位サーバと keep-alive 設定をする場合は、当然 HTTP 通信をする必要がある
- fastcgi_pass ではなく、proxy_pass を用いる
-
上位サーバと keep-alive 接続をする場合は、上位サーバ側の keep-alive タイムアウトの設定も調整する必要がある
-
クライアント <-> Nginx 間とNginx <-> 上位サーバ間は、keep-alive の動作が互いに影響しなかった
4 参考URL
- Alphabetical index of directives
- Hypertext Transfer Protocol — HTTP/1.1
- Keep-Alive: ヘッダー (HTTP)
- マスタリング Nginx
5 余談
記事を書く前は、Nginx のタイムアウトに関するもろもろについて書こうとしていたのですが、最初に着手した keep-alive のタイムアウトの検証でだいぶ盛り上がってしまい、力尽きたので keep-alive の記事ということにしました。。他のタイムアウトについてもあとで書くはず。。
ちなみに、keep-alive が盛り上がる前、まだ時間に余裕があった時にまとめたのが下の表です。
5.1 タイムアウトに関するディレクティブ
ngx_stream_module(商用である NGINX Plus のみ利用可), ngx_http_scgi_module, ngx_mail_core_module に関するディレクティブは省いています。
ディレクティブ名 | 説明 | デフォルト値 | 備考 |
---|---|---|---|
auth_http_timeout | Nginxと認証サーバとの通信のタイムアウト値 | 60s | ngx_mail_ssl_module |
client_body_timeout | クライアントからリクエストボディを読み取る歳のタイムアウト値を指定する。タイムアウトとなった場合、クライアントに 408 エラーが返る | 60s | |
client_header_timeout | クライアントからのヘッダ全体を読み取る際のタイムアウト値。タイムアウトとなった場合、クライアントには 408 エラーが返る。 | 60s | |
fastcgi_cache_lock_timeout | リクエストが、キャッシュにエントリが作成されるか、fastcgi_cache_lockが開放されるのを待機する時間 | 5s | v1.1.12 から |
fastcgi_connect_timeout | FastCGIサーバへのリクエストが行われた際に、Nginxがコネクションの受け入れを待機する最大時間 | 60s | |
fastcgi_next_upstream_timeout | 次のサーバへリクエストを渡す際のタイムアウト値。0 にすることでこの制限を無効にする。 | 0 | v1.7.5 から |
fastcgi_read_timeout | FastCGIサーバからの読み取りのタイムアウト時間 | 60s | |
fastcgi_send_timeout | FastCGIサーバに対する書き込みのタイムアウト時間 | 60s | |
keepalive_timeout | 引数は2つ。1つ目のパラメータはサーバ側で開かれたままになるkeep-aliveクライアントコネクションのタイムアウトを設定する。0の値はkeep-alive接続を無効にします。任意の2つ目のパラメータは”keep-alive: timeout=time” 応答ヘッダフィールドの値を設定します。 | 75s | 2つ目の引数は任意 |
lingering_timeout | lingering_close と組み合わせることで、Nginx がコネクションをクローズする前に、追加で送信されたデータを待ち受ける時間を指定する | 5s | |
reset_timedout_connection | 本ディレクティブが有効な場合、タイムアウトしたコネクションは即座にリセットされ、関連するメモリがすべて開放される。デフォルトでは、ソケットが FIN_WAIT1 の状態のまま残る。keep-alive コネクションは常にデフォルトの動作を行う。 | off | |
proxy_cache_lock_timeout | リクエストが、キャッシュにエントリが作成されて proxy_cache_lock が開放されるまで待機する時間 | off | v1.1.12 から |
proxy_connect_timeout | 上位サーバへのリクエストを行う際に、Nginx がコネクションの確立を待つタイムアウト値 | 60s | ngx_http_proxy_module |
proxy_next_upstream_timeout | 次のサーバへリクエストを渡す際のタイムアウト値。0 にすることでこの制限を無効にする。 | 0 | v1.7.5から |
proxy_read_timeout | 上位サーバからの読み取り操作のタイムアウト値 | 60s | |
proxy_send_timeout | 上位サーバへの書き込み操作のタイムアウト値 | 60s | |
proxy_timeout | メールプロキシのタイムアウト値 | 24h | ngx_mail_ssl_module |
resolver_timeout | 名前解決のタイムアウト値 | 30s | |
resolver_timeout | DNS関連の操作に関するタイムアウト値 | 30s | ngx_mail_ssl_module |
send_timeout | 書き込み処理に対するクライアントからのレスポンスタイムのタイムアウト値 | 60s | |
ssl_session_timeout | クライアントがキャッシュに格納されている SSL ディレクティブを再使用できる時間 | 5m | ngx_http_ssl_module, ngx_mail_ssl_module |
timeout | バックエンドサーバへのコネクションを終了させる前に Nginx が待機する時間 | 60s | ngx_mail_core_module |