ネットワーク パフォーマンスの TCP 最適化


このページでは、Google Cloud およびハイブリッド クラウド環境で、TCP 接続のレイテンシを短縮する適切な設定を計算する方法について説明します。また、Google Cloud 内のプロセス間の接続レイテンシを改善する方法についても説明します。

最新のマイクロサービス アーキテクチャでは、単一タスクの小規模なサービスを開発することがデベロッパーに向けて提唱されています。これらのサービスの通信には、システムで予測される信頼性に応じて TCP または UDP のいずれかが使用されます。このため、マイクロサービス ベースのシステムの通信では高信頼性と低レイテンシが非常に重要となります。

Google Cloud はグローバル ネットワークを提供することによって信頼性を高め、レイテンシを短縮しているため、グローバルに展開できます。グローバル ネットワークでは、リージョンやゾーンを超えて Virtual Private Cloud(VPC)ネットワークを作成できます。アプリケーションは、Google Cloud ネットワークから離れることなく、リージョンやゾーン間で相互に接続できます。

従来のデータセンター環境用に作成されたアプリケーションをハイブリッド クラウド環境に移行すると、企業のデータセンターで実行されるアプリケーション コンポーネントとクラウドで実行されるアプリケーション コンポーネントが混在するため、パフォーマンスが低下することがあります。パフォーマンスの低下は多数の要因が絡み合って発生します。この記事では、ネットワークの任意の場所で大量のデータが移動するアプリケーションにおける往復のレイテンシ、およびレイテンシが TCP 通信のパフォーマンスに与える影響について詳しく説明します。

問題: レイテンシと TCP の動作

TCP では、送信側が高速で受信側が低速な場合にオーバーランを防ぐため、ウィンドウ処理という仕組みが使用されます。この仕組みでは、受信側のウィンドウが更新されるのを送信側が待つことなく送信できるデータ量を、受信側からアドバタイズします。この結果、受信側アプリケーションがその接続でデータを受信できない場合に、アプリケーションを待機するキューに入れられるデータ量は制限されます。

この TCP ウィンドウを使用することで、送信側システムと受信側システムはメモリを効率的に使用できます。受信側アプリケーションでデータがすべて使用されると、ウィンドウの更新が送信側に送信されます。ウィンドウの更新は最短 1 往復で発生します。このため、TCP 接続の一括転送のパフォーマンスの制限の 1 つは次の式で表されます。

スループット <= ウィンドウ サイズ / ラウンドトリップ時間(RTT)のレイテンシ

TCP の初期設計では、このウィンドウの最大サイズは 65,535 バイト(64 KiB - 1)です。これは、送信側が送信可能な最大データ量であり、この限度に達すると、さらにデータの送信が可能になったことを知らせるウィンドウの更新を受信するまで送信できません。

導入時からの TCP の変遷

TCP が導入されて以降、主要機能の一部が変更されました。

  • 一般的なネットワーク速度が 10,000 倍以上高速化しました。
  • 一般的なシステムのメモリ容量が 10,000 倍以上増加しました。

ネットワークの高速化によって、当初の TCP ウィンドウ サイズではネットワーク リソースを十分に使用できなくなりました。送信側は、ネットワーク条件下で実現可能な最高速度でウィンドウいっぱいのデータを送信した後、TCP ウィンドウの更新を受け取るまでかなりの時間アイドル状態になります。また、メモリ容量の大幅な拡大により、送信側と受信側でネットワーク通信に使用できるメモリが増加し、高速化によって生じた限界に対処できるようになりました。

このデータ交換の様子を次の図に示します。

送信側が送信できるデータ量は 64 KB だけで、送信後はウィンドウ更新を取得するまで非常に長時間待機する

送信側は TCP ウィンドウの更新を待機してから次のデータを送信するため、ネットワークをフル活用できていません。

一度に送信するデータ量の増加

一度に送信するデータ量を増やすとこの問題を解決できます。ネットワークの帯域幅が増加するとパイプ(ネットワーク)を流れるデータ量が増加します。また、パイプが長くなるほどデータの受信確認に要する時間も長くなります。この関係を帯域遅延積(BDP)といいます。BDP は帯域幅とラウンドトリップ時間(RTT)の積で、パイプにできるだけ多くのデータ量を流すために最適な送信ビット数が得られます。これは以下の式で表されます。

