『ハイパフォーマンスブラウザネットワーキング』メモその2

メモの続きです。今回はTCP。

2章 TCPの構成要素

  • IP (Internet Protocol)はホスト間のルーティングとアドレッシングを提供する。
  • TCP (Transmission Control Protocol)は信頼性の低い通信路上で信頼性の高い通信の抽象を提供する。具体的には、欠損パケットの再送順序通りの転送輻輳制御と回避データ完全性など、ネットワーク通信の複雑さの大部分をアプリケーションから隠蔽する。
    • 輻輳とは、物が一箇所に集中し混雑する様態のこと。輻輳の発生を回避したり、輻輳状態から速やかに回復させる技術のことを輻輳制御と呼び、輻輳の状態が悪化し、通信効率が非常に低くなる状態を輻輳崩壊と呼ぶ。
    • パケットの往復時間がいずれかのホストの最大再送送信間隔を超える場合、そのホストは同じデータグラムのいくつものコピーをネット上に流し始める。このとき、ネットワークは深刻な事態に陥る。スイッチングノードにあるすべての利用可能なバッファは最終的に埋め尽くされてしまい、パケットを破棄する必要がある。パケットの往復にかかる時間が最大値に達しており、ホストは各々のパケットを何度も送信し、最終的には各パケットのコピーが目的に複数個到着する。これが輻輳崩壊である。
  • TCPストリームを扱う際、送信されるバイト列が相手側で受信されるバイト列と完全に同一であることや、このバイト列が順序通りにクライアントに届くことが保証されている。
  • TCPはタイムリーな転送よりも正確な転送のために最適化されており、この事実がブラウザにおけるパフォーマンスの最適化を困難にする。
  • HTTP標準はトランスポートプロトコルとしてTCPのみを指定しているわけではなく、必要であればUDP (Universal Datagram Protocol)や他のトランスポートプロトコルを使用してHTTP通信を行うこともできる。しかし、事実上すべてのHTTPトラフォックはTCPを使用する。
  • TCP接続は3ウェイハンドシェイクで開始する。
    • このハンドシェイクにより、クライアントとサーバ間で、データ交換を開始する前に、最初のパケットのシーケンス番号や、接続に関する他の様々な変数に関して両者の合意をとる。
    • シーケンス番号は、セキュリィ上の理由から、両者により無作為に選ばれる。
    • 3ウェイハンドシェイク(Client:SYN -> Server:SYN ACK -> Client:ACK)が完了すると、クライアントとサーバの間でデータを流し始めることができるようになる。クライアントはACKパケットの送信直後にデータパケットを送信することができる
    • この開始手続き(3ウェイハンドシェイク)は、すべてのTCP接続に適用される。新規接続では毎回パケット往復によって、データが送信可能になるまでのレイテンシが発生する。例えば、クライアントがニューヨーク、サーバがロンドンにあり、光ファイバ回線上で新たにTCP接続を開始する場合、3ウェイハンドシェイクに最低56msかかる。
    • 3ウェイハンドシェイクによる遅延は、TCP接続の開始を高コストにする。これが、TCPを利用するアプリケーションにとって接続の再利用が重要な最適化である理由である。
  • 輻輳崩壊を回避するための、双方向にデータを送信する速度を管理するためのメカニズムが、フロー制御輻輳制御輻輳回避である。
  • フロー制御は、受信側が処理できないほどの大量のデータを送信側から送出することを防止するメカニズムである。
    • TCP接続上の両者において、受信データの維持に使用可能なバッファサイズを知らせるために、各ホストは受信ウィンドウサイズ(rwnd)をアドバタイズする。
    • サーバとクライアントのどちらかが現在のペースの通信を維持できない場合、より小さなウィンドウサイズを相手側にアドバタイズできる。ウィンドウサイズが0に達すると、今までに送信されたバッファ上のデータがアプリケーションによって処理されるまでは、新たにデータを送るべきでないという合図になる。この一連の処理は、TCP接続の生存期間中継続する。ACKパケットが両者の最新のrwnd値を相手に伝えることにより、両者はそれぞれの容量と処理速度に合わせて転送速度を動的に調整できる
    • 最初のTCP標準では、rwndの最大値は16ビット、65,535バイト(64KB)であったが、TCPウィンドウのスケーリングを行うことにより、最大受信ウィンドウサイズが65,535バイトから1ギガバイトとなる。現在、主要プラットフォーム状においては、TCPウィンドウスケーリングはデフォルトで有効になっている。
  • フロー制御では、データ受信側の容量をオーバーフローさせないようデータ送信側で制御できるものの、ネットワーク自体の許容量を超えることを防ぐメカニズムがいずれの側にも存在していない。送信側、受信側ともに通信開始時には利用可能な知らないため、これを推測し、絶えず変わり続けるネットワークの状態に対して通信速度を調整するためのメカニズムが必要である。それが、スロースタート、輻輳回避、高速再送(fast retransmit)、高速リカバリ(fast recovery)である。
  • クライアント-サーバ間における利用可能な容量を推測する唯一の方法は、実際にデータを送受信することであり、これがスロースタートの狙いである。
    • 開始にあたり、サーバはTCP接続ごとに輻輳ウィンドウサイズ(cwnd)変数を初期化し、控えめなシステム設定値を初期値にセットする。輻輳ウィンドウサイズは、データ送信側が相手からACKを受け取る前に続けて送信できるデータ量であり、データ送信側における制限である。cwnd変数は、ホスト間でアドバタイズされることも交換されることもない、プライベートな変数である。
    • クライアント-サーバ間の「送信中未応答」データ(ACKがが返されていないデータ)の最大値は、rwnd変数とcwnd変数のうちの小さい方によって制限される
    • サーバとクライアントは最適な輻輳ウィンドウサイズを決定するために、パケットのACKが返されるごとに徐々にウィンドウサイズを大きくする。これがスロースタート。
    • cwndの初期値は、1999年4月のRFC2581で最高4セグメント、2013年4月のRFC6928で10セグメントとなっている。なお、Ethernetにおける1ネットワークセグメントは、1460バイト。
    • TCP接続は利用可能な帯域幅の大きさに関わらずスロースタートの段階を経なければならず、リンクの全容量を直ちに使用できるわけではない(だから、TCP接続の再利用が重要となる)。
    • 仮に、クライアントとサーバの受信ウィンドウサイズがrwnd=65,535バイト(64KB)、初期輻輳ウィンドウサイズが4セグメント、パケット往復時間(RTT)が56ms(ロンドンからニューヨーク)とすると、輻輳ウィンドウサイズが受信ウィンドウサイズまで到達するには45セグメントまでの増加が必要であり、それには224msかかる(=4回のパケット往復と数百msのレイテンシ)。
    • 輻輳ウィンドウの増加にかかる時間を減らすには、クライアント-サーバ間の往復時間を減らすか、または初期輻輳ウィンドウサイズを大きくするか(現状、最大10セグメント)。
    • スロースタートは、巨大なファイルやストリーミングのダウンロードではさほど問題にならない。大きな総転送時間によって、スロースタートフェーズのコストは薄められる。しかし、HTTP接続はバースト的に短く行われることが多く、最大ウィンドウサイズに到達する前にリクエストが終了してしまうことも珍しくない。そのため、多くのWebアプリケーションのパフォーマンスは、サーバとクライアントの間のパケット往復時間に左右される。
  • クライアントがTCP接続を再利用できる場合、3ウェイハンドシェイクのコストとスロースタートの振りが存在しないため、大幅なパフォーマンス向上が見込める。
  • TCPは本質的に、パフォーマンス制御のためのフィードバックメカニズムとしてパケットロスを利用している。
  • スロースタートでのウィンドウサイズの増加は、輻輳ウィンドウサイズが受信側の受信ウィンドウサイズやシステム設定の輻輳閾値を超えるまで、もしくはパケットロスが発生して輻輳回避アルゴリズムが制御を行うまで。
  • 輻輳回避においては、パケットロスはネットワークの輻輳を示しているということが暗黙的に想定される。通信路のどこかで、パケットを捨てなければならないほど混み合ったリンクやルーターに遭遇しているということ。そこで、ウィンドウサイズを調節することで、それ以上のパケットロスを抑え、ネットワークを必要以上に圧迫することを避けなければならない。
  • 輻輳ウィンドウサイズが一度リセットされると、輻輳回避アルゴリズムは、その後のパケットロスを最小化するためのウィンドウサイズ拡大アルゴリズムを指定する。
    • 当初用いられていた拡大アルゴリズムは、AIMD(最小分散制御)アルゴリズム。これに対し、Linuxカーネル3.2以上におけるデフォルトの輻輳回避アルゴリズムは、PRR(比例的レート縮小)。これはパケットロスを伴う接続のレイテンシを平均で3-10%削減する。
  • すべてのTCPパケットは送信される際に一意のシーケンス番号を持ち、また受信側にデータを順序通りに渡さなければならない。あるパケットが通信路上で失われた場合、それ以降のシーケンスを持つパケットは、失われたパケットが再送信され受信側に届くまで、受信側のバッファに保持されなければならない。アプリケーションはすべてのデータが揃うまで処理を待たねばならず、アプリケーションがソケットからデータを読み取ろうとする際には、それを単に伝送遅延と認識する。この現象はHoL(Head of Line)ブロッキングと呼ばれる。
  • まとめると、TCPの中心となる原則は以下。
    • 3ウェイハンドシェイクはパケット1往復分のレイテンシを発生させる
    • スロースタートはすべての新規接続に適用される
    • フロー制御と輻輳制御はすべての接続のスループットを制御する
    • TCPのスループットは現在の輻輳ウィンドウサイズによって制限される
  • サーバ設定のチューニングポイントは以下。
    • TCP初期ウィンドウの増加
    • スロースタートリスタートは無効に設定
    • ウィンドウスケーリング(RFC1323)
    • TCP Fast Open
  • アプリケーションの動作のチューニングポイントは以下。
    • パケットを送信しないことより速くパケットを送信する方法は存在しない。できるだけ少ない数のパケットを送信する。
    • パケットをより速く進ませることは不可能だが、より近い場所からの送信は可能。
    • TCP接続の再利用はパフォーマンスの向上に不可欠。

うーん、なかなか覚えることが多くて大変だ。。。