小さな TCP 書き込みが待たされる理由:Nagle アルゴリズムと遅延 ACK、TCP_NODELAY

リクエストを小さな write 2 回に分けて送ると、2 回目の断片が相手の ACK を待つことがあります。Nagle アルゴリズムと遅延 ACK のルールを RFC 896、1122、9293 に分けて確認し、TCP_NODELAY と Linux 専用オプションがそれぞれ何を変えるかを整理しました。

開発ネットワークTCP

クライアントがリクエストのヘッダーを write() で 1 回、ボディをもう 1 回で送ります。サーバーはリクエストがそろえばすぐに処理できるのに、レスポンスが遅れて届きます。ボディがサーバーに遅れて届くためです。こういうときに疑ってみる価値があるのが、送信側の Nagle アルゴリズムと受信側の遅延 ACK の組み合わせです。どちらも単独で見ればパケット数を減らすための妥当なルールですが、リクエスト・レスポンス型の通信ではお互いを待つ形になります。

ただし、小さな書き込みが遅いからといって、いつもこの組み合わせが原因とは限りません。以下では、それぞれのルールの出どころ、標準が何を要求しているか、どんな条件で待ちが生じるかを分けて見ていきます。標準は現行の TCP 仕様である RFC 9293(2022 年 8 月、STD 7)を基準にし、以前の要件を定めた RFC 1122 と、歴史的な背景として RFC 896 をあわせて参照します。Linux の動作は、2026 年 10 月 18 日に確認した man-pages リポジトリの tcp(7)(未リリースの開発版)だけを根拠にしています。

送信側:Nagle アルゴリズム

出発点は 1984 年に John Nagle が書いた RFC 896 です。この文書は冒頭で、議論のためのメモであり標準を定めるものではないと明記しています。扱ったのは Telnet のようにキー入力を 1 文字ずつ送るケースでした。データ 1 バイトにヘッダー 40 バイトが付いた 41 バイトのパケットが文字ごとに出ていき、負荷の高いネットワークではこのオーバーヘッドが輻輳を悪化させる、という問題です。

RFC 896 が提案したルールは単純です。送信済みのデータに確認応答(ACK)を受けていないものが残っていれば、ユーザーから新しいデータが来ても新しいセグメントを送らない。原文は、この抑制はタイマーやサイズの判定なしに無条件だと書いています。接続が空いているときは未確認のデータがないので書いた時点で送られ、保留されたデータは相手の ACK が届いたときにまとめて送られます。

RFC 1122(1989 年)はこれを要件に取り込み、条件を 1 つ加えました。未確認のデータがある間、送信側 TCP はユーザーデータを PSH ビットに関係なくバッファーにためますが、未確認のデータが確認されるか、最大サイズのセグメント(Eff.snd.MSS バイト)を満たせるようになれば送ります。実装は Nagle アルゴリズムを実装すべきであり(SHOULD)、アプリケーションが接続ごとにこれを無効にする手段は必ずなければなりません(MUST)。

RFC 9293 は RFC 1122 の TCP に関する部分を置き換えるもので、同じ内容を 3.7.4 節に置いています。実装の推奨は SHLD-7、接続ごとに無効にする手段は MUST-17 です。この節は、Nagle アルゴリズムが現在のほとんどの TCP コードベースに実装されており、ときに小さな変形が加えられていると述べています。いずれの場合も、送信は slow start による制限も受けます。

受信側:遅延 ACK

受信側は、データセグメントごとに ACK を 1 つずつ返す代わりにそれより少なく返すことで効率を上げられます。これが遅延 ACK です。RFC 1122 の 4.2.3.2 節と RFC 9293 の 3.8.6.3 節の要件は次のとおりです。

  • 遅延 ACK を実装すべきである(SHOULD、RFC 9293 では SHLD-18)。
  • 遅延は 0.5 秒未満でなければならない(MUST、MUST-40)。
  • 最大サイズのセグメントが続く流れでは、少なくとも 2 つ目のセグメントごとに ACK を返すべきである(SHOULD、SHLD-19)。RFC 9293 はここに「または新しいデータ 2×RMSS バイトごと」を加えています。

RFC 1122 の説明によれば、ACK を遅らせる理由の 1 つは、アプリケーションがウィンドウを更新してすぐにレスポンスを送る機会を与えることです。そうすると ACK、ウィンドウ更新、レスポンスのデータが 1 つのセグメントにまとまります。文書は、文字単位のリモートログインでサーバーが送るセグメント数を 3 分の 1 にできる例を挙げています。

標準が決めているのは上限(0.5 秒未満)で、実際のタイマー値は実装が選びます。この記事では特定の OS の値は扱いません。

2 つが出会う順序

リクエストを 2 回に分けて書き、サーバーはリクエストが全部そろわないと応答できないとします。最初、接続に未確認のデータはありません。もう 1 つ前提を置きます。サーバーの遅延 ACK の実装が、この最初の小さなセグメントへの ACK を遅らせることを選ぶ、というものです。標準はこれを認めているだけで求めてはいないので、別の実装ならこのセグメントにすぐ ACK を返すこともあります。