BDP(ビット)= 帯域幅(ビット/秒)× RTT(秒)

BDP の計算値は、TCP ウィンドウ サイズの最適値となります。

たとえば、RTT が 30 ミリ秒で帯域幅が 10 Gbps のネットワークについて考えます。ウィンドウ サイズは、当初の TCP ウィンドウ サイズ(65,535 バイト)とします。この値では、帯域幅の能力を十分に活用することはできません。このリンクで実現可能な TCP の最大パフォーマンスは次のとおりです。

(65,535 バイト * 8 ビット/バイト) = 帯域幅 * 0.030 秒
帯域幅 = (65,535 バイト * 8 ビット/バイト) / 0.030 秒
帯域幅 = 524280 ビット / 0.030 秒
帯域幅 = 17476000 ビット/秒

別の言い方をすれば、上記の値ではスループットが 17 Mbits/秒を少し上回る程度となり、10 Gbps というネットワーク能力のごく一部しか活用されていないことになります。

解決策: TCP ウィンドウ サイズのスケーリング

当初の設計の TCP ウィンドウ サイズによって生じるパフォーマンスの限界を解決するため、TCP プロトコルが拡張され、ウィンドウ サイズをより大きくスケーリングできるようになりました。ウィンドウのスケーリングでは、最大 1,073,725,440 バイト(約 1 GiB)までのウィンドウがサポートされます。この機能については、RFC 7323TCP ウィンドウ スケール オプションとして記載されています。

ウィンドウ スケールの拡張によって、TCP ウィンドウの定義が 30 ビットの使用で拡大され、さらに TCP ヘッダーの 16 ビットのウィンドウ フィールドでこの 30 ビット値を使用する暗黙的なスケーリング係数が使用されています。Linux ベースのシステムでこの機能が有効になっているかどうかを確認するには、次のコマンドを使用します。

sudo sysctl net.ipv4.tcp_window_scaling

Google Cloud Linux 仮想マシンはすべてデフォルトでこの機能が有効になっています。このオプションが有効な場合は、戻り値 1 が返されます。この機能が無効になっている場合は、次のコマンドを使用して有効化できます。

sudo sysctl -w net.ipv4.tcp_window_scaling=1

ウィンドウ サイズの拡大によるスループット

上記の例を使用して、ウィンドウをスケーリングした場合のメリットについて説明します。前回と同様、レイテンシが 30 ミリ秒で帯域幅が 10 Gbps のネットワークを想定します。次の式を使用して新しいウィンドウ サイズを計算します。

(リンク速度 × レイテンシ)÷ 8 ビット = ウィンドウ サイズ

上記の値を代入すると、次のようになります。

(10 Gbps * 30 ms/1,000 秒) ÷ 8 ビット/バイト = ウィンドウ サイズ
(10,000 Mbps * 0.030 秒) ÷ 8 ビット/バイト= 37.5 MB

TCP ウィンドウ サイズを 37 MB に拡大すると、TCP の一括転送パフォーマンスの理論的な限界をネットワーク能力に近い値まで向上させることができます。システムのオーバーヘッド、平均パケットサイズ、リンクを共有するフロー数など、他にもパフォーマンスに影響を与える要因は多数ありますが、ウィンドウ サイズを拡大すると、以前のウィンドウ サイズ制限によるパフォーマンス低下が大幅に緩和されます。

TCP ウィンドウ サイズを変更する Linux 調整パラメータの設定

Linux では、TCP ウィンドウ サイズが以下の sysctl(8) 調整パラメータの影響を受けます。

net.core.rmem_max
net.core.wmem_max
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem

最初の 2 つの調整パラメータは、これらのパラメータ値を超えないようにアプリケーションのリクエストを制限することで、TCP ウィンドウ サイズを直接制御しようとするアプリケーションの最大 TCP ウィンドウ サイズに影響を与えます。残りの 2 つの調整パラメータは、Linux の自動調整に従うアプリケーションの TCP ウィンドウ サイズに影響を与えます。

