🎟️

TCでネットワーク遅延にはまった話 〜遅延を入れたら、なぜか帯域幅まで狭まってしまった〜

に公開

TCを使用した帯域制限テストでハマった備忘録です。好きでハマったわけではなくむずくてハマってました。遅延(Latency)を入れたら、なぜか帯域幅まで狭まってしまったという症状の人に刺さると嬉しいです。

TCを使って帯域 1 Gbps 制限(TBF)だけでは問題なかったのに、10 ms の遅延(netem)を足した瞬間に 200 Mbit/s まで落ちた、、、どうして、、、


検証環境

項目 内容
OS Ubuntu 22.04 LTS
IF 10 GbE (シングルキュー)
クライアント / サーバ それぞれ物理ホスト
テストツール netperf 2.7.0 / iperf3 3.14

1. TL;DV

  1. tc tbfコマンド で 1 Gbps帯域制限だけを入れる
sudo tc qdisc add dev NIC名 root handle 1: tbf rate 1gbit burst 12mbit latency 50ms
   → 実測 0.95 Gbps前後で安定

↓安定してるね~

iperf3 -c 132.17.21.4 -t 3
Connecting to host 132.17.21.4, port 5201
[  5] local 132.17.21.12 port 57040 connected to 132.17.21.4 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   116 MBytes   972 Mbits/sec    0   5.20 MBytes
[  5]   1.00-2.00   sec   119 MBytes   999 Mbits/sec    0   5.20 MBytes
[  5]   2.00-3.00   sec   117 MBytes   982 Mbits/sec    0   5.20 MBytes
  1. さらに netem delay 10ms を同じインターフェースに追加
    10 ms の遅延を付与
sudo tc qdisc add dev NIC名 parent 1: handle 10: netem delay 10ms
   → 実測 200 Mbit/s まで急低下

↓なんだこれは、、、

iperf3 -c 132.17.21.4 -t 3
Connecting to host 132.17.21.4, port 5201
[  5] local 132.17.21.12 port 57040 connected to 132.17.21.4 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   116 MBytes   200 Mbits/sec    0   5.20 MBytes
[  5]   1.00-2.00   sec   119 MBytes   203 Mbits/sec    0   5.20 MBytes
[  5]   2.00-3.00   sec   117 MBytes   198 Mbits/sec    0   5.20 MBytes
  1. カーネルの送受バッファ上限 (*_mem_max) を 32 MiB、アプリのソケットバッファを 8 MiB に設定
    → 再び 0.95 Gbps に回復
    どこを設定したかは3.対策で!

2. 原因

  • BDP (帯域 × RTTで算出される値)

    • 1 Gbps × 10 ms = 約 1.25 MiB
    • この量の未ACKデータが回線に乗っていないと設定どおりの性能が出ない。
  • 送信ウィンドウ = min( 輻輳ウィンドウOR 受信ウィンドウ )

    • Ubuntu既定の受信ウィンドウ上限は 256–512 KiB 程度
    • BDP の約 40 % しかなく、ACK 待ちで送信停止 → 200–400 Mbit/s で頭打ち

3. 対策

  1. 両端のカーネル上限を拡張

    net.core.wmem_max = 33554432
    net.core.rmem_max = 33554432
    net.ipv4.tcp_wmem = 4096 131072 33554432
    net.ipv4.tcp_rmem = 4096 131072 33554432
    
  2. テストアプリ側で 8 MiB のソケットバッファを要求

    netperf ... -- -m 256K -s 8M,8M -S 8M,8M
    
  3. 受信ウィンドウが 8 MiB 広がり、TBF の 1 Gbps がそのまま上限として効く


4.そもそもどういうこと

Window Sizeについて

・Window Size は TCP 通信の端末間が「受信側がACK 無しにどのくらいの TCPペイロード (MSS) を受信できるか」であって、3wayhandshakeのSYNをクライアントから送った後、SYNACKパケットが返ってくる。そこでBDP(Bandwidth-Delay Product)という概念が存在していて、これはデータ転送時に必要なウィンドウの大きさを見積もるための指標。

送信可能量について

送信可能量は常に
min(輻輳ウィンドウ cwnd, 受信ウィンドウ rwnd)
を超えられない(フロー制御+輻輳制御)。

BDPはB(帯域幅の容量(ビット/秒))とRTT(往復遅延時間)の積で出せる。
BDP(Bandwidth-Delay Product)= B × RTT
→ BDPは回線を満たすのに同時に飛んでいてほしい未ACKデータ量(in-flight)の目安。これを満たせないと、本来の性能を出せない。実質的な一度に送れるデータ量の上限。

