More Related Content Similar to TIME_WAITに関する話 (20) More from Takanori Sejima (12) TIME_WAITに関する話3. 自己紹介
- まぁまぁ MySQL でご飯食べてます
- 一時期は Resource Monitoring や KVS にも
力入れてました
- ネットワーク的には素人です
- Linuxとハードウェアは嗜む程度
- disk I/O にはむかしから興味あります
- その他 slideshare はこちら
- http://www.slideshare.net/takanorisejima/
6. 対象とする環境
- Ubuntu 14.04 LTS
- kernel 3.13 or 4.4
- 余談ですが、 EC2 で一番使われてるOSは、
Ubuntu らしいですね。
- Amazon EC2でもっとも人気のあるOSはUbuntu
- なので Ubuntu を対象にするのは無難かなと思
います。また、16.04 LTS の GA kernel は 4.4
なので、応用効くと思います。
10. kernel 3.13 だとここらへん
- tcp_v4_connect() から読んでくと
- net/ipv4/tcp_ipv4.c#L223
- inet_hash_connect()
- __inet_hash_connect() で
inet_get_local_port_range() で connect(2)
時に割り当てできる Ephemeral port の
range をとって、ひたすら for ループ回して、
空いてる port 見つからなかったら
11. return -EADDRNOTAVAIL;
- ここに来るはず
- inet_get_local_port_range() は
net.ipv4.ip_local_port_range の min/max を返す関数
で、connect(2) したとき ip_local_port_range で指定し
た範囲で port 割り当てられなかったらエラー。
- ESTABLISHED や TIME_WAIT で socket 大
量に開いてて Ephemeral port 割り当てできな
いと、Linux 的には EADDRNOTAVAIL 返
す。
12. TCP 的になぜ TIME_WAIT はあるのか
- 理由は二つあって、一つは
a. FIN 受けた側(Passive Close)側が 、 FIN 送った
(Active Close)側に FIN+ACK を返して、 LAST_ACK
に遷移した後
b. Active Close 側が FIN+ACK への ACK を Passive
Close 側に返したんだけど、 packet 落ちても
c. Active Close 側が TIME_WAIT で待ってれば、
Passive Close 側が FIN+ACK 再送すれば、Active
Close 側がACK再送できる(ACK来たら LAST_ACK
から、 直ちに CLOSED に遷移できる)
14. TCP 的になぜ TIME_WAIT はあるのか
- もう一つは
a. Active Close 側が送信した packet を、Passive Close
側が、(packet 落ちるかなんかして)受信できてない状
態になって
b. その後すぐまた connect(2)し、同じ送信元 {ip,port} と
送信先 {ip,port} のセットで通信がはじまって
c. a. で受信できてなかった packet がたまたま遅れてやっ
てきて、それが TCP の sequence number 的にかぶっ
てて packet が受けられてしまうと不味い
15. ただ、これらの問題は
- RFC 7323 で定義されてる TCP Time Stamp
Option と PAWS で回避できます。
- tcp header に timestamp つけることで、sequence
number が一周しても、 timestamp を比較することに
よって、受信側は古い packet かどうか判断できます。
- ただ Option なので、無効化されている環境もありえま
す。
- ややこしい話なので後述します。
16. というわけで
- これら二つの TCP 的な目的から TIME_WAIT
という状態が必要で
- TIME_WAIT で待ち続けている socket が多い
状態で connect(2) すると、 Linux は
EADDRNOTAVAIL を返す可能性がある。
- そうであるならば、これら二つの TCP 的な目的
を満たしつつ、 EADDRNOTAVAIL が発生しな
い状況にすればよい。
18. はじめに大前提
- TCP 的に port が 2byte のデータなら、パブリッ
ククラウド使ってるなら、インスタンスをスケール
ダウンして数並べれば良いんじゃないかと思い
ます。
- EC2 の c4.large でも c4.8xlarge でも、 TCP 的に使え
る port が 0-65535 なのは変わらないわけです。
- あるいは、コネクションプーリングできるなら、そ
れでも良いと思います。
20. ではどうするか
1. 先ずは Monitoring する。
a. kernel 4.4 にして、 Monitoring の精度を上げる。
b. Ephemeral port の使用状況をざっくり Monitoring す
る。
2. ip_local_port_range を変更する。
a. (必要であれば)ip_local_reserved_ports を指定
3. net.ipv4.tcp_tw_reuse = 1 にする。
4. 接続先の MySQL や KVS を集約して、 tcp_tw_reuse で
TIME_WAIT の socket を再利用しやすくする。
21. 1. 先ずは Monitoring する
- (個人的に)継続的な Monitoring は、すべての
基本だと思うんで、先ずは Monitoring
- TIME_WAIT の数をざっくり調べるには
- /proc/net/sockstat の tw
- なぜざっくりかというと、(少なくとも Ubuntu の)
kernel 3.13 だと、 TIME_WAIT の socket
が、 60sec (TIME_WAIT_LEN)経っても回収
されるとは限らないからです
22. netstat -o あるいは --timers
- netstat には -o というオプションがあります
- Include information related to networking timers.
- kernel 3.13 で TIME_WAIT 多いサーバで次の
コマンドを打つと、 timewait (0.00/0/0) がけっこ
う残ってることがあるのですが
- $ netstat -nato | egrep -c 'timewait.*(0.00'
- これは TIME_WAIT を回収できるまでの残り時
間で、 60sec 以上経ってるから 0です
23. なぜ kernel 4.4 にすると良いのか?
- kernel 4.4 の場合、 60sec 経つと、速やかに
TIME_WAIT 回収されるようです。
- ただ、なんでこのあたりの修正が効くのかは、い
まの私にはわかりませんでした。
- kernel4.1 で入ったこの patch で性能改善した
結果なんでしょうか?
25. Ephemeral port をざっくり数える
- Webサーバの場合、DBなどに接続するだけで
なく、 Reverse Proxy とか ELB から接続され
たりするので、それらをWebサーバから
close(2) すると TIME_WAIT になりますが、こ
うやって数えることができます
- $ netstat -nat | grep -v '127.0.0.1' | egrep -c '
1[0-9]+.[0-9]+.[0-9]+.[0-9]+:80
+[0-9]+.[0-9]+.[0-9]+.[0-9]+:[0-9]+ .*TIME_WAIT'
26. TCP:80 の TIME_WAIT を上手く除外
- connect(2) するときに EADDRNOTAVAIL 返
るのが困るなら
- Web サーバが LISTEN してる TCP:80 で残っ
てる TIME_WAIT(Reverse Proxy や ELB な
どに対して close(2) して残った TIME_WAIT)
を除外すると
- connect(2) に影響する、Ephemeral port の
TIME_WAIT を数えやすくなるわけです
27. 2. ip_local_port_range を変更する
- LISTEN してる port と被らないなら、
ip_local_port_range を変更するのは確実
- kernel 3.13 では 32768 - 61000、 kernel 4.4 では
32768 - 60999 が default
- 例えば、下限を 32768 から 24225 にするだけ
で、使える Ephemeral port が約30%増加
- よく使われてる fluentd は default で tcp:24224 を使う
ので、被らないように 24225。
28. net.ipv4.ip_local_reserved_ports
- fluentd で tcp:24224 を LISTEN してるんだけ
ど、net.ipv4.ip_local_port_range もっと下の
range まで指定したいときは、
ip_local_reserved_ports に、 LISTEN したい
port を列挙しておけば良いです。
- __inet_hash_connect() の中で予約された port を使わ
ないように見てる ので
29. 3. net.ipv4.tcp_tw_reuse = 1 にする
- ようやく出てきました tcp_tw_reuse
- これについては Coping with the TCP
TIME-WAIT state on busy Linux servers が最
高に良い資料で、ほとんどここに書いてあると
思いますが
- いちおう触れておきます
30. tcp_tw_reuse が使える条件は?
- TCP Time Stamps Option 有効な接続
- net.ipv4.tcp_timestamps = 1(default) で、client も
server も TCP Time Stamps Option 有効 なとき
- かつ、 source の ip と port、 destination の ip
と port が一致してるとき
- __inet_check_established() が INET_MATCH() でみ
てる
31. ざっくり仕組みを書くと
- source と destination の ip と port が一致した
ら、TIME_WAIT の socket が使ってる
Ephemeral port の再利用を試みるのだが
- tcp_tw_reuse は「TIME_WAIT になって一秒
以上経過した socket」を再利用する
- 何と比較して一秒と判断するかというと、 Active
Close側がFIN+ACK受け取ったときの時間を見
てる
33. - Active Close 側が送信した最後の ACK が受
信できてなかった場合
- Passive Close 側は LAST_ACK で待っているが、(雑
にいうと) TCP Timestamps が効いて(PAWSが効い
て)、SYN 再送しつつ connection 張れる
34. 雑に描くとこう
SEQ = Sequence number
TS = Time stamp
ecr = Time stamp echo reply
timestamp や sequence
number はざっくりしたイメージ
です。
実際のものと、ずらす値や桁数
は異なります。
例えば、tcp_tw_reuse で
sequence number ずらすとき、
実際のソースコードでは
+65535+2 されてます。
35. - PAWSというものが RFC7323 で定義されてい
る。(雑にいうと)高速な回線では、 sequence
number だけでは不充分。そこで TCP
TimeStamps Option で TCP header につけら
れた timestamp を見て、受信側は packet を破
棄したりできる。timestamp は 1msec ~ 1sec
間隔で更新されるのが RFC で良いとされるて
いるので
tw_reuse が一秒を基準にするのは
36. - TIME_WAIT に遷移してから 1sec 後に再利用
すれば、TCP header の timestamp が必ず更
新されており、遅延してやってきた
(TIME_WAITなsocketを再利用する前の)
packet があったとしても、遅延してきた packet
は timestamp 古いから、受信側が破棄でき
るってことではないかなぁ。たぶん
37. Time Stamp Option に守られてないと
- LAST_ACK で待ってる Passive Close 側に
SYN 送ると、 RST が返ってくる。 SYN_SENT
で RST 受けると、 ECONNREFUSED になっ
てしまう。
- TimeStamp Option が有効だと、 ACK の確認
して RST する前に、 PAWS で再送されるか
ら、それで助かるって設計のようだ。
38. 4. 接続先の MySQL や(略)
- tcp_tw_reuse は有効な手段だけど、
INET_MATCH() でマッチしないと使えないので
- WEBサーバから見たとき、 接続先の DB や
KVS が多いと、マッチしない可能性がある
- よって、 tcp_tw_reuse を活用したいなら、接続
先の DB や KVS などの ip と port の組み合わ
せは、少ないほうが望ましい
41. COM_QUIT や quit
- MySQL のプロトコルには COM_QUIT、
memcached には quit というコマンドがありま
す
- これらをクライアントから送ると、サーバ側から
close(2) してくれるので、本来、 TIME_WAIT
は mysqld や memcached 側にしか残らない
のが正しいはずです
44. MySQL 5.7.18 では
- mysql_close() では、 COM_QUIT 送ってから
end_server() 呼んでるのだが、 COM_QUIT
送るところで skip_check flag 立ってるので、
recv(2) などされず に、最終的に shutdown(2)
& close(2) されている。
- ということは、タイミング次第で RFC793の P.39
の Simultaneous Close Sequence になる可能
性がある。 recv(2) して欲しいなぁ
45. 残念ながら
- COM_QUIT を送ってる client にも mysqld 側
にも、 TIME_WAIT が残ってしまう可能性があ
る
- じっさいこれ見たことあります。 Simultaneous Close
Sequence に入っちゃってるのでしょう。
46. なので
- Feature request を出してみました
- https://bugs.mysql.com/bug.php?id=8635
6
- 直して欲しい方は、お手数ですが Impact on
me: のところで Affects Me を押して頂けると助
かります。ボタン押すのに Oracle アカウントが
必要なんですが、無料でとれます。
48. 基本的に
- DB や KVS に接続しまくるWEBサーバは、
tcp_tw_reuse 有効にすれば、 TIME_WAIT が
溜まっててもだいたい動くと思うけど
- 次のような条件は環境によって異なるので、ど
れくらい reuse できるかは環境依存
- WEBサーバが同時に connect(2) する数
- すなわち、 (thread や process の数) * (一回の
requestで接続されるDBやKVS)
49. いろいろMonitoringしても難しい
- /proc で TIME_WAIT は数えられるけど、
tcp_tw_reuse で再利用できる TIME_WAITの
数を数えるのは難しいので
- まずは次の式を満たす範囲で運用して
- (ip_local_port_range の 上限 - 下限) >= TIME_WAIT
の総数
- どれくらい reuse されるかは、徐々に試せば良
いのでは