クライアント (Nagle 有効)                  サーバー (遅延 ACK)
write(ヘッダー) ── セグメント 1 ────────▶  受信。リクエストが不完全で応答できない
write(ボディ)     未確認データがあるので保留   載せるレスポンスがないので ACK を遅らせることがある
             ◀────────────────── ACK ──  ほかに ACK のきっかけがなければタイマーが送る(標準では 0.5 秒未満)
             ── セグメント 2 ────────▶   リクエスト完成、処理開始
             ◀─────────────── レスポンス ──
  1. ヘッダーは未確認のデータがないので、すぐに送られます。
  2. ボディは小さく、ヘッダーがまだ確認されていないので、Nagle のルールにより保留されます。
  3. サーバーはリクエストの一部しか受け取っておらずレスポンスを作れないため、ACK に載せるデータがありません。小さなセグメントが 1 つだけなので「最大サイズのセグメント 2 つごと」の条件にも当たらないので、サーバーは ACK を遅らせることができ、このシナリオでは実際に遅らせます。
  4. ほかに ACK を送るきっかけがなければ、やがてサーバーの遅延 ACK タイマーが ACK を送ります。その ACK が届いて、ようやくボディが送られます。

この順序は 2 つのルールの定義と上の前提から導いた説明です。RFC 9293 付録 A.3 は、一般的な OS では Nagle アルゴリズムと遅延 ACK がどちらも実装され既定で有効になっており、リクエスト・レスポンス型ではこの組み合わせがアプリケーションの性能を落とすことがあると述べています。

裏返すと、上の条件のどれか 1 つが崩れればこの待ちは起きません。リクエストを 1 回で書く、2 回目の書き込みの前に最初のセグメントの ACK がすでに届いている、サーバーに送るデータがあって ACK がすぐそれに載る、サーバーが最初のセグメントへの ACK を遅らせずに返す、といった場合です。逆に、小さな書き込みが遅れる原因はこれ以外にもあります。送信は slow start の制限と、相手が広告したウィンドウから未確認のデータを引いた使用可能ウィンドウ(usable window)の制限も受けます。オプションを変える前に、パケットキャプチャで 2 つ目のセグメントが本当に相手の ACK の直後に出ているかを確かめるほうが確実です。

どう避けるか

メッセージを 1 回で書く

アプリケーション側でヘッダーとボディをバッファーにまとめて 1 回で書けば、未確認のデータがない接続では Nagle のルールが送信を止めません。パケット数も増えません。ただし、メッセージが 1 セグメントより大きい場合、満杯のセグメントは送られても、最後に残った小さな断片は先行データの ACK を待つことがあります。

TCP_NODELAY で Nagle を無効にする

標準が必ず用意するよう求めている「接続ごとに無効にする手段」は、Linux では TCP_NODELAY ソケットオプションです。RFC 9293 付録 A.3 も、Nagle の無効化は一般にソケットオプションで提供されており、多くのアプリケーションが単に無効にしていると述べています。Linux の tcp(7) によれば、TCP_NODELAY を設定するとデータが少なくてもセグメントはできるだけ早く送られます。設定しない場合は、十分な量がたまるまでバッファーに置き、小さなパケットを頻繁に送らないようにします。

#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>

int one = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one) == -1) {
    /* errno を処理する */
}

代償もはっきりしています。Nagle を無効にしたままメッセージを細かく分けて書くと、書き込みごとに小さなパケットになることがあります。RFC 896 が解決しようとした問題に戻るわけで、TCP_NODELAY を使う場合でもメッセージは 1 回で書くほうがよいでしょう。

Linux 専用のオプション

次の 2 つは、tcp(7) が移植性を意図したコードでは使うべきでないと書いている Linux の動作です。

  • TCP_CORK:Nagle の無効化とは逆に、さらにためる方向のオプションです。設定中は満たないフレームを送らず、解除するとたまったものを送り出します。sendfile(2) の前にヘッダーを付けるときに役立つと説明されています。現在の実装には 200 ミリ秒の上限があり、それを過ぎると自動的に送信されます。TCP_NODELAY より優先されますが、TCP_NODELAY を設定した時点で保留中の出力は明示的に送り出されます。
  • TCP_QUICKACK:受信側で ACK を遅らせずにすぐ返す quickack モードに切り替えます。恒久的な設定ではなくモードの切り替えにすぎず、その後はカーネル内部の処理に応じて再びモードが変わることがあります。

標準の外にある変形

RFC 9293 付録 A.3 は、この問題を改善する Nagle アルゴリズムの変形(Minshall の提案)を紹介しています。一部の一般的な OS に実装されており相互運用性には影響しない一方、TCP の標準には取り込まれていないと書いています。どのシステムがこの変形を使っているかは、この記事では確認していません。

まとめ

  • RFC 896 は Nagle アルゴリズムの出典ですが、標準文書ではありません。
  • 要件は RFC 1122 で定まり、現在の基準は RFC 9293 です。Nagle の実装は SHOULD、接続ごとに無効にする手段は MUST、遅延 ACK は SHOULD で、0.5 秒未満が MUST です。
  • 待ちは「小さな 2 回目の書き込み+未確認のデータ+応答できず、実際に ACK を遅らせることを選んだ受信側」が重なったときに起きます。
  • まずメッセージを 1 回で書き、必要なら TCP_NODELAY を使います。TCP_CORK と TCP_QUICKACK は Linux に限った話です。

参考にした文書

リンクと内容は 2026 年 10 月 18 日に確認しました。

ブログへ