(追記:タイトルが少々煽り気味な気がしたので微妙に変更しました。)
h2oとnginxの性能比較
nginxよりも速いとされるh2oですが、実際に自分でもローカルでベンチマークを取ってみました。環境は以下の通りです。
- 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(ベンチマーク)
ベンチマークコマンド
実行するベンチマークコマンドは以下になります。なお、オプションはできるだけRequest/secが大きくなるように調整しています。
./wrk -c 100 -t 10 -d 10 http://127.0.0.1/
サーバが返すレスポンス
サーバが返すレスポンスはnginxに付属しているindex.html
です。サイズは612バイトです。
$ 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
また、あらかじめ下記のパラメータを追加してカーネルパラメータのチューニングをしておきます。(別にいらんかったかも)
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の設定ファイル(初期状態)をこんな感じで定義します。
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を取り合ってしまうのでワーカスレッドの数を小さくしてみます。(ついでに最大コネクション数も引き上げます)
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はこちら。
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からエラー系の設定やコメントの除去、アクセスログをオフといったことを行っています。
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にします。(また最大コネクション数も同じにします)
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
というディレクティブがあります。
# 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にします。
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)のオプションをセットするディレクティブです。これを有効にした場合、簡単に言うとレスポンスヘッダとファイルの内容をまとめて送るようになるので送信するパケット数を最小化できます。
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は以下になります。
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
については進行中のブランチがあるようです。
ところで、tcp_nopush
は本来tcp_nodelay
(nginxではデフォルトで有効)とは反対の動きをするのですが、nginxはこの二つを組み合わせてもうまく動作するように実装されているようです。(NGINX OPTIMIZATION: UNDERSTANDING SENDFILE, TCP_NODELAY AND TCP_NOPUSH)