最適なウィンドウ サイズの値は、それぞれのユーザーの状況によって異なりますが、システムのデータ送信が予期される経路の BDP(帯域遅延積)の最大値が 1 つの基準となります。この場合は、次の手順で調整パラメータを設定する必要があります。

  1. root 権限を保有していることを確認します。
  2. 現在のバッファ設定を取得します。変更をロールバックする場合に備えて、これらの設定を保存しておきます。

    sudo sysctl -a | grep mem
    
  3. 環境変数で、使用する新しい TCP ウィンドウ サイズを設定します。

    MaxExpectedPathBDP=8388608
    
  4. すべての種類の接続に OS の受信バッファの最大サイズを設定します。

    sudo sysctl -w net.core.rmem_max=$MaxExpectedPathBDP
    
  5. すべての種類の接続に OS の送信バッファの最大サイズを設定します。

    sudo sysctl -w net.core.wmem_max=$MaxExpectedPathBDP
    
  6. TCP 受信メモリのバッファ(tcp_rmem)を設定します。

    sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 $MaxExpectedPathBDP"
    

    tcp_rmem 設定では 3 つの値を設定します。

    • 1 つの TCP ソケットに割り当て可能な受信バッファの最小サイズ。この例での値は 4096 バイトです。
    • デフォルトの受信バッファサイズ。この値は、他のプロトコルで使用される /proc/sys/net/core/rmem_default の値より優先されます。この例での値は 87380 バイトです。
    • 1 つの TCP ソケットに割り当てることができる受信バッファの最大サイズ。この例では、以前に設定した値(8388608 バイト)に設定されています。
  7. TCP 送信メモリのバッファ(tcp_wmem)を設定します。

    sudo sysctl -w net.ipv4.tcp_wmem="4096 16384 $MaxExpectedPathBDP"
    

    tcp_wmem 設定では 3 つの値を設定します。

    • 単一の TCP ソケットで使用可能な TCP 送信バッファの最大スペース。
    • 単一の TCP ソケットで使用可能なデフォルトのバッファ スペース。
    • TCP 送信バッファの最大スペース。
  8. 調整パラメータを設定して、以降の接続で指定した値を使用するようにします。

    sudo sysctl -w net.ipv4.route.flush=1
    

再起動後もこれらの設定を保持するには、以前に設定したコマンドを /etc/sysctl.conf ファイルに追加します。

sudo bash -c 'cat << EOF >> /etc/sysctl.conf
net.core.rmem_max=8388608
net.core.wmem_max=8388608
net.ipv4.tcp_rmem=4096 87380 8388608
net.ipv4.tcp_wmem=4096 16384 8388608
net.ipv4.route.flush=1
EOF'

更新後のウィンドウ サイズで RTT をテストする

BDP を十分に活用できるように TCP ウィンドウ サイズを拡大すると、次の図のようになります。

送信側は一度に大量のデータを送信可能で、ウィンドウ更新通知の待機時間はわずかである

TCP ウィンドウ サイズは、関連プロセスで使用可能なリソースと使用される TCP アルゴリズムに応じていつでも調整できます。上図に示されるように、ウィンドウのスケーリングによって、当初の TCP 仕様で定義されている 65 KiB のウィンドウ サイズと比較して接続が大幅に改善されています。

これは、自分でテストできます。まず、ローカル PC とリモート PC の両方で調整パラメータを設定して、双方で TCP ウィンドウ サイズが変更されていることを確認します。次に、以下のコマンドを実行します。

dd if=/dev/urandom of=sample.txt bs=1M count=1024 iflag=fullblock
scp sample.txt your_username@remotehost.com:/some/remote/directory

1 つ目のコマンドは、ランダムデータを含む 1 GB の sample.txt ファイルを作成します。2 つ目のコマンドは、そのファイルをローカルマシンからリモートマシンにコピーします。

scp コマンドの出力に注意してください。帯域幅が Kbps 単位でコンソールに表示されます。TCP ウィンドウ サイズの変更前と変更後で、結果が大幅に変化したことがわかります。

次のステップ