タグ: TCP

  • TCPのNagleアルゴリズム、小さな送信を待たせる条件とACKの関係

    TCPのNagleアルゴリズム、小さな送信を待たせる条件とACKの関係

    短いデータをアプリから続けて渡したのに、二つ目がすぐネットワークへ出ない。TCPでは、その理由の一つにNagle(ネーグル)アルゴリズムがあります。小さいパケットの増えすぎを防ぐため、まだ確認されていない送信がある間、後続の短いデータをまとめます。待つ条件を知ると、通信の遅さを回線速度だけで考えずに済みます。

    簡単にまとめると?

    • Nagleは、未確認の送信データがあるときに、後続の小さいデータをまとめる仕組みです。いつでも一定時間待つタイマーではありません。
    • 先行データがACKで確認されるか、有効MSS分のデータがたまると、この待ち条件が外れます。他の送信制限は別に残ります。
    • LinuxのTCP_NODELAYはNagleを無効にしますが、遅延の原因が別にあれば解決しません。書込み時刻・送信・ACKを対応させて確かめます。

    小さな荷物にも、ヘッダーは付いてくる

    TCPは、アプリが渡したバイト列をセグメントという単位に分け、IPパケットに載せます。データが短くても、宛先や順序などを示すヘッダーは必要です。歴史的なRFC 896は、キーボードから1文字ずつ送る場合に、1バイトのデータに40バイトのヘッダーが付く例で問題を説明しました。これは当時の条件による例で、現在のすべてのパケットが40バイトのヘッダーになるという意味ではありません。

    独自の単純計算として、ヘッダーを40バイトに固定し、100バイトずつ10回分のデータを考えてみます。10個に分けるとデータ1000バイトにヘッダー400バイト、1個にまとめられればヘッダー40バイトです。合計は1400対1040バイトになります。リンク層の情報やACKは除いた比較ですが、データの量が同じでも、小分けの数で負担が変わることが分かります。

    ただし、まとめるために待てば応答は遅くなり得ます。効率と待ち時間のどちらを重く見るかは、送る内容によって違います。

    待つのは「未確認」と「短い後続」が重なったとき

    ACK(確認応答)は、相手のTCPがどこまでデータを受け取ったかを知らせます。相手のアプリがその内容を処理し終えた、という通知ではありません。

    RFC 9293の基本形では、未確認の送信データがある間、後続データをバッファーへためます。先行データが確認されるか、有効MSS(実際に送れるセグメントの最大データ長)分を送れるようになると、Nagleによる待ちが外れます。最初から未確認データがなければ、この規則は最初の短い送信を待たせる理由になりません。PSHビットを付けることも、この待ちを必ず解除する方法ではありません。

    次の図では有効MSSを説明用に1000バイトとし、相手の受信窓と輻輳制御には余裕があると仮定します。現実のMSSは経路やヘッダーなどに左右され、1000バイトが推奨値という意味ではありません。

    有効MSSを1000バイトとした例。未確認100バイトと待機100バイトなら待つ。全確認ACK後は100バイト、未確認でも待機1000バイトなら満量のセグメントを送信できる。
    基本形の条件を比べる独自の例。他の送信制限は満たされていると仮定し、実装全体を再現した図ではない。

    最初の100バイトが未確認で、次の100バイトだけが待機していれば、後続は短いままなので待ちます。最初の100バイトすべてを確認するACKが来れば、待機していた100バイトはNagleの条件を抜けます。別の展開として、ACKより先にあと900バイトが加われば、待機分は1000バイトとなり、満量のセグメントを送れるようになります。

    この二つは別の解除経路です。「ACKが来るまで絶対に何も送らない」と覚えると、満量のセグメントを送る場合を見落とします。

    遅延ACKと重なると、待ち時間が目立つ

    受信側も、毎回すぐACKを返すとは限りません。確認応答をまとめる遅延ACKによって、ACKの数を減らすことがあります。送信側は後続の短いデータをため、受信側は確認応答を少し待つ。この組合せで、アプリからは短い送信の間に不自然な間があるように見える場合があります。

    たとえば、要求を前半と後半に分けて書き込み、相手アプリは後半まで受け取ってから応答する設計を考えます。前半が未確認の間に後半だけが小さく残ると、相手アプリが応答を作れる時点まで遅れかねません。だからといって、すべての遅延がNagleと遅延ACKの組合せとは限りません。RFCにも実装上の変形が記されており、待ち時間を一律に200ミリ秒などと決めることはできません。

    TCP_NODELAYで変わること、変わらないこと

    Linuxのtcp(7)マニュアルでは、TCP_NODELAYを有効にするとNagleを無効にし、少量でも可能な限り早く送ると説明されています。短い更新の即時性が重要な通信では、検討する価値があります。ただしTCP_CORKなど他の設定との関係があり、設定名だけで最終挙動を決めつけないことが必要です。

    TCP_NODELAYは、ネットワークの混雑、相手の受信バッファー不足、アプリ内部のバッファリングをなくす設定ではありません。1回のwriteが必ず1パケットになることも、相手の1回のreadが同じ境界になることも保証しません。TCPが運ぶのは順序付きのバイト列であり、アプリのメッセージ境界はアプリ側で定義する必要があります。

    測るときは三つの時刻をそろえる

    原因を絞るには、①アプリが書き込んだ時刻とサイズ、②実際にデータが出た時刻、③先行データへのACKが戻った時刻を比べます。小さい後続がACK直後に出るなら、Nagleが関係するという仮説を立てられます。ただし相関だけで断定せず、同じ負荷と書込み単位で設定を変えて比較します。

    試験では平均だけでなく、短い要求が遅れた回数や応答時間の分布、パケット数も見ます。待ち時間が減ってもパケット数が大きく増えたなら、効率との交換条件を確認できます。まとめてよいデータはアプリ側で適切にまとめ、急ぐ小さいデータは待たせない設計を検討する、という判断につながります。

    ここでの数値と図は、仕様の条件を切り分けるためのモデルです。実ネットワークの測定結果や、特定OSで必ず同じ順番になるという保証ではありません。

    出典