[Nginx] keep-alive の設定と検証

December 07, 2014

このエントリは 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 connection

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))

no keep-alive

今回の構成のように、プロキシサーバと上位サーバが共存している場合は、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 に切り替えてみました。構成は以下のようになります。

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

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

Profile picture

Written by Narimichi Takamura (@nari_ex) who works at Topotal as CEO. He love engineering and fighting game.