Edited at

ぜんぶTIME_WAITのせいだ!

More than 1 year has passed since last update.


課題

突然キャンペーンとかの高トラフィックが来る!とか言われると色々困ることはあるものの、今のご時世クラウドだからスペック上げときゃなんとかなるでしょ。ってとりあえずCPUとかメモリあげて見たものの、キャンペーンが始まったら意外と早くブラウザからつながらない!!とか言われたりする。

CPUもメモリもそんなに負荷は特に高くもない。調べてみたらTIME_WAITが大量にあった。

graph 3.png


とりあえず何とかしたい


TIME_WAIT数をコマンドで確認

$ netstat -anp|grep TIME_WAIT

__(snip)__

tcp 0 0 192.168.1.1:80 192.97.67.192:56305 TIME_WAIT -
tcp 0 0 192.168.1.1:80 192.63.64.145:65274 TIME_WAIT -
tcp 0 0 192.168.1.1:80 192.39.2.205:3325 TIME_WAIT -
tcp 0 0 192.168.1.1:80 192.102.102.9:54300 TIME_WAIT -
tcp 0 0 192.168.1.1:80 192.71.125.102:61769 TIME_WAIT -
__(snip)__


カーネルパラメータの設定変更

Tuning Red Hat Enterprise Linux on IBM によると、tcp_tw_reuse,tcp_tw_recycle,tcp_fin_timeoutを下記のように追記して設定することで下げることが出来そう。


/etc/sysctl.conf

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30


カーネルパラメータの設定反映

# sysctl -p

graph3.png

ふむ、たしかにTIME_WAIT数は下がる


TIME_WAITってなんなんだ?

EFSM/SDL modeling of the original TCP standard (RFC793) and the Congestion Control Mechanism of TCP Renoのネットワーク状態遷移図を確認してみる。

efsm.png

うーん、これを見ると通信上はFIN-WAIT-1/FIN-WAIT-2/CLOSINGから遷移して、滞留する必要なさそうにみえるのになぁ。


見方を変えてみる

Wikipedia:Transmission Control ProtocolよりESTABLISHEDからCLOSEDになるまでの状態をシーケンスで見てみる。

tcp.jpg

例えば、InisiatorがWEBサーバで、Receiverの方がPCとかスマホとすると、WEBサーバ側がTIME_WAITのときにスマホ側でCLOSEDされるのを待ってるのかぁ。

そうすると、Receiver側のCLOSEDが早く行われるならば、Inisiator側の待ち時間(tw_fin_timeout値)はなるべく縮めても良いのか!?


再度カーネルパラメータの内容を確認してみる

tcp_fin_timeout


Time to hold socket in state FIN-WAIT-2, if it was closed by our side. Peer can be broken and never close its side, or even died unexpectedly. Default value is 60sec. Usual value used in 2.2 was 180 seconds, you may restore it, but remember that if your machine is even underloaded WEB server, you risk to overflow memory with kilotons of dead sockets, FIN-WAIT-2 sockets are less dangerous than FIN-WAIT-1, because they eat maximum 1.5K of memory, but they tend to live longer.



  • ソケットをこちら(上記Receiver)側からクローズしたときに、FIN-WAIT-2 状態に保つ時間。

  • 通信先が切れてしまったり、先方でのクローズをずっと行わなかったり、 あるいは予期しない形で終了してしまうかもしれません。

  • デフォルトの値は 60 秒です。

  • 2.2 で用いられていた通常の値は 180 秒で、 この設定にすることもできますが、マシンが (必ずしも負荷が高くなくても) WEB サーバだったりすると、 膨大な FIN-WAIT-2 ソケットのせいでメモリが溢れる危険もあります。

  • FIN-WAIT-2 ソケットは 1.5K のメモリしか消費しないので FIN-WAIT-1 ほど危険ではありませんが、長く存在する傾向があります。

tcp_tw_reuse


This allows reusing sockets in TIME_WAIT state for new connections when it is safe from protocol viewpoint. Default value is 0 (disabled). It is generally a safer alternative to tcp_tw_recycle.

