🐢

必ずTCP_NODELAYだ。毎度毎度、うんざり。

2024/05/17に公開

著者: マーク・ブルッカー


以下は、マーク・ブルッカー(Marc Brooker)のブログ投稿『It’s always TCP_NODELAY. Every damn time.』の日本語訳である。

⚠️ OpenBSDのメーリングリスト

ジョブ・スナイデルス(Job Snijders)が、OpenBSDのメーリングリストで、sysctlにNagleを無効にする機能を加える考えを示している。

皆さんへ、

1980年代初頭、「Nagleアルゴリズム」とも呼ばれるTCP輻輳制御を改善方法が提案された。RFC 896を参照のこと。

Nagleのアルゴリズムは、ユーザランドのアプリケーションの連続した小さなパケットを1つのTCPパケットにまとめることができる。これはレイテンシの増加という代償を伴う: 送信側は、リモート側から確認応答を受け取るが、フルサイズのセグメントを送信するのに十分な追加データが蓄積されるまで、ローカルでデータをキューに入れる。

このアプローチは、複数のユーザが1200ボーの回線で同時に作業していた40~50年前には有益だったかもしれない。Nagleのアルゴリズムは、送信するデータが小刻みに増加する場合、極小セグメントの送信を抑制する。トレードオフは、「スループットの向上」と引き換えに「ある程度の双方向性を犠牲にする」ことである。

ここ数日、現代におけるNagleのアルゴリズムの適用性と有用性が疑問視されるようになった。Nagle自身が述べているように、Nagleのアルゴリズムは遅延Acks(RFC 813)と負の相互作用をする: https://news.ycombinator.com/item?id=10608356、より詳しい説明: https://datatracker.ietf.org/doc/html/draft-minshall-nagle

しかし、「最新のサーバは、数百マイクロ秒でも膨大な量の作業を実行できることを考えると、たとえ1 RTTでもデータ送信を遅らせることは、明らかな勝利とは言えない」と主張する人もいる。https://brooker.co.za/blog/2024/05/09/nagle.html

基本的には、sshhttpdiscsidrelayedbgpdunwindなど、さまざまなアプリケーションがNagleのアルゴリズムを無効にしている。ブルームと私は、Nagleを明示的に有効にするアプリケーションを知らない。

標準規格は、RFC 9293のセクション3.7.4で、「TCP実装は、短いセグメントを結合するためにNagleアルゴリズムを実装する必要がある(SHOULD)。しかし、アプリケーションが個々の接続でNagleアルゴリズムを無効にする方法がなければならない(MUST)」と記載している。

そこで、さらに一歩進んで、システム全体でアルゴリズムを無効にできるようにしてはどうだろうか? :-)

以下の変更で、sysctl net.inet.tcp.nolayを導入し、これを1に設定すると、単純にすべてのTCPソケットでTCP_NODELAYが設定される。

net.inet.tcp.nolayを1に設定しても、アプリケーションはgetsockopt()setsockopt()を使用してTCP_NODELAYを検査し、無効にできることに留意する。

おそらく将来的には、さらに研究と熟考を経て、このsysctlのデフォルトを0から1に変更することになるのではないだろうか?

敬具、

Job


ありがたいことに、もう1980年代ではない。

分散システムのレイテンシ問題をデバッグする際、私が最初にチェックするのは、TCP_NODELAYが有効になっているかどうかだ。私だけではない。私が知っている分散システム構築者は皆、この単純なソケット・オプションを有効にすることで、レイテンシ問題がすぐに解決され、何時間も浪費している。これは、デフォルトの動作が間違っていること、そしておそらくこの概念全体が時代遅れであることを示唆している。

まず、私たちが何を話しているのかを明確にしよう。1984年[1]のJohn Nagle(ジョン・ネーグル)のRFC896に勝る情報源はない。まず、問題提起である:

小さなパケットには特別な問題がある。キーボードから発信される1文字メッセージの送信にTCPを使用する場合、典型的な結果は、各バイトの有用なデータに対して41バイトのパケット(1バイトのデータ、40バイトのヘッダ)が送信されることになる。この4000%のオーバーヘッドは気に障るが、負荷の軽いネットワークでは許容範囲である。

要するに、NagleはTCPヘッダのコストをもっとうまく均して、ネットワークのスループットを向上させることに興味を持っていた。最大40倍のスループット向上だ! これらの小さなパケットには、主に2つの原因があった。それはシェルのような、人間が1度に1バイトずつ入力する対話型アプリケーションと、writeコールを何度も使ってカーネルにメッセージを垂れ流すような実装の不十分なプログラムである。この問題を修正するためのNagleの提案はシンプルでスマートだった。

シンプルでエレガントな解決策を発見した。

その解決策とは、ユーザから新しい送信データが到着したとき、そのコネクション上で以前に送信されたデータが未応答のままであれば、新しいTCPセグメントの送信を禁止するというものである。

多くの人がNagleアルゴリズムについて話すとき、タイマーについて話すが、RFC896はネットワーク上の往復時間以外のいかなるタイマーも使用していない。

Nagleアルゴリズムと遅延ACK

