nginxのパラメータチューニングとh2o

(追記:タイトルが少々煽り気味な気がしたので微妙に変更しました。)

# h2oとnginxの性能比較

[nginx](http://nginx.org/en/docs/)よりも速いとされる[h2o](http://h2o.github.io/)ですが、実際に自分でもローカルでベンチマークを取ってみました。環境は以下の通りです。

 * EC2の`c4.8xlarge`インスタンス
 * gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-16)
 * Linux ip-172-31-13-40 3.14.35-28.38.amzn1.x86_64 #1 SMP Wed Mar 11 22:50:37 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
 * nginx-1.8.0
 * h2o-1.2.1-alpha1
 * [wrk](https://github.com/wg/wrk)(ベンチマーク)

# ベンチマークコマンド

実行するベンチマークコマンドは以下になります。なお、オプションはできるだけRequest/secが大きくなるように調整しています。

```
./wrk -c 100 -t 10 -d 10 http://127.0.0.1/
```

# サーバが返すレスポンス

サーバが返すレスポンスはnginxに付属している`index.html`です。サイズは612バイトです。

```html:index.html
$ cat /usr/local/nginx/html/index.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$
```

## sysctl.conf

また、あらかじめ下記のパラメータを追加してカーネルパラメータのチューニングをしておきます。(別にいらんかったかも)

```sysctl.conf
net.core.somaxconn=32768
net.core.netdev_max_backlog=32768
net.ipv4.tcp_max_syn_backlog=32768
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=10
net.core.rmem_max  = 16777216
net.core.wmem_max  = 16777216
net.ipv4.tcp_rmem  = 4096 349520 16777216
net.ipv4.tcp_wmem  = 4096 65536 16777216
net.ipv4.ip_local_port_range= 1024 65535
net.ipv4.tcp_timestamps = 0
```

# h2o

ではまずh2oのベンチマークからはじめます。

## h2o.conf(初期状態)

h2oの設定ファイル(初期状態)をこんな感じで定義します。

```yaml:h2o.conf
listen: 80
hosts:
  "127.0.0.1:80":
    paths:
      /:
        file.dir: /usr/local/nginx/html
```

## ベンチマーク(初期状態)

これでベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   265.78us  760.09us  17.04ms   96.66%
    Req/Sec    59.04k     7.09k  116.13k    71.93%
  5880641 requests in 10.10s, 4.56GB read
Requests/sec: 582269.87
Transfer/sec:    462.01MB
```

秒間58万リクエスト捌けました。

## スレッド数を調整する

h2oはデフォルトでCPUコア数と同じ数のワーカースレッドを起動します。
これはこれでよいのですが、今回のようにローカルでベンチマークを実行する場合、
CPUコア数と同じ数のワーカースレッドを起動してしまうとベンチマークプログラムとCPUを取り合ってしまうのでワーカスレッドの数を小さくしてみます。(ついでに最大コネクション数も引き上げます)

```yaml
num-threads: 16
num-name-resolution-threads: 1
max-connections: 10240
```

これで再度ベンチマークを実行すると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   158.90us  302.95us  14.96ms   97.11%
    Req/Sec    71.36k    10.12k  106.08k    70.63%
  7153711 requests in 10.10s, 5.54GB read
Requests/sec: 708328.89
Transfer/sec:    562.03MB
```

秒間70万リクエストを越えました。なお、`num-threads`はこれ以上増やしても`wrk`とCPUを取り合ってしまうせいか性能が上がりませんでした。

最終的なh2o.confはこちら。

```yaml:h2o.conf
num-threads: 16
num-name-resolution-threads: 1
max-connections: 10240
listen: 80
hosts:
  "127.0.0.1:80":
    paths:
      /:
        file.dir: /usr/local/nginx/html
```

# nginx

つづいてnginxです。

## nginx.conf(初期状態)

nginx.confの初期状態は以下になります。nginxをインストールした直後のnginx.confからエラー系の設定やコメントの除去、アクセスログをオフといったことを行っています。

```nginx:nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    access_log off;

    sendfile on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
```

## ベンチマーク(初期状態)

これでベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1/
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    45.04ms   58.61ms 206.35ms   78.77%
    Req/Sec     5.52k     2.77k   10.10k    62.83%
  535382 requests in 10.02s, 433.46MB read
Requests/sec:  53433.93
Transfer/sec:     43.26MB
```

秒間5万リクエスト。h2oの初期状態の10分の1程度です。

## ワーカプロセス数とコネクション数を上げる

nginxはh2oと違い、デフォルトのワーカ数が小さいのでこれをさきほどのh2o.confと同じく16にします。(また最大コネクション数も同じにします)

```nginx
worker_processes 16;
events {
    worker_connections 10240;
}
```

(追記: h2oのmax-connectoins ≒ nginxのworker_processes * worker_connectionsなので実際には差があるかも、という意見を頂いたので念のため追試してみましたが、コネクション数は変えてもスコアはほぼ同じでした。(h2oのmax-connectionsを10240、nginxのworker_connectionsを1024 or 640にして実施))

これでベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   827.38us    2.79ms  64.81ms   94.35%
    Req/Sec    62.79k     8.74k   77.95k    84.26%
  6308521 requests in 10.10s, 4.99GB read
Requests/sec: 624638.51
Transfer/sec:    505.72MB
```

秒間62万リクエスト。一気に伸びました。

## open_file_cacheでファイル情報をキャッシュ

nginxには一度オープンしたファイルのディスクリプタやサイズ、更新時刻、i-nodeといった情報をキャッシュするための`open_file_cache`というディレクティブがあります。

```nginx
# max: キャッシュの最大数
# inactive: アクセスがないキャッシュの有効期限
open_file_cache max=100 inactive=20s;
```

これを設定してベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   761.52us    6.12ms 138.30ms   98.88%
    Req/Sec    70.55k     8.20k   85.08k    87.69%
  7073264 requests in 10.10s, 5.59GB read
Requests/sec: 700324.14
Transfer/sec:    567.00MB
```

h2oと同じくらいのところまで来ました。

## accept_mutex_delay

`accept_mutex_delay`は`accept()`時のmutexの確保に失敗した際の待機時間を調整するためのディレクティブです。デフォルトだと500msでこれだと少し大きいので100msにします。

```nginx
accept_mutex_delay 100ms;
```

これでベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   401.34us    2.83ms  85.56ms   98.64%
    Req/Sec    72.03k     8.27k  105.77k    86.40%
  7216133 requests in 10.10s, 5.71GB read
Requests/sec: 714511.56
Transfer/sec:    578.48MB
```

h2oを越えました。

## tcp_nopush

`tcp_nopush`はnginxが利用するTCPソケットにTCP_CORK(Linux)、あるいはTCP_NOPSUH(BSD)のオプションをセットするディレクティブです。これを有効にした場合、簡単に言うとレスポンスヘッダとファイルの内容をまとめて送るようになるので送信するパケット数を最小化できます。

```nginx
tcp_nopush on;
```

これでベンチマークを走らせると、

```
Running 10s test @ http://127.0.0.1
  10 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   163.09us    0.99ms  36.45ms   99.12%
    Req/Sec    82.93k     9.71k  110.85k    69.90%
  8330029 requests in 10.10s, 6.59GB read
Requests/sec: 824799.77
Transfer/sec:    667.78MB
```

秒間82万リクエスト。すごいですね。最終的なnginx.confは以下になります。

```nginx:nginx.conf
worker_processes 16;

events {
    worker_connections  10240;
    accept_mutex_delay 100ms;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    access_log off;

    sendfile on;
    open_file_cache max=100 inactive=20s;
    tcp_nopush on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
```

# まとめ

h2oとnginxのベンチマークをローカルで取ってみました。実際やってみた感想としては

 * h2oはほとんど設定しなくてもかなりパフォーマンスが出るようになってる
 * nginxもチューニング次第でh2oに匹敵するパフォーマンスは出る

といった所でしょうか？一方で今回nginx側で有効にした`open_file_cache`や`tcp_nopush`相当の機能はまだh2oには実装されていません。この二つだけで秒間20万くらい違うのでh2oでもこれらが実装されるとガチガチにチューニングしたnginxよりも速くなりそうです。`open_file_cache`については進行中の[ブランチ](https://github.com/h2o/h2o/pull/138)があるようです。

ところで、`tcp_nopush`は本来`tcp_nodelay`(nginxではデフォルトで有効)とは反対の動きをするのですが、nginxはこの二つを組み合わせてもうまく動作するように実装されているようです。([NGINX OPTIMIZATION: UNDERSTANDING SENDFILE, TCP_NODELAY AND TCP_NOPUSH](https://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html))

# 参考

 * [nginx documentation](http://nginx.org/en/docs/)
 * [H2O](http://h2o.github.io/)
 * [G-WANはなぜ速いのか？をnginxと比べながら検証してみた](http://blog.nomadscafe.jp/2013/09/benchmark-g-wan-and-nginx.html)
 * [NGINX OPTIMIZATION: UNDERSTANDING SENDFILE, TCP_NODELAY AND TCP_NOPUSH](https://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html)