Note: The tcp_tw_reuse setting is particularly useful in environments where numerous short connections are open and left in TIME_WAIT state, such as web servers and loadbalancers. Reusing the sockets can be very effective in reducing server load. Starting in Linux 2.6.7 (and back-ported to 2.4.27), linux includes alternative congestion control algorithms beside the traditional 'reno' algorithm. These are designed to recover quickly from packet loss on high-speed WANs.



  • これは、プロトコルの観点から安全に新しい接続でTIME_WAITのソケット再利用することを許可します。

  • デフォルトは0(無効)。

  • これは一般的にtcp_tw_recycleよりも安全な方法です。

Note: tcp_tw_reuse設定は、Webサーバやロードバランサーのような、大量に短い接続がオープンしTIME_WAIT状態になるような環境では特に有用です。ソケットの再利用は、サーバ負荷軽減に非常に効果的です。Linux2.6.7から始まり、linuxは伝統的な'Reno'アルゴリズムに加えて代替の輻輳制御アルゴリズムを実装している。高速WAN上でパケットロスから素早く復旧するためデザインされている。

tcp_tw_recycle


This enables fast recycling of TIME_WAIT sockets. The default value is 0 (disabled). Should be used with caution with loadbalancers.



  • TIME-WAITソケットを早く再利用することが出来る。

  • デフォルトの値は0。

  • ロードバランサなどを使用している場合は注意して使用する必要がある。


  • [注]kernel4.1.2で削除されているようです。 tcp: remove tcp_tw_recycle


tcp_fin_timeoutはどのくらいにすればいいの?

何件かのサイト(1,2,3)をチェックしてみたが、30秒説がよく見つかるものの幅は1〜30秒とこれといった決定打はなかった。ネットワーク速度が向上してきたので60秒では長いけど それぞれのサーバ環境に合わせて と言った感じに取れる。


設定の参考値を自分なりに考えてみる


  • なるべく積極的にリソースを開放したい

  • 外部と通信するものは通信状態が様々なのである程度時間を保つ

  • 内部で通信するものはGigabit Ethernetなどが増えているのである程度短くできる

サーバ用途
tcp_fin_timeout参考値
備考

WEB
20〜30秒

Hadoop
15〜30秒

KVS(cassandra)
1〜5秒
攻めすぎ?


TIME_WAITの待ち時間状況を確認するには?

netstatにまだ便利オプションがあった。これで確認しながら調整するのが良さそう。

$ netstat -anpto

__(snip)__

tcp 0 0 192.168.1.1:80 192.2.197.186:58601 TIME_WAIT - timewait (52.22/0/0)
tcp 0 0 192.168.1.1:80 192.82.117.34:56977 TIME_WAIT - timewait (30.70/0/0)
tcp 0 0 192.168.1.1:80 192.135.233.60:60358 TIME_WAIT - timewait (40.65/0/0)
tcp 0 0 192.168.1.1:80 192.4.206.121:64294 TIME_WAIT - timewait (29.46/0/0)
tcp 0 0 192.168.1.1:80 192.2.197.186:58600 TIME_WAIT - timewait (52.22/0/0)
tcp 0 0 192.168.1.1:80 192.82.117.34:56464 TIME_WAIT - timewait (28.03/0/0)
tcp 0 0 192.168.1.1:80 192.169.145.26:60343 TIME_WAIT - timewait (13.64/0/0)
tcp 0 0 192.168.1.1:80 192.193.81.104:61038 TIME_WAIT - timewait (24.37/0/0)
__(snip)__


tcp_tw_recyleになにか問題あるの?

ココココ2の検証によるとnet.ipv4.tcp_timestampsnet.ipv4.tcp_tw_recycleの両方を使うと

Linux/net/ipv4/tcp_ipv4.c

Linux/include/net/tcp.h

上記部分のロジックで、ロードバランサーやNATの配下ある場合に問題が発生するらしい。

tcp_tw_recycle.png


まとめ

LB直下にない単独のサーバであれば下記の様な設定が良い気がする。また、Inbound側だけの通信ならうまくTIME_WAITが減らせるかも知れない。※tcp_fin_timeoutは環境に合わせて調整


/etc/sysctl.conf

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

LB直下のWEBサーバなどはtcp_tw_recycleを無効にした設定を考えたほうが良いかもです。


/etc/sysctl.conf

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 30

今回は昔からよく出ている3つのパラメータだけにフォーカスしたが、沢山パラメータがあるのでより効率よくするためにまだまだ余地はありそう。

デフォルト値は歴史的に変化していないものがありそうなのでちゃんと理解が必要なんだなと再認識。