読者です 読者をやめる 読者になる 読者になる

Carpe Diem

備忘録。https://github.com/jun06t

負荷が低いのにアクセスを捌けきれない時の対応

負荷試験 MongoDB Nginx

概要

MongoDBでCPU使用率ロードアベレージが高くないのに処理が詰まっている現象が起きました。
その時間にbatchが動いていてアクセスが急に増えることが原因と言うのは分かっているのですが、負荷的には十分余裕があり不思議な状態でした。

そこでdstatで見るポイント - Carpe Diemでも述べたように、負荷の状態から判断する基準があります。

  1. ロードアベレージを確認する
  2. 1が高ければCPU、ディスクI/O、メモリにボトルネックがある
  3. 1が低ければTCPコネクションにボトルネックがある

今回の現象から判断するに、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 files64000と十分な数値でした。

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の接続数を増やしてみる

somaxconn10000に設定したので、もう少し多い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が設定されている

Module ngx_http_core_module

こちらで分かるように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がなくなったことが分かります。

まとめ

SYNパケットのdropがある場合は

  • カーネルパラメータでbacklog、somaxconnを調整する。
  • プロセスのbacklog数も設定可能であれば調整する
  • 反映後はプロセスを再起動する

を実行すると改善されます。

ソース