ぜんぶTIME_WAITのせいだ！


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

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

![graph 3.png](https://qiita-image-store.s3.amazonaws.com/0/8726/ab218f2b-950a-b93d-aff0-300af25c5f8b.png "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](http://www.redbooks.ibm.com/redpapers/pdfs/redp3861.pdf) によると、`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](https://qiita-image-store.s3.amazonaws.com/0/8726/994f5f3e-5d62-d000-95f2-0b66e2b2840e.png "graph3.png")

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

#TIME_WAITってなんなんだ？

[EFSM/SDL modeling of the original TCP standard (RFC793) and the Congestion Control Mechanism of TCP Reno](http://www.medianet.kent.edu/techreports/TR2005-07-22-tcp-EFSM.pdf)のネットワーク状態遷移図を確認してみる。

![efsm.png](https://qiita-image-store.s3.amazonaws.com/0/8726/e06af875-e837-693f-69ba-50da5d9d209f.png "efsm.png")

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

#見方を変えてみる

[Wikipedia:Transmission Control Protocol](http://en.wikipedia.org/wiki/Transmission_Control_Protocol)よりESTABLISHEDからCLOSEDになるまでの状態をシーケンスで見てみる。

![tcp.jpg](https://qiita-image-store.s3.amazonaws.com/0/8726/048a8d91-0809-1ed0-e987-ac0243771be9.jpeg "tcp.jpg")

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

そうすると、<font color="red">**Receiver側のCLOSEDが早く行われるならば、Inisiator側の待ち時間(tw_fin_timeout値)はなるべく縮めても良いのか！？**</font>

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

**[tcp_fin_timeout](https://cs.uwaterloo.ca/~brecht/servers/ip-sysctl.txt)**

> 	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](http://kaivanov.blogspot.jp/2010/09/linux-tcp-tuning.html)**

> 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](http://kaivanov.blogspot.jp/2010/09/linux-tcp-tuning.html)**

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

- TIME-WAITソケットを早く再利用することが出来る。
- デフォルトの値は0。
- ロードバランサなどを使用している場合は注意して使用する必要がある。
- <font color="red">[注]kernel4.1.2で削除されているようです。</font> [tcp: remove tcp_tw_recycle](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v4.12&id=4396e46187ca5070219b81773c4e65088dac50cc)


#tcp_fin_timeoutはどのくらいにすればいいの？

何件かのサイト([1](http://hiroki.jp/2009/11/11/604/),[2](http://books.google.co.jp/books?id=5bT2NEArm_8C&pg=PA166&lpg=PA166&dq=time_wait+1%E7%A7%92&source=bl&ots=frtrEM6uQ9&sig=eLik3S7CwsXV2XlPBp3AqyjTmuE&hl=ja&sa=X&ei=20xKU9r1F8Lg8AXUqILQCw&ved=0CEcQ6AEwBDgK#v=onepage&q=time_wait%201%E7%A7%92&f=false),[3](http://www.atmarkit.co.jp/ait/articles/1403/25/news004_3.html))をチェックしてみたが、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になにか問題あるの？

[ココ](http://d.hatena.ne.jp/nice20/20070521)と[ココ2](http://ya.maya.st/d/200804c.html#s20080430_2)の検証によると`net.ipv4.tcp_timestamps`と`net.ipv4.tcp_tw_recycle`の両方を使うと

[Linux/net/ipv4/tcp_ipv4.c](http://lxr.free-electrons.com/source/net/ipv4/tcp_ipv4.c#L1530)
[Linux/include/net/tcp.h](http://lxr.free-electrons.com/source/include/net/tcp.h#L157)

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

![tcp_tw_recycle.png](https://qiita-image-store.s3.amazonaws.com/0/8726/080d4d7b-95cf-7809-2cab-6ce2d0918c70.png "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
```

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

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