18 ポイント 投稿者 GN⁺ 2025-12-23 | 1件のコメント | WhatsAppで共有
  • 分散システムのレイテンシ問題をデバッグする際、最初に確認すべき項目は TCP_NODELAY の設定である
  • Nagleアルゴリズムは1984年のRFC896で提案された方式で、小さなパケット送信時のTCPヘッダーのオーバーヘッドを減らすために設計された
  • しかし、遅延ACK (delayed ACK) メカニズムと組み合わさると、データ送信がACK受信まで遅延し、レイテンシに敏感なアプリケーションの性能を悪化させる
  • 現代のデータセンター環境ではRTTが非常に短く、ほとんどのシステムがすでに大きなメッセージを送信しているため、Nagleアルゴリズムの利点はほとんど失われている
  • したがって、現代の分散システムでは TCP_NODELAY をデフォルトで有効化すべきであり、Nagleアルゴリズムはもはや不要である

Nagleアルゴリズムの背景

  • 1984年のJohn Nagleによる RFC896は、キーボード入力のような小さなデータ送信で発生する40バイトのヘッダーに対して1バイトのデータという4000%のオーバーヘッド問題を解決するために提案された
    • 当時の問題は、ユーザーが1文字ずつ入力するたびに小さなパケットが送信され、ネットワーク効率が低下することだった
    • 解決策は、前のデータがACKされていない状態では新しいセグメントを送信しないよう制限する方式だった
  • このアプローチは当時のネットワーク環境では有効だったが、レイテンシが重要な現代システムには不向きである

NagleアルゴリズムとDelayed ACKの相互作用

  • Delayed ACK (RFC813, RFC1122) は、受信側が即座にACKを送らず、応答データが生じるかタイマーが満了するまでACKを遅らせる方式である
  • NagleアルゴリズムはACKを待って送信を止め、delayed ACK はACKを遅らせるため、双方が互いを待つ膠着状態が発生する
  • John Nagle自身もこの組み合わせを「ひどい組み合わせ」と表現し、2つの機能は独立して導入されたが、併用すると遅延を引き起こすと指摘している

現代環境での問題点

  • データセンター内のRTTは約500μsで、同一リージョン内でも数ミリ秒程度と非常に短い
  • このような環境で1 RTTぶん送信を遅らせることは、性能低下につながる
  • また現代の分散システムは、TLS、シリアライズ、プロトコルオーバーヘッドなどにより、すでに十分に大きなメッセージを送信しているため、1バイトパケット問題はほとんど存在しない
  • 小さなメッセージの最適化は、今ではアプリケーション層で処理されている

TCP_NODELAYの必要性

  • レイテンシに敏感な分散システムでは、TCP_NODELAY を有効にして Nagleアルゴリズムを無効化することが推奨される
    • これは「非効率」でも「誤った設定」でもなく、現代のハードウェアとトラフィック特性に合った選択である
  • 著者はTCP_NODELAYがデフォルト値になるべきだと主張する
    • 一部の「write() 呼び出しごとに送信する」コードは遅くなる可能性があるが、そのようなコードは根本的に修正されるべきである

その他の関連オプション

  • TCP_QUICKACK オプションはACK遅延を減らすが、移植性の問題と一貫しない動作のため、根本的な解決策ではない
  • 核心的な問題は、カーネルがアプリケーションの意図した時点より長くデータを保持することであり、write() 呼び出し時に即座に送信されるべきである

結論

  • Nagleアルゴリズムは、過去にネットワーク効率を高めるための優れた発明だったが、
    現代の高速ネットワークと分散システム環境では、むしろ遅延を招く時代遅れの機能になっている
  • したがって、TCP_NODELAY を常に有効化することが現代システム設計の基本原則として提示されている

