2009年11月28日土曜日

ネットワーク性能のチューニング (TCP編)

このエントリーをはてなブックマークに追加
前回はsun4vアーキテクチャのSolarisでネットワーク性能を改善する方法について説明しました。今回はSolaris一般についてTCPの性能を改善する方法を説明します。

TCPの性能のチューニングといえば、まずはウィンドウサイズです。必要なウィンドウサイズは、通信相手とのRTT(ms)÷1000×帯域(bps)÷8で求められます。今どきはRTTが300msくらいあるヨーロッパ相手でも50Mbpsとか出ることがあるので、2MBはほしいです。国内については、石川県から東京を経由して行くので場所によってはRTTがいくらか大きくなりますが、悪くても50ms程度なので2MBもあれば320Mbpsまで対応できます。

受信ウィンドウの最大値を決めるカーネルパラメータはtcp_recv_hiwatで、デフォルトは48KiBです。ミラーサーバは受信のスループットをあまり必要としませんが、これはあまりにも小さすぎます。ftp.jaist.ac.jpはヨーロッパで50Mbps出るところとrsyncすることがあるので、前述の通り2MBに増やします。また、tcp_max_bufはtcp_recv_hiwat以上でないといけないので、デフォルトの1MiBから2MBに引き上げます。

ミラーサーバでは受信ウィンドウが大きすぎても問題ありません。送信側がACKを待たずに送ってくるパケット数は、送信側の輻輳回避アルゴリズムで決まります。受信側は受信ウィンドウのサイズまでパケットを保持する必要がありますが、これが2MBに達することは普通ありません。メモリの確保は動的に行われるので、メモリの浪費を気にする必要もありません。

送信ウィンドウの最大値に影響するカーネルパラメータはtcp_cwnd_maxです。このパラメータは輻輳ウィンドウの最大値を決めます。送信ウィンドウの大きさは、受信側からアドバタイズされた受信ウィンドウと輻輳ウィンドウの小さいほうになります。tcp_cwnd_maxが小さすぎるとアドバタイズされた受信ウィンドウを使い切れません。デフォルトは1MiBですが、これも2MBに引き上げます。

大きすぎる送信ウィンドウには注意する必要があります。送信側はACKの返っていないパケットを保持しなければならないので、最悪で接続数×tcp_cwnd_maxだけメモリが必要です。30,000を超えるコネクションを持つことがあるftp.jaist.ac.jpでは60GBにもなります。

アドバタイズされる受信ウィンドウはたいてい65,535以下ですし、1MBを超える受信ウィンドウがアドバタイズされても、輻輳ウィンドウがそこまで届くことは普通はありません。実際には、それほど大きなメモリは必要になりませんが、最悪の可能性は留意するべきです。ウィンドウサイズとは別に、TCP全体で使用するメモリを制限できるOSもあります。利用できるのなら、そういう制限を検討するのもいいでしょう。

もう一つウィンドウサイズに関係のありそうなカーネルパラメータにtcp_xmit_hiwatがありますが、これは関係ありません。この値が示すのは、TCPスタックが保持する未送信のデータサイズの最大値です。ユーザランドからブロックせずに一度にTCPスタックに送れるデータサイズに影響します。

ftp.jaist.ac.jpで動くサーバでは、rsync以外はsendfilevシステムコールを用いるので、このサイズは関係ありません。rsyncはバッファサイズに256KiBを用いているので、tcp_xmit_hiwatをデフォルトの48KiBから十分大きな512KiBまで引き上げます。

まとめると
/usr/sbin/ndd -set /dev/tcp tcp_max_buf 2000000
/usr/sbin/ndd -set /dev/tcp tcp_cwnd_max  2000000
/usr/sbin/ndd -set /dev/tcp tcp_xmit_hiwat 524288
/usr/sbin/ndd -set /dev/tcp tcp_recv_hiwat 2000000
こんな感じになります。Web上を探して見つかるものとはずいぶん違いますが、TCPのソースコードを読んだ上で実運用に基づいて決めてますので、見当違いな値ではないはずです。数字は内部でMSSで丸められたりするので、2のべき乗にこだわらなくてもいいです。

ウィンドウサイズをチューニングするとコネクション確立後のスループットが上がります。リクエスト/秒を上げるには、コネクションの確立に関するパラメータもチューニングする必要があります。

ftp.jaist.ac.jpは特に忙しくないときで毎秒150リクエストくらい、忙しいときで毎秒400リクエストくらい処理します。この数字はそれほど大きなものではありません。それでもデフォルトのパラメータのままだとリクエストをかなり取りこぼします。

取りこぼしたリクエストの数はkstatコマンドでkstat tcp:::*listendrop*とするとわかります。tcp_listendropq0が3ウェイハンドシェイクが完了する前に取りこぼした数で、tcp_listendropがコネクションが確立したのにアプリケーションがacceptせずに取りこぼした数です。

SYN flood攻撃を受けるとtcp_listendropq0が大きく増えますが、これは気にしても仕方がありません。SYN flood攻撃を受けたわけでもないのにtcp_listendropq0が増えている場合は、3ウェイハンドシェイクを待つリクエストを保持するキューのサイズが足りません。これはtcp_conn_req_max_q0というカーネルパラメータで指定できます。デフォルトは1024ですが、ftp.jaist.ac.jpでは8,192まで増やしています。

CPUの負荷が高くなると確立した接続をacceptせずに取りこぼすことがあります。CPUの負荷が高くないのにtcp_listendropqが増えている場合は、listenのバックログが足りません。バックログの最大値はtcp_conn_req_max_qで指定できます。デフォルトは128ですが、ftp.jaist.ac.jpでは4,096まで増やしています。かなり大きいですが、2,048だと平常時でもぽろぽろ取りこぼすので4,096にしています。

カーネルパラメータで指定するのは最大値で、実際のバックログはアプリケーションでlistenを呼ぶときに指定します。したがって、アプリケーションの設定ファイルも変更する必要があります。Apache HTTP ServerならListenBackLogディレクティブで4,096を指定します。

tcp_listendropq0もtcp_listendropqも多少大きすぎても問題ありません。ただしq0のほうをむやみに増やすと、SYN flood攻撃を受けている間に正常なリクエストを受けられる確率が上がる代わりに、不正なSYNに対する処理コストが増えるのでおいしくありません。

Sun Fire T2000のネットワーク性能をチューニングしている間に、Web上のいろんな資料を当たったのですが、Sunのマニュアルを含めて本当のことを詳しく書いてあるものがなくて、かなり苦しみました。このブログが同じ問題に取り組む人の助けになるとうれしいです。

0 件のコメント:

コメントを投稿