Nagleの素晴らしく巧みな提案は、もう一つのTCP機能である遅延ACKとの相互作用が不十分だった。遅延ACKの背後にある考え方は、少なくとも送り返すデータがあるまで(例えば、ユーザの入力をエコーバックするtelnetセッション)、またはタイマーが切れるまで、パケットの確認応答の送信を遅らせることである。1982年のRFC813は、ACKを遅延させることを提案した最初のものである。

データの受信者は、ある状況下では確認応答の送信を控え、その場合、後で確認応答を送信するようにタイマーを設定しなければならない。しかし、受信者は、他のイベントが介在して、タイマーの割り込みが必要なくなることが合理的に推測できる場合にのみ、これを行うべきである。

これは、1989年のRFC1122でさらに正式なものとなっている。この2つの機能の相互作用が問題を引き起こす。Nagleのアルゴリズムは、応答ACKを受信するまでデータの送信をブロックするが、遅延ACKは応答の準備ができるまでACKを遅らせる。パケットを最大に保つには最適だが、レイテンシの影響を受けやすいパイプライン・アプリケーションにはあまり適していない。

この点は、Nagle自身が何度も指摘している。例えば、以下のHacker Newsのコメント:

それは今でも腹立たしいことだ。本当の問題は、tinygram(小さなパケット)の防止ではない。ACKの遅延と、あの馬鹿げた固定タイマーだ。この2つは同時期にTCPに導入されたが、それぞれ独立していた。私はtinygram防止(Nagleアルゴリズム)を行い、バークレイは遅延ACKを行った。どちらも1980年代前半のことだ。この2つの組み合わせはひどいものだ。

システム構築者にとって、これはよくある状況のはずだ。つまり、システムの2つの合理的な機能が相互作用して、望ましくない動作を生み出してしまうのだ。この種の相互作用は、プロトコルの設計を難しくしている要因の一つである。

Nagleに罪はないのか?

残念ながら、遅延ACK[2]だけではない。遅延ACKや愚かな固定タイマーを抜きにしても、Nagleのアルゴリズムの動作は、おそらく分散システムにおいて私たちが望むものではないだろう。データセンター内のRTTは通常500マイクロ秒程度で、同じ地域のデータセンタ間では数ミリ秒、世界中を一周すると最大で数百ミリ秒になる。最新のサーバがわずか数百マイクロ秒でも実行できる膨大な作業量を考えると、たとえ1RTTでもデータ送信を遅らせても、明らかに勝利とは言えない。

より明確に説明するため、Nagleのアルゴリズムの背後にある正当化に戻ろう。ヘッダのコストを均し、シングルバイト・パケットの40倍のオーバーヘッドを回避するというものである。しかし、今どきシングルバイト・パケットを送る人はいるだろうか? ほとんどの分散データベースやシステムはそうではない。その理由の一つは、単に伝えたいことが多いからであり、一部はTLSのようなプロトコルの追加オーバーヘッドのためであり、一部は符号化とシリアル化のオーバーヘッドのためである。しかし、大部分は、もっと言いたいことがあるようだ。

小さなメッセージを送らないという中心的な懸念はまだ非常に現実的なものだが、私たちはそれを非常に効果的にアプリケーション層に押し込んだ。Nagleのアルゴリズムがどのようなものであれ、JSONでラップして1バイトずつ送信するのはあまり効率的ではない。

Nagleは必要か?

まず、議論の余地のない見解は、最新のデータセンタのハードウェアで動作するレイテンシに敏感な分散システムを構築している場合、心配することなくTCP_NODELAYを有効にする(Nagleのアルゴリズムを無効にする)。悪いと思う必要はない。罪ではない。大丈夫だ。先に進め。

もっと議論の余地があるのは、Nagleのアルゴリズムは、トラフィックとアプリケーションの組み合わせや、現在のハードウェアの能力を考慮すると、最新のシステムには必要ない、ということだ。言い換えれば、TCP_NODELAYがデフォルトであるべきだ。そのため、「write every byte」コードのいくつかは、そうでない場合よりも遅くなるかも知れないが、効率性を重視するのであれば、そのようなアプリケーションはとにかく修正すべきだ。

Creative Commons Attribution 4.0 International License.

更新

  • 2024.5.17
脚注
  1. ここでは触れないが、RFC896は、コンピュータ・ネットワークにおける準安定動作について、私が見つけた中で最も古い記述の一つでもある。その中で、Nagleは次のように述べている。「この状態は安定している。飽和点に達した後、破棄するパケットを選択するアルゴリズムが公平であれば、ネットワークは劣化した状態で動作し続けることになる。」 ↩︎

  2. このことがインターネット上で広まるにつれ、多くの人がTCP_QUICKACKについて尋ねてきた。移植性の欠如や、奇妙なセマンティクス(冗談抜きでman ページを読んでほしい)など、いくつかの理由から、私はTCP_QUICKACKに手を出さない。もっと大きな問題は、TCP_QUICKACKは、カーネルが私のプログラムが望むよりも長くデータを保持するという根本的な問題を解決していないことだ。私がwrite()と言ったら、write()ということだ。 ↩︎

GitHubで編集を提案

Discussion