1件のコメント

 
GN⁺ 2025-12-23
Hacker Newsの意見
  • 昔のマルチポイントネットワーキング時代に作られた Nagle アルゴリズムの背景を説明している
    当時は複数のホストが1つのイーサネットチャネルを共有していたため、衝突を避けるために CSMA/CD を使っていた
    しかし今日のイーサネットの大半はポイントツーポイント構成で、送受信を同時に行えるフルデュプレックス環境である
    したがって CSMA はもはや不要であり、TCP_NODELAY を設定して Nagle アルゴリズムを無効化するのは、ほとんどの場合で合理的だと思う
    • CSMA に関する動機が Nagle アルゴリズムの設計に実際にあったのか、それとも単に時代背景に触れているだけなのか気になる
    • 実際のところ、Nagle アルゴリズムは単なる**パケット統合(coalescing)**のためのものだった
      それがデフォルト設定になったのは、ネットワーク史上の大きな失敗の1つだと思う
    • ちなみにイーサネットは CSMA/CD、WiFi は CSMA/CA を使う
      2014年ごろにデータセンタースイッチを交換した際、10Mbit ハーフデュプレックスをサポートしていなかったため、一部の旧式機器を残さざるを得なかった経験がある
    • アプリケーションがパケットサイズを気にしない、あるいは遅延に敏感でない場合には、Nagle はかなり理にかなっている
      小さすぎるパケットの生成を防いでくれる
    • ネットワークレイヤーの混同があるように思う
      Nagle は TCP 層の最適化であり、小さなパケットをまとめて効率を上げる役割を持つ
      CSMA は物理/データリンク層の問題で、Nagle とは別物だ
  • ゲーム開発中にネットワーク遅延の問題をデバッグしていてこの記事を見つけた
    Go で書かれたバックエンドはデフォルトでTCP_NODELAYが設定されていたので原因ではなかったが、Nagle の問題認識に関する部分は興味深かった
    以前の議論もあり、このスレッド を参照できる
    • Julia Evans の良い記事もおすすめ
      DICOM プロトコルのようなチャット型通信では、TCP_NODELAY=1 に設定するとスループットが大きく向上する
    • どんなゲームを開発しているのか気になる。自分もEbitengineGolangでのゲーム開発が好きなので興味がある
  • Nagle 本人が10年ほど前に語っていたところによると、本当の問題はdelayed ACKだという
    関連リンク 参照
    今どきのワークロードでは delayed ACK に大きな利点はないと思う
    HTTP 中心の現代環境では、Nagle も delayed ACK も両方無効にするほうがよいと考える
    • 元記事でもこれに触れている
      データセンター間のRTT が数百マイクロ秒レベルなので、1 RTT でも遅らせるのはむしろ損になりうる
  • ポーランド語で “nagle” は「突然」という意味らしく、アルゴリズム名とあまりに合っていて驚いた
    • Nominative determinism のまた別の例のようだ
      Wiki リンク
    • 面白いことに、“NODELAY on” のときは突然送り、“off” のときはまとめて送る、というふうに単語の意味が両方の設定を含んでいるようにも見える
    • 実際には John Nagle という人物が書いた RFC 896 に基づくアルゴリズムである
  • Nagle アルゴリズムがカーネルのデフォルトになっているのはおかしいと思う
    いつ送るか、いつバッファリングするかはアプリケーションが決めるべきだ
  • 記事で MSG_MORE に触れていないのは意外だった
    Linux では追加データがまもなく送られることをカーネルに知らせるヒントで、ヘッダーとデータを分けて送るときに便利だ
    io_uring と組み合わせるとさらに効率がよい
    • 実際には、1回のシステムコールで複数のデータ片をコピーなしで送ることもできる
  • Nagle アルゴリズムの問題は、**ソケット API に即時送信(flush)**機能がないことだと思う
    即時応答が必要なメッセージの後でバッファを空にして送る機能があるとよい
    最近の TCP チャネルでは同期・非同期メッセージが混在していて、さらに複雑だ
    SCTP のようなプロトコルがもっと広く使われてほしい
    • ストリーム API に flush 機能がない点には同意する。明らかな設計上の欠落だと思う
    • ネットワーク I/O をファイルのように扱おうとするUNIX 哲学は理解できるが、メッセージ指向 API が最初からあればこうした問題はなかったはずだ
      TLS のようなラッピングでもメッセージ境界を見つけるのが面倒になる
    • すべての send に MSG_MORE を付け、最後だけ外せば、間接的に flush 効果を得られそうだ
    • ストリーム API はいろいろと不便だ
      理想的には「バッファリング可」ビットを立てて大きな送信を分割し、最後に「即時送信」を指定できるべきだ
      TCP_CORK はそれに少し近い代替手段だが、やや雑な感じがする
      ファイル I/O でも似た問題がある
    • TCP_CORK が何なのか気になる
  • (2024) 以前の議論は このリンク にあった
  • Oxide and Friends ポッドキャストのエピソード でこの話題を扱っている
    かなり面白い内容だ
    • Oxide はサーバー OS とハードウェアを新たに設計する会社なので、従来のプロトコルを見直すというアプローチがブランド哲学によく合っている
  • Nagle アルゴリズムはポリシーをカーネルに入れたもののようで違和感がある
    アプリケーションが遅延とスループットのバランスを自分で調整できるべきだ
    • delayed ack がないなら合理的なアルゴリズムであり、TCP スタックの一部として存在するのは、その層で問題を解決しようとしたからだ
      しかしアプリケーションレベルで実装するにはunacked dataを知る必要があり、非効率になる
    • 理論的にはその通りだが、現実には大半のユーザースペースコードはネットワーク下層を気にしない
      単純な20ms flush タイマーがあるだけでも、ずっとましだっただろう
    • 実際には TCP_NODELAY はソケット単位で設定するので、アプリケーションが直接選ぶユーザースペースの判断に近いと思う
    • あるプログラムのトレードオフが別のプログラムに影響することもあるので、カーネルがシステム全体の観点から仲裁役を担う必要があると思う