概要
MongoDBでCPU使用率やロードアベレージが高くないのに処理が詰まっている現象が起きました。
その時間にbatchが動いていてアクセスが急に増えることが原因と言うのは分かっているのですが、負荷的には十分余裕があり不思議な状態でした。
そこでdstatで見るポイント - Carpe Diemでも述べたように、負荷の状態から判断する基準があります。
今回の現象から判断するに、TCPコネクションに原因がありそうです。
原因調査
Too many open filesは出ているか
ファイルディスクリプタが足りない場合はコネクション数が足りずに処理が詰まってしまいます。
そしてその場合Too many open files
というエラーが出ます。
しかし今回は出ていませんし、またlimitsを確認してみても
$ cat /proc/2889/limits Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size unlimited unlimited bytes Max stack size 8388608 unlimited bytes Max core file size 0 unlimited bytes Max resident set unlimited unlimited bytes Max processes 64000 64000 processes Max open files 64000 64000 files Max locked memory 65536 65536 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 515271 515271 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us
Max open files
が64000
と十分な数値でした。
ちなみにもし足りない場合は以下の記事を参考に設定を変更します。
Ephemeral portは足りているか
もしかしたらローカルのポートが枯渇して接続ができなくなっているかもしれませんので確認します。
$ sudo sysctl -a | grep port_range net.ipv4.ip_local_port_range = 1024 65535
こちらも十分ありますね。
そもそも接続を受けきれないのではないか
DSAS開発者の部屋:高負荷サイトのボトルネックを見つけるにはに似た状況があったのでこちらのやり方で調査してみました。
netstat -s
で確認すると
$ netstat -s TcpExt: ... 1380 times the listen queue of a socket overflowed 1380 SYNs to LISTEN sockets dropped ...
このようにSYNs to LISTEN sockets dropped
がありました。つまりSYNパケットを取りこぼして接続を受けきれないことが分かります。
ちなみにこの統計値はOSの起動時からの値なので、再起動すればリセットされます。
対応方法
このSYNパケットを受け入れるキューサイズは/etc/sysctl.conf
の以下のパラメータによって調整できます。
パラメータ | 説明 | デフォルト |
---|---|---|
net.ipv4.tcp_max_syn_backlog | ソケットキューのサイズ。 ただしbacklogでmaxconnより大きな数字を定義しても、maxconnの値が優先される。 |
1024 |
net.core.somaxconn | ソケットキューの長さの最大値を定義するカーネルパラメーター | 128 |
こちらの数値の75%を超えるとdropしてしまうようなので、なるべく大きな数値を設定します。
net.core.somaxconn = 2048 net.ipv4.tcp_max_syn_backlog = 2048
設定後反映します。
$ sudo sysctl -p
こうすればシステムを再起動せずに反映できます。ただ実行中のプロセスには反映されないので、そのプロセスに反映させたい場合はプロセスを再起動してください。
Nginxで検証してみる
初期状態
デフォルトのNginxの設定で、abを使って大量の接続をさせます。
somaxconn
のデフォルト値が128
なので、それより多い500
同時接続で試してみましょう。
$ ab -c 500 -n 10000 http://127.0.0.1/
を実行すると
$ netstat -s ... 386 times the listen queue of a socket overflowed 386 SYNs to LISTEN sockets dropped ...
このようにdropします。
対応
kernelパラメータを設定
/etc/sysctl.conf
に以下を設定します。
net.core.somaxconn = 10000 net.ipv4.tcp_max_syn_backlog = 10000
反映
$ sudo sysctl -p
Nginxを再起動
$ sudo service nginx restart
再度実行してみる
$ ab -c 500 -n 10000 http://127.0.0.1/
を実行すると
$ netstat -s ... 386 times the listen queue of a socket overflowed 386 SYNs to LISTEN sockets dropped ...
先ほどと数が変わっていないので、dropしていないことが分かります。ちゃんと受けきれるようになりました。
もう少しabの接続数を増やしてみる
somaxconn
を10000
に設定したので、もう少し多い1000
同時接続で試してみましょう。
$ ab -c 1000 -n 10000 http://127.0.0.1/
すると今度はdropが発生してしまいます。
885 times the listen queue of a socket overflowed 885 SYNs to LISTEN sockets dropped
何故でしょうか?
実はNginxの方でもbacklogが設定されている
こちらで分かるようにNginxの方ではデフォルトで511
でbacklogを設定されているので、それ以上のSYNパケットを同時に処理することができません。
なのでNginx側でもbacklog数を設定する必要があります。
server { listen 80 backlog=2048; location / { root html; index index.html; } }
このようにNginxでも十分な数を設定します。
再起動して再実行
$ ab -c 1000 -n 10000 http://127.0.0.1/
実行してみます。
885 times the listen queue of a socket overflowed 885 SYNs to LISTEN sockets dropped
先ほどと変わっていないので、dropがなくなったことが分かります。
その他
今回のkernelパラメータ設定に加えて、僕がよく使ってる設定を貼っておきます。
### optimized config
kernel.panic = 5
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.core.rmem_max = 1048576
net.core.wmem_max = 1048576
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 5
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
まとめ
SYNパケットのdropがある場合は
- カーネルパラメータでbacklog、somaxconnを調整する。
- プロセスのbacklog数も設定可能であれば調整する
- 反映後はプロセスを再起動する
を実行すると改善されます。