in-flight はスループットと RTT の積で表せる:

\begin{aligned} \text{in-flight} &= \text{Throughput} \times RTT \;\le\; \min(\text{cwnd},\,\text{rwnd},\,\text{sndbuf}) \\[4pt] \text{Throughput} &\le \dfrac{\min(\text{cwnd},\,\text{rwnd})}{RTT} \quad(\text{sndbuf が十分大きい前提}) \end{aligned}

目標帯域 𝐵を出したければ、
min(cwnd,rwnd)≥B×RTT=BDP を満たす必要がある。

つまり実効スループットは概ね min(輻輳ウィンドウ, 受信ウィンドウ) / 往復遅延時間に支配される。今回、往復遅延時間が行きで10s遅延かかるようにしたので、当てはめると、
1 Gb/s × 10 ms ≒ 1.25 MB が必要ウィンドウサイズの目安になる。もし rwnd(あるいは cwnd)がこれを下回ると、窓/RTT 相当(≒200 Mb/s など)で頭打ちになる。

ここで、3.対策で出てきた以下の設定は何をしているかというと、

net.ipv4.tcp_rmem = [min, default, max](バイト)

→受信用バッファ(rcvbuf)のオートチューン範囲。負荷やRTT/BDPに応じて default から max まで自動で広がる。大きくすると、rwnd の上限も大きくできる。

net.ipv4.tcp_wmem = [min, default, max](バイト)

→直接は tcp_wmem で cwnd を設定しているわけではない。ただし
tcp_wmem(+ net.core.wmem_max)を上げる → 送信ソケットバッファ(sndbuf)に十分な未送信/未ACKデータを溜められる
送信側が常に「帯域を満たすだけのデータ」をキューに持てる → ACK が途切れず入って cwnd の増加が進む。

net.core.rmem_max / net.core.wmem_max

→アプリが SO_RCVBUF / SO_SNDBUF で要求できる絶対上限(ハード上限)。

で、sndbuf が十分大きい前提だと、以下のようにcwndとrwndしか気にする必要がなくなる。

\begin{aligned} \text{in-flight} &= \text{B}\, \times RTT \;\le\; \min(\text{cwnd},\,\text{rwnd}) \\ \text{B} &\le \dfrac{\min(\text{cwnd},\,\text{rwnd})}{RTT} \end{aligned}

となる。そこでcwndとrwndを大きくしたので、スループットが改善したという次第。
めちゃ理解に時間かかったので分かりにくいOR違ってたらコメントください。追記とかします。

5. まとめと運用指針

遅延を伴う帯域試験では必ず BDP を計算する
→RTT を 10 ms 足しただけで必要ウィンドウは 100 倍になる。

送受どちらのバッファも BDP 以上に
→片側だけ広げても min() で小さい方に張り付く。

TBF の burst は BDP 以上を推奨
→今回は 32 Mbit (≈ 4 MiB) で十分だったが、高帯域ならさらに拡大。
遅延値を変えたらBDP とバッファ設定を必ず再計算。
→例:50 ms にすれば BDP は 6.25 MiB になる。

この記事が「帯域制限+遅延」を一発で再現したい人の参考になれば幸いです。

おまけ

今までかけた遅延と帯域制限の消し方です。これを忘れると一瞬ビビるヨ!

sudo tc qdisc del dev NIC名 root

参考文献

Transmission Control Protocol (TCP)
https://datatracker.ietf.org/doc/rfc9293/

TCP Extensions for High Performance
https://www.rfc-editor.org/rfc/rfc7323.html

【図解】TCP window size の仕組み〜MSS(MTU)との違い,calculated window size,unknown factor,受信バッファの設定変更~
https://milestone-of-se.nesuke.com/nw-basic/tcp-udp/window-size-mss-mtu/#google_vignette

O’Reilly の Building Blocks of TCP から学ぶTCP通信
https://zenn.dev/port_inc/articles/yuki-onodera-whatistcp

32.5. 高スループットのための TCP 接続のチューニング
https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/9/html/monitoring_and_managing_system_status_and_performance/tuning-tcp-connections-for-high-throughput_tuning-the-network-performance

輻輳ウインドウ(cwnd) と受信ウインドウ(rwnd)の関係性について
https://liberation-of-se-like-slaves.net/【tcp】輻輳ウインドウcwnd-と受信ウインドウrwndの関係/

Discussion