nginx〜go〜mongoDBを使ったREST-WebAPIサーバのパフォーマンスチューニング 第2回 nginx基本チューニング(有効なのはtcp_nopushだけ）

この記事ではAWS ec2上のCentOS7にインストールしたnginx 1.10.2を使っています。

連載一覧
[第1回 REST-WebAPIの特徴に合わせてチューニングしよう] (http://qiita.com/toast-uz/items/828bbb48d58eb65f1f9f)
[第2回 nginx基本チューニング(有効なのはtcp_nopushだけ）] (http://qiita.com/toast-uz/items/48754a95aa0c60db67f5)
[第3回 バックエンドはコネクションプーリングで] (http://qiita.com/toast-uz/items/b459100189efb995b9c5)
[第4回 DB接続もコネクションプーリングで] (http://qiita.com/toast-uz/items/9c1b97bee8f4a7c13bfc)

今回は、nginxの基本チューニングを、項目ごとに実施して効果を確かめます。最終的に、効果がありそうなものは、`tcp_nopush on;` だけでした。遅延印加の環境では、絶対性能はそれほど大きくありませんので、超高性能下でのチューニングノウハウは、必ずしも有効ではないことが分かります。

以降の連載では`tcp_nopush on;`を前提に検証を進めます。

チューニング項目については、[nginxのパラメータチューニングとh2o] (http://qiita.com/cubicdaiya/items/235777dc401ec419b14e)を大変参考にさせていただきました。

# 1. チューニング項目と測定結果

測定は、ec2のt2.microサーバ2台の間で、遅延印加100msの環境で同時1万接続について行いました。詳細は、[第1回] (http://qiita.com/toast-uz/items/828bbb48d58eb65f1f9f)を参照ください。
測定コマンドは、`$ ab -c 10000 -n 1000000 -r http://172.31.20.18/` です。測定誤差を減らすために、十分大きな試行回数としています。

なお、本環境ではabでごく一部のリクエストにおいてエラーが発生しますが、-rオプションにより、エラーを無視して最後まで試行するようになります。エラー発生比率は0.1%以下であり、無視してよいでしょう。エラーに関する考察は項4を参照ください。

| チューニング項目 | Requests/sec | エラー発生数（100万回につき） |効果|
|:-----------|------------:|:------------:|:---:|
| 未チューニング |     2,211 |     363|-|
| accept_mutex_delay 100ms; |  2,185 |  357 |なし|
| tcp_nopush on;    |  2,431 |  174 |<font color="Red">あり</font>|
| access_log off;   |   2,196 |  711 |なし|
| カーネルチューニング |   2,130 |  558 |なし|

# 2. 各項目の詳細解説

nginx.confのパラメータについては、[コアの機能] (http://mogile.web.fc2.com/nginx/ngx_core_module.html)、[ngx_http_core_module モジュール](http://mogile.web.fc2.com/nginx/http/ngx_http_core_module.html) を参照してください。

未チューニングでも、同条件での連載第1回の値（1,515）よりも顕著に改善していました。この原因は不明です。

# 2.1. accept_mutex_delay 100ms;

nginx.confの設定項目の１つです。結果は効果なし、不採用です。

「他のworkerプロセスが新しい接続を受け付けている場合に、workerプロセスが新しい接続の受け付けを再開するまでの最大の期間を指定します。」と説明されています。workerが複数の場合に効果があり、1つの場合（今回の測定条件）では効果が無さそうです。実測では効果は認められませんでした。

workerをCPUコア数に合わせて複数化していけば、本設定は効果がありそうに思います。しかし、nginx 1.11.3からは、前提となるmutex設定がデォフルトoffになるとのことで、本パラメータに頼るのは避けておいた方がよいでしょう。

# 2.2. tcp_nopush on;

nginx.confの設定項目の１つです。結果は効果あり、採用です。

「オプションを有効にすることにより、応答ヘッダの送信とファイルの開始を一つのパケットですることができます:一杯になったパケットでファイルを送ることができます。」と説明されています。

実際に、tcp_nopushを切り替えて、tcpdumpをとってみます。

+ testクライアント: ```$ ab -n 1 http://172.31.20.18/```
+ webサーバ: ```# tcpdump -n host 172.31.16.182```

以下のように、tcp_nopush off;で11パケットだったものが、tcpu_nopush on;で9パケットに減っていることが分かります。

遅延印加の環境では、パケット送受信のオーバーヘッドが大きいため、本設定によりパケット送受信の回数が減ることで、性能が改善したのでしょう。

tcp_nopush off;

```:tcpdump
06:24:08.743613 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [S], seq 1176315219, win 26883, options [mss 8961,sackOK,TS val 214540 ecr 0,nop,wscale 7], length 0
06:24:08.743650 IP 172.31.20.18.http > 172.31.16.182.54076: Flags [S.], seq 3234668064, ack 1176315220, win 26847, options [mss 8961,sackOK,TS val 216502 ecr 214540,nop,wscale 7], length 0
06:24:08.844348 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [.], ack 1, win 211, options [nop,nop,TS val 214641 ecr 216502], length 0
06:24:08.844381 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [P.], seq 1:81, ack 1, win 211, options [nop,nop,TS val 214641 ecr 216502], length 80
06:24:08.844391 IP 172.31.20.18.http > 172.31.16.182.54076: Flags [.], ack 81, win 210, options [nop,nop,TS val 216603 ecr 214641], length 0
06:24:08.844830 IP 172.31.20.18.http > 172.31.16.182.54076: Flags [P.], seq 1:234, ack 81, win 210, options [nop,nop,TS val 216604 ecr 214641], length 233
06:24:08.844853 IP 172.31.20.18.http > 172.31.16.182.54076: Flags [FP.], seq 234:846, ack 81, win 210, options [nop,nop,TS val 216604 ecr 214641], length 612
06:24:08.945600 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [.], ack 234, win 219, options [nop,nop,TS val 214742 ecr 216604], length 0
06:24:08.945621 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [.], ack 847, win 228, options [nop,nop,TS val 214742 ecr 216604], length 0
06:24:08.945626 IP 172.31.16.182.54076 > 172.31.20.18.http: Flags [F.], seq 81, ack 847, win 228, options [nop,nop,TS val 214742 ecr 216604], length 0
06:24:08.945635 IP 172.31.20.18.http > 172.31.16.182.54076: Flags [.], ack 82, win 210, options [nop,nop,TS val 216704 ecr 214742], length 0
```

tcp_nopush on;

```:tcpdump
06:21:21.594679 IP 172.31.16.182.54074 > 172.31.20.18.http: Flags [S], seq 3777182627, win 26883, options [mss 8961,sackOK,TS val 47397 ecr 0,nop,wscale 7], length 0
06:21:21.594716 IP 172.31.20.18.http > 172.31.16.182.54074: Flags [S.], seq 2935256801, ack 3777182628, win 26847, options [mss 8961,sackOK,TS val 49353 ecr 47397,nop,wscale 7], length 0
06:21:21.695442 IP 172.31.16.182.54074 > 172.31.20.18.http: Flags [.], ack 1, win 211, options [nop,nop,TS val 47497 ecr 49353], length 0
06:21:21.695490 IP 172.31.16.182.54074 > 172.31.20.18.http: Flags [P.], seq 1:81, ack 1, win 211, options [nop,nop,TS val 47497 ecr 49353], length 80
06:21:21.695505 IP 172.31.20.18.http > 172.31.16.182.54074: Flags [.], ack 81, win 210, options [nop,nop,TS val 49454 ecr 47497], length 0
06:21:21.695841 IP 172.31.20.18.http > 172.31.16.182.54074: Flags [FP.], seq 1:846, ack 81, win 210, options [nop,nop,TS val 49455 ecr 47497], length 845
06:21:21.796436 IP 172.31.16.182.54074 > 172.31.20.18.http: Flags [.], ack 847, win 224, options [nop,nop,TS val 47598 ecr 49455], length 0
06:21:21.796512 IP 172.31.16.182.54074 > 172.31.20.18.http: Flags [F.], seq 81, ack 847, win 224, options [nop,nop,TS val 47598 ecr 49455], length 0
06:21:21.796522 IP 172.31.20.18.http > 172.31.16.182.54074: Flags [.], ack 82, win 210, options [nop,nop,TS val 49555 ecr 47598], length 0
```

# 2.3. access_log off;

nginx.confの設定項目の１つです。結果は効果なし、不採用です。

これは説明不要でしょう。access_logは、デフォルト設定では大量に出るため、offにすることで性能改善がかなり図れると期待しましたが、効果ありませんでした。

今回の測定したレベルの性能では、access_log書き込みのオーバーヘッドはあまり大きくなかったようです。

# 2.4. カーネルチューニング

[nginxのパラメータチューニングとh2o] (http://qiita.com/cubicdaiya/items/235777dc401ec419b14e)で説明されているカーネルチューニングをフルセット実施してみました。

```:webサーバ
# vi /etc/sysctl.d/99-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

# sysctl -p
```

特にsomaxconnについては、listenキューの最大長であり、値を大きくすることで、性能改善が期待されました。しかし、結果は効果なし、不採用です。

# 3. チューニング後の確認

結局、tcp_nopush on;だけでしたが、チューニング後の状態を確認します。
負荷計測中のwebサーバのリソース状況を、vmstatで確認したところ、CPU idleは最小でも84%あり、メモリやディスクにも顕著な逼迫はありませんでした。

```:vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 4  0      0 609236    868 254136    0    0    38    27  512  608  1  1 98  0  0
 0  0      0 607832    868 255628    0    0     0     0 3535 4656  4 10 85  0  1
 0  0      0 604984    868 258380    0    0     0     0 4655 5784  4 11 84  0  1
 0  0      0 601816    868 261680    0    0     0   743 4030 4890  3 11 85  0  1
 0  0      0 599956    868 263484    0    0     0     1 4727 5780  3 12 84  0  1
 0  0      0 598024    868 265424    0    0     0     0 2998 4055  3  9 87  0  1
```

そのため、ネットワーク遅延を原因とするオーバーヘッドが、性能限界の原因になっているものと予測します。実際に、第1回の結果では、同時セッション数を増やすことで、性能が改善されていました。

しかし、再度測定すると、この事実は確かめられませんでした。同時2万セッションで測定したところ、エラー発生回数が19,374回と大幅増となり、性能は改善しませんでした。

なお、しばらくいろいろ測定していると、同時1万セッションの測定で、1,611 Requests/secに減少してしまいました。リブートして（忘れずに遅延印加したところ）、2,714 Request/secに増加しました。この原因は今のところ謎です。第1回と比較して、今回の冒頭の未チューニング状態で性能が改善していたのも、同じ要因かもしれません。

# 4. ベンチマークソフトの選定について

今回の測定で、わずかながらエラーが発生しました。Webサーバのリソースは十分に余裕があると思われる（項3によると、CPU負荷はピークで約15%）ことから、abの性能を疑いました。実際に、nginxのerrorログレベルをinfoにしてみると、以下のようなエラーが大量に発生して、クライアント側との通信に遅れが出ているように思われます。

```:error.log
2017/01/09 08:14:19 [info] 2164#2164: *1209857 recv() failed (104: Connection reset by peer) while sending response to client, client: 172.31.16.182, server: localhost, request: "GET / HTTP/1.0", host: "172.31.20.18"
2017/01/09 08:15:15 [info] 2164#2164: *1210027 client timed out (110: Connection timed out) while waiting for request, client: 172.31.16.182, server: 0.0.0.0:80
```

これに対して、tcpdumpによる詳細調査や、他のベンチマークソフトの検討を行いました。しかし、結果として、以下の理由により、abをそのまま使い続けることにしました。

+ tcpdumpによる切り分けでは、サーバ側からだけでなくクライアント側からでもRSTを発行しているケースがあり、必ずしもクライアント側だけが異常であるとは認められなかった。
+ httperfを試したが、アップデートが止まっているとともに、バイナリ配布だと同時1024コネクションまでしか達成できず、ソースからコンパイルすると同時セッションが大きいケースでSegmentation Falutが発生した。
+ wrkについては、[第1回](http://qiita.com/toast-uz/items/828bbb48d58eb65f1f9f)で記述したように、KeepAliveありを前提としているため、採用しなかった。
+ weighttpを試したが、エラーは減るもののゼロにはならなかった。また、weighttpは機能が少なく、PUT/POSTが使えない等、REST-WebAPIサーバの性能測定に使うには制限が多すぎた。
+ Jmeter、Tsungは、設定〜結果出力でコマンドラインのみで操作できず、複雑で扱いにくかった。
+ エラー発生率は0.1%以下と小さい。

遅延印加のためのtcが、エラーの原因かもしれません。