2013/10/22 追記した.
Starletのコード読んでてlistening socketにTCP_DEFER_ACCEPTとかいうオプション渡してたので、これ何だって思って調べた.
TCPに特に詳しいわけではないので理解に誤りがあるかもしれない.
package Starlet::Server; ... # set defer accept if ($^O eq 'linux') { setsockopt($self->{listen_sock}, IPPROTO_TCP, 9, 1) # 9がTCP_DEFER_ACCEPTを表す and $self->{_using_defer_accept} = 1; } ...
TCP_DEFER_ACCEPTはLinux 2.4から導入されている.
Linux 2.6.32から挙動が若干変わっているらしい.
(linux の TCP_DEFER_ACCEPT (サーバサイド) の挙動について - kazuhoのメモ置き場)
ApacheとかNginxでも使われてるっぽい.
TCP_DEFER_ACCEPTを理解するのに下記の記事が参考になった.
Take advantage of TCP/IP options to optimize data transmission - TechRepublic
TCP_DEFER_ACCEPTが導入された動機
HTTPリクエストに必要なデータサイズは小さいため,HTTPリクエストは1個のパケット(DATAパケット)に収まる.
本当に必要なパケット(DATAパケット)は1個なのにTCPの3-way handshakeをやらないといけなくて,HTTPリクエストを受信するまでに必要なパケット数が4個になる.
これらの4個のパケットのせいで余分な遅延やオーバヘッドが発生してしまう.
例えば,3-way handshakeのfinal ACKがパケットロスしたときに,サーバはESTABLISHED stateに移行しない.
ESTABLISHEDになってないと,final ACKの次に送信されたDATAパケットがサーバ到着後にdropされたりする.
この場合は,サーバがSYS/ACKを再送してクライアントのACKを待つことになる.
TCP_DEFER_ACCEPTの挙動
listening socketかconnected socket(つまりサーバサイドかクライアントサイド)のどちらにTCP_DEFER_ACCEPTオプションをつけるかで挙動が変わる.
listening socketの場合は,final ACKを待たずに,DATAパケットを受信するまで,accept(2)をブロックする.
(普通はfinal ACKを受信した時点でacceptは処理をユーザプロセスに戻す)
これにより,final ACKがパケットロスしても,DATAパケットさえ受信すればaccept(2)は成功する.
したがって,DATAパケットのロスが無ければSYN/ACKの再送を回避できる.
connected socketの場合は,サーバからSYN/ACKを受信した後すぐにACKを返さずに,ユーザプロセスがwriteを発行した時点でDATAパケットとACKを一緒にして返す.
これにより,やりとりするパケットを1個減らせる.
上記の挙動からわかるように,TCP_DEFER_ACCEPTの使用はクライアントがACKの送信後にすぐにデータを送信する,つまりクライアントから喋り始めるプロトコルであることが前提となっている.
雑感
SYN/ACKの再送問題を回避したい(他にもあるかもしれない)っていうTCP_DEFER_ACCEPTの目的がすぐに理解できなかった.
acceptをブロックするとか動作の内容は書いてあるけど,何のためにそんなことをするのかを直接的に説明した文章が見つからなくてつらい感じだった.
参考
- Man page of TCP
- Take advantage of TCP/IP options to optimize data transmission - TechRepublic
- linux の TCP_DEFER_ACCEPT (サーバサイド) の挙動について - kazuhoのメモ置き場
- TCP_DEFER_ACCEPT - moriyoshiの日記
- core - Apache HTTP Server
追記
Starletの作者であるkazuhoさんやkazeburoさんからコメントいただきました.
サーバサイドでのTCP_DEFER_ACCEPTは、特にプリフォーク型のhttpdにおいては、処理中となるプロセス数を減らすためのテクニックかと / “TCP 3-way handshakeのレイテンシ軽減のためのTCP_DEFE…” http://t.co/t8ZYj1Izbw
— Kazuho Oku (@kazuho) 2013, 7月 22
サーバサイドでのTCP_DEFER_ACCEPTは、特にプリフォーク型のhttpdにおいては、処理中となるプロセス数を減らすためのテクニックかと / “TCP 3-way handshakeのレイテンシ軽減のためのTCP_DEFE…” http://t.co/t8ZYj1Izbw
— Kazuho Oku (@kazuho) 2013, 7月 22
linux で client-side で TCP_DEFER_ACCEPT をセットした場合に ACK 遅延するって man tcp には書いてないな
— Kazuho Oku (@kazuho) 2013, 7月 22
サーバ側のTCP_DEFER_ACCEPTはユーザ空間でaccept後にデータの到着を待たなくてもよくなって、サーバの効率がよくなるってのが主な目的だと思ってた
— masahiro nagano (@kazeburo) 2013, 7月 22
acceptはforkされたプロセスで実行してるから,TCP_DEFER_ACCEPTしてもacceptを実行するプロセスは結局ブロックされそうだけど,よくわかってない
— ゆううき (@y_uuk1) 2013, 7月 22
@kazuho 最初のACKがきてからacceptをユーザプロセスに返して再度read(もしくはpollなど)でブロックするより,defer_accept有効にしてreadでブロックせずにacceptでブロックをまとめた方が効率がよいという感じでしょうか?
— ゆううき (@y_uuk1) 2013, 7月 22