Skip to content

ネットワークトランスポートの内部構造

翻訳についての注記: 本ドキュメントは英語原文 06-scaling/14-network-transport-internals.md を日本語に翻訳したものです。コードブロックおよび表内の技術用語は原文のまま維持しています。

TL;DR

本書のすべての分散システムの数字 — タイムアウト予算、レプリケーションラグ、テールレイテンシ — は、ひとつの物理的事実に行き着きます: 2台のマシン間の往復には光速で決まる下限があり、プロトコルは往復を消費するのです。コールドなHTTPSリクエストは、最初の有用なバイトが動く前に、TCPのハンドシェイク、TLSのハンドシェイク、スロースタートの慎重な立ち上がりを支払います。クロスリージョンのRTT 80msでは、それは300ms超の純粋なプロトコルコストであり、だからこそコネクション再利用がネットワーキングにおける単一最大のレバレッジなのです。ハンドシェイクの下では、輻輳制御が一度流れ始めたバイトの速度を決め(throughput ≤ cwnd / RTT、そして損失下ではMathisの上限 ≈ MSS / (RTT × √p) — 損失とレイテンシは掛け算になります)、ヘッドオブラインブロッキングは直された場所の一つ下の層で再浮上し続けます: HTTP/2はHTTP/1.1のリクエスト直列化を直したものの、TCPのバイトストリームのブロッキングを相続し、QUICはトランスポートをUDP上に再構築して — ストリーム単位の配送、1-RTTの統合ハンドシェイク、IP変更を生き延びるコネクションID — それを直しました。そのすべての下にはカーネルパス — 割り込み、epoll、ソケットバッファ — があり、ほぼすべての人にとって十分で、バイパス(XDP、DPDK)するのはリクエストではなくパケットが仕事のロードバランサやプロキシだけです。本章はコストモデルを物理から積み上げ、tcpdumpする前にコネクションの振る舞いを予測できるようにします。上位のポリシー層 — プールのサイズ設定、DNS、ドレイン — はDNSとコネクション管理に、フリート視点はロードバランシングにあります。


物理の請求書: RTTがコストの単位

光ファイバー中の光は約 2/3 c — およそ1ミリ秒あたり200km、つまり直線距離100kmあたりRTT 1msで進みます。実際の経路は大円より20〜50%長い(ファイバーは測地線ではなく鉄道や海底に沿う)ので:

経路大円距離現実的なRTT下限典型的な実測値
同一データセンター~0.05–0.5 ms0.1–1 ms
同一メトロ (AZ間)< 100 km~1 ms1–2 ms
米国東西海岸間4,100 km~41 ms60–70 ms
ニューヨーク → ロンドン5,570 km~56 ms70–80 ms
米国 → アジア太平洋10,000+ km~100 ms130–180 ms

2つの帰結が他のすべてを組織します:

  1. RTTは床であり、目標ではありません。 どんなプロトコルの工夫も、ハードウェア更新も、ベンダー製品もこれを動かせません。レバーは往復を減らす(プロトコル設計、キャッシング、コネクション再利用)と距離を縮める(エッジ拠点、CDNマルチリージョン配置)だけです。
  2. バイトではなく往復を数える。 APIを支配する小さなペイロードでは、転送時間は往復回数の前ではノイズです。RTT 80msでのコールドコネクションの台帳:
text
DNS lookup (uncached)           1 RTT-ish     ~80 ms   (resolver-dependent)
TCP handshake (SYN/SYN-ACK/ACK) 1 RTT          80 ms
TLS 1.3 handshake               1 RTT          80 ms   (TLS 1.2: 2 RTTs)
HTTP request + response         1 RTT          80 ms
                                          ─────────
First byte, cold:               ~320 ms — of which 0 ms is your server.

Same request on a warm, pooled connection:  ~80 ms.  (QUIC 0-RTT: also ~80 ms, cold.)

3つ目の構造的コストは**帯域遅延積(BDP)**です: 経路は 帯域 × RTT バイトを飛行中に保持でき、送信側はそれを埋めるだけの大きさのウィンドウを必要とします。RTT 100msの1Gbps経路は12.5MBを保持します — 送信側の輻輳ウィンドウ(あるいは受信側の広告ウィンドウ、あるいはサイズを誤ったソケットバッファ)がそれより小さければ、スループットは比例して落ちます: その経路上の256KBウィンドウは 256 KB / 100 ms ≈ 20 Mbps、パイプの2%に頭打ちされます。パケット損失ゼロで、ウィンドウサイズ以外にどこにも見える設定ミスなしに、です。


TCP: ハンドシェイク、スロースタート、ウィンドウの機構

TCPは、信頼性のない パケットネットワークの上に、信頼性のある順序付きバイトストリームを提示します。以下はすべてその抽象の代金です。

ハンドシェイク(SYN → SYN-ACK → ACK)はデータの前に1 RTTを消費し、シーケンス番号の同期とサーバーメモリの防御のために存在します: ハーフオープン接続は状態を消費するため、SYNフラッドはそこを攻撃し、SYNクッキー(状態をシーケンス番号に符号化しサーバーが何も保存しない)はそこを守ります。TCP Fast OpenはSYNにデータを載せようとして、実世界でほぼ失敗しました — ミドルボックスが見慣れないパケットを落としたのです。これは*プロトコル硬直化(ossification)*の教科書的教訓になりました: デプロイ済みのインターネットは観測可能なプロトコルの振る舞いの周りに石灰化し、いまトランスポートを進化させる唯一の方法は、ミドルボックスの視界を超えて暗号化することです(QUICの真の妙手、後述)。

スロースタート。 新しいコネクションは経路の容量を知らないので、探ります: 輻輳ウィンドウ(cwnd)は10セグメント(約14.6KB、RFC 6928)から始まり、損失か閾値までRTTごとに倍増します。14.6KBから12.5MBのBDPに達するには約10 RTT — RTT 100msなら、TCPがパイプを使えるようになる前にまる1秒です。短いフローが決して全帯域に達しない理由、「iperf ではスループットが出るのに実リクエストは遅い」が想定内である理由(iperfは長いフローを流し、あなたのRPCは短いフローを流す)、そしてコネクション再利用が二重に効く理由がこれです: プールされたコネクションはハンドシェイク立ち上がりの両方を支払い済みなのです。(ひとつ注意: slow_start_after_idle を無効にしない限り、cwnd は無活動の後に下がって戻ります — ウォームなコネクションは、喋り続けている場合にのみウォームです。)

ウィンドウがすべてを支配します:

text
throughput ≤ min(cwnd, receiver_window) / RTT

Sustained throughput under random loss (Mathis et al., 1997):
throughput ≈ (MSS / RTT) × (1 / √p)        p = loss probability

1460 B MSS, 80 ms RTT, 0.1% loss:  1460/0.08 × 1/√0.001  ≈ 0.58 MB/s ≈ 4.6 Mbps
Same loss at 1 ms RTT (intra-DC):                        ≈ 370 Mbps

この表は二度読んでください: 同じ0.1%の損失率が、80倍のRTTでは80倍のスループットを奪います。 損失とレイテンシは掛け算です。「わずかに損失のある」WANリンクがクロスリージョンレプリケーションを壊滅させる一方、同じ損失がDC内では気づかれない理由であり、損失ベースの輻輳制御が再考されねばならなかった理由です(次節)。

2つの小さなタイマーが不釣り合いな量の苦しみを生みます。Nagleのアルゴリズムは直前のパケットがACKされるまで小さな書き込みをまとめ、遅延ACKは相乗りを期待してACKを最大40ms保留します。それぞれ単体では合理的ですが、組み合わさると、write-write-read パターンが40msの停止にデッドロックします — RPCシステムの古典的な謎レイテンシです。まともなRPCスタックはすべて TCP_NODELAY を設定します。それを前提とし、かつ検証すべきです。


輻輳制御: CUBIC、BBR、バッファブロート

輻輳制御は「どれだけ速く送ってよいか?」に連続的に答えるもので、インターネットの実質的なアドミッション制御システムです — 惑星規模で実装されたバックプレッシャーです。

**損失ベースの制御(Reno、次いでLinuxデフォルトのCUBIC)**はパケット損失を輻輳シグナルとして扱います: 損失まで cwnd を育て、削り、再び育てる(CUBICは3次曲線で再成長し、長BDP経路の回復を速める)。このモデルには構造的欠陥が2つあります。第一に、容量を見つけるために損失を作り出す必要がある — キューを溢れるまで満たします。第二に、輻輳による損失とランダムな損失(無線干渉、不調な光モジュール)を区別できず、何のシグナルでもない損失に対してスループットを切り下げます — 上述のMathisペナルティです。

キューを満たすことには名前があります: バッファブロート。ルーターやホームゲートウェイの深いバッファはパケットを落とさず、数秒のキューイング遅延として吸収します。パイプは「フル活用」されている一方、すべてのパケットは行列に座っています — 損失ゼロのまま、負荷下でレイテンシが10〜100倍に登ります。バルク転送が走るたびにpingが膨らむなら、それはバッファブロートであり、帯域不足ではなくキューイング理論の事実です(稼働率カーブ)。修正はネットワーク側(AQM: CoDel/FQ-CoDelは早く落として早くシグナルする。ECNは落とす代わりにマークする)と、送信側の世界モデルに宿ります:

**BBR(Google、2016年。現行はv3)**は損失をシグナルとすることを完全に放棄します。経路のボトルネック帯域最小RTTを連続的に推定し、ちょうど estimated_bw でペーシングして飛行中データを約1 BDPに保ちます — 損失ベース制御が構造的に行き過ぎる動作点です。帰結: BBRはランダム損失にほとんど反応せず(損失の多いWAN経路では巨大 — これだけでクロスリージョンのスループットが10倍になることもある)、バッファを満たしません(低いキューイング遅延)。ただし代償もあります: 周期的にプローブしなければならず(容量発見のため一時的に多く送る)、初期バージョンはリンクを共有するCUBICフローを飢えさせ、RTT感受性は低レイテンシのフローに有利に働きます。Googleはgoogle.comとYouTubeで運用しており、Linuxでは sysctl ひとつの距離です。損失に晒されるクロスリージョン経路では、CUBIC対BBRの計測は手に入る最も安価な大きな勝利のひとつです。

データセンター内では問題が反転します: RTTはマイクロ秒、フローは短く、障害モードはインキャストです — scatter-gatherのファンアウト(ファンアウトパターン)で100台のサーバーが1つのクエリに同時に応答し、同期したバーストがトップオブラックスイッチの浅いバッファをマイクロ秒で溢れさせます。ランダム損失のモデルは役立ちません。答えはECNベースの制御(DCTCPはマークされたパケットの割合に反応し、キューをほぼ空に保つ)、応答へのジッター付与、ファンアウト幅の上限です。scatter-gatherクエリのp99に硬いステップ関数があるなら、どのサーバーを責めるより先にスイッチのバッファ占有を見てください。


TLS 1.3: 一度払えば、再開は無料

TLS 1.3(RFC 8446)はハンドシェイクを2往復から1往復に削りました — 鍵交換、暗号スイート交渉、証明書配送が単一のフライトに圧縮され、最初のメッセージ以降はすべて暗号化されます(証明書も含む。受動的な観測者からどのサイトかという視界を奪い、Encrypted Client Helloで完成する)。レガシー暗号の削除は脆弱性クラスの丸ごとの削除でもありました。より古いものを交渉する理由はありません。

システム設計が宿るのは再開(resumption)です。再訪クライアントはセッションチケットを提示して1 RTTで再開します — あるいはゼロで: 0-RTTはアプリケーションデータを最初のフライトと一緒に、前セッションの鍵で暗号化して送ります。落とし穴は実装ではなく本質です: 0-RTTパケットはそれを捕獲した攻撃者によりリプレイ可能で、サーバーには見分けられません。ルール: 0-RTTには冪等なリクエストのみ(冪等性) — CDNは GET を受け入れ、安全でないメソッドを0-RTTから弾きます。あなたのAPIゲートウェイも同じ強制をするか、無効化しなければなりません。

運用上の注意を2つ。セッションチケット鍵はフリート全体の秘密であり、ローテーションが必須です(漏れた長寿命チケット鍵は再開セッションを遡って復号します — これは鍵ローテーション問題であり、無視すれば前方秘匿性を静かに壊します)。そしてサービスメッシュでは、ホップごとのmTLSはハンドシェイク税をサイドカーホップごとに支払うことを意味します — それを賄えるものにしているのがメッシュのコネクションプーリング(サイドカーパターン)です。


HTTP/1.1 → HTTP/2 → HTTP/3: ヘッドオブラインブロッキングは下に降りる

この系譜は、ひとつのバグをスタックの下へ追いかける物語です。

HTTP/1.1はコネクションあたり同時1リクエストです。パイプライン(複数送り、順に受ける)はデプロイで失敗しました — 遅い1応答が後続すべてを塞ぐ、アプリケーションレベルのヘッドオブラインブロッキングです — ので、ブラウザはホストごとに6本の並列コネクションを開き、6倍のハンドシェイクと6倍のスロースタートを支払いました。ドメインシャーディングはこのハックの拡大版です。

HTTP/2は、バイナリフレーミングとヘッダ圧縮(HPACK — ヘッダはリクエスト間で高度に反復的で、高リクエストレートでは圧縮が効く)により、1本のコネクション上で並行ストリームを多重化します。1コネクション、1ハンドシェイク、全ストリームで共有される1つの輻輳ウィンドウ。しかしTCPはコネクション全体の順序どおりのバイト配送を保証します: パケットを1つ失えば、すでにバイトが到着済みのストリームも含め、再送まで全ストリームが停止します。HTTP/2はアプリケーションのHOLを直し、トランスポートのHOLを相続したのです — 高損失下ではHTTP/1.1の6コネクションより厳密に悪い。あの6本の運命は少なくとも独立していたからです。クリーンなネットワークではH2の圧勝。損失の多いネットワークでは、単一共有コネクションが弱点です。(リソースを先送りするはずだったH2のサーバープッシュは、数年後にChromeから削除されました — キャッシュを知るクライアントはサーバーの推測に勝ち、誰も頼んでいないデータのプッシュは、節約しようとした帯域そのものを浪費します。)

HTTP/3は同じセマンティクスをQUICのストリームに写像し、ストリームは独立に失敗します。バグはついにトランスポート層で死にます — 元々そこにあったのです。

内部RPCでは、gRPC-over-H2が標準であり続けます: データセンターネットワークは低損失で(トランスポートHOLは滅多に噛みつかない)、コネクションはメッシュが管理します。内部で実際に噛みつくH2の詳細はストリーム並行数の上限(MAX_CONCURRENT_STREAMS、一般に100)です: お喋りなクライアントが天井に当たると、リクエストはクライアント側でキューに積まれます — サーバーからは見えず、クライアント側レイテンシとしてのみ現れます。サーバー容量を足す前にこれを疑ってください。


QUIC: UDPの上に再構築されたトランスポート

QUIC(RFC 9000)は、40年分の後知恵で書かれたTCPの第二稿であり、UDP上にデプロイされています。世界のミドルボックスを通して新しいIPプロトコル番号をデプロイするのは不可能だからです — またも硬直化。再設計が買ったもの:

  • 1-RTTの統合ハンドシェイク、0-RTTの再開。 トランスポートとTLS 1.3のハンドシェイクは積み重ねではなく融合されています。TLSはQUICの上の層ではなく、QUICの構成部品です。コールド接続は1 RTT、再開は0(リプレイのルールは上と同じ)。
  • 第一級のトランスポートオブジェクトとしてのストリーム。 ストリームAの損失はストリームBを決して止めません — 再送とフロー制御はストリーム単位です。これがHOLの修正であり、より良いRPC基盤でもあります: 1リクエスト1ストリームなら、どのリクエストも他のリクエストのパケットを待ちません。
  • 4タプルの代わりのコネクションID。 TCPコネクションは(送信元IP, 送信元ポート, 宛先IP, 宛先ポート)そのものであり、どれかが変われば死にます。QUICコネクションはIDなので、アドレス変更を生き延びます — リクエスト中のWiFiからセルラーへの移行や、より日常的には、NATのポート再バインド(これは一日中、アイドルなTCPコネクションを静かに殺しています。WebSocketを大規模運用する誰にでも聞いてください)。ロードバランサにとってはコネクションIDがルーティングキーでもあります: QUIC対応のL4バランサはこれでルーティングし、クライアントの移動が誤ったバックエンドに着地しないようにします(ロードバランシング)。
  • 暗号化されたトランスポートヘッダ。 シーケンス番号、ACK、コネクション状態はネットワークから見えません — プライバシー(だけ)のためではなく、ミドルボックスがそれらへの依存を育てられないようにし、プロトコルを進化可能に保つためです。請求書: ネットワーク運用者は受動的なRTT/損失の観測性を失い(スピンビットが交渉された欠片)、DDoSスクラビングは難しくなります。
  • より良い損失回復。 単調増加のパケット番号(再送は新しい番号を得る)はTCPの再送曖昧性を消し、より豊かなACKレンジはRTT推定を締めます。

コストは現実です。QUICは同じバイト数に対してTCPの2〜3倍のCPUを燃やします — 数十年分のTCPハードウェアオフロード(セグメンテーション、チェックサム、kTLS)が適用されないためです。ただしUDP GSOと新興のNICサポートが差を狭めています。すべてのパケットはユーザー空間でパケット単位に暗号化されます。増幅攻撃防御は、アドレス検証まではサーバーをクライアント初期バイトの3倍に制限します(QUICが1200バイト以上の初期パケットを義務付ける理由)。そして一部のネットワークは今もUDPをブロックまたは絞ります: すべてのHTTP/3デプロイはTCPフォールバックを保持し、Alt-Svc/HTTPS-RRでクライアントをH3へ誘導します — フォールバック率を監視してください。上昇するそれは、フリートを静かにTCPの挙動へ戻します。

最も効く場面: モバイルと損失の多いラストマイル(独立ストリーム+マイグレーション)、短フロー支配のワークロード(1-RTTセットアップ)、高RTT経路。最も効かない場面: クリーンなデータセンターネットワーク上のウォームなプール済みコネクション — 内部gRPCが移行していない理由です。


カーネルパス — そしてバイパスすべきとき

パケットの到着はガントレットです: NICのDMA → 割り込み → ドライバのポーリング(NAPI) → softirqでのIP/TCP処理 → ソケットバッファ → epoll の起床 → read() によるユーザー空間へのコピー。各ステージには予算があり、それを知ればどの最適化が本物かがわかります:

  • 割り込みモデレーションとRSS。 NICは割り込みをまとめ(レイテンシ対CPUのトレード)、フローをCPUキューにハッシュ分散(RSS)して1コアがボトルネックにならないようにします。エレファントフローは依然1コアに落ちます — 兄弟コアが暇な中での per-core softirq 飽和が署名です。
  • システムコールは約1μs(Spectre対策後はより高い)。毎秒100万パケットでは、パケットごとのシステムコールこそがワークロードです。バッチング(sendmmsg)、epoll(10万ソケットのレディネスを1コールで — C10Kの答えであり、nginxからNode、Tokioまであらゆるイベントループの中のエンジン)、io_uring(カーネルと共有するsubmit/completeリング。システムコールそのものをバッチで消す)は、同じ税へのエスカレートする答えです。
  • コピーは帯域を食います。 ファイルからソケットへの read()+write() は、CPUが一度も検査しないバイトのためにユーザー/カーネル境界を2回渡ります。sendfile() はバイトをカーネル内に留め、kTLSはTLSでもそれを成立させます(Netflixはこの方式で動画を配信しています)。GSO/GROはセグメンテーションをまとめ、パケットごとのスタックコストを償却します。
  • conntrackは頼んでいない状態です。 カーネルのコネクション追跡(NAT、ステートフルファイアウォール、kube-proxy)は上限つきハッシュテーブルです。高い接続レートで満杯になると、新規接続がノード全体で失敗し、唯一の手がかりは nf_conntrack: table full です。見えない天井の論理の一層下です。

必要量がおよそ毎秒コアあたり100万パケットを超えると、スタック自体がコストになり、脱出口はXDP/eBPF(検証済みプログラムをドライバで、スタックの前に実行 — 約100nsでパケットを破棄・書き換え・リダイレクト。CloudflareがDDoS洪水を落とす方法であり、MetaのL4バランサKatranがコモディティサーバーでラインレート転送する方法)とDPDK(NICをユーザー空間に渡してポーリング: 最大スループット、永遠に100%で固定される1コア、そしてTCPがくれたすべての再実装)です。判断則は正直な自己分類です: あなたの仕事がリクエスト(パース、判断、応答)なら、カーネルパスはボトルネックから程遠い — 自分のアプリケーションをプロファイルしてください。あなたの仕事がパケット(バランス、フィルタ、転送 — 単位あたりの仕事がナノ秒)なら、スタックがボトルネックであり、XDPが現代のデフォルトです。これはまさにL4/L7の分割です: L4バランサはパケットの仕事でXDPやカーネルバイパスの上に建てられ、L7プロキシはリクエストの仕事でepollの上で幸せに暮らします。


障害モード

MTUブラックホール。 トンネル(VPN、VXLAN、IPsec)が経路MTUを1500未満に縮め、フルサイズのパケットは断片化が必要になり、ルーターのICMP「too big」シグナルが過剰防衛のファイアウォールにブロックされ、送信側は永遠に学べません。署名: 小さなリクエストは成功し、大きなものは永遠にハングする — ハンドシェイク(小)は通り、ペイロード(フルMSS)は消えます。ヘルスチェックは緑のまま。修正: ICMPタイプ3/4を許可、トンネル端でMSSクランプ、あるいはPLPMTUD。QUICは初期パケットを1200バイト以下に抑えることで最悪部分を回避します。

アイドルタイムアウトの待ち伏せ。 NATやファイアウォールがアイドル接続の状態を黙って落とし、どちらのエンドポイントも次の書き込みがタイムアウトするまで — 数分後、リクエストの奥深くで — 気づきません。TCPキープアライブのデフォルトは2時間(役立たず)。データベース、メッセージブローカーのクライアント、長寿命のものすべてに、アプリケーションレベルのハートビートか積極的なキープアライブ調整が必要です。(QUICのコネクションIDとPINGはまさにこのために設計されました。)あなた自身の層間のアイドルタイムアウトの競争も参照。

再送タイムアウトの崖。 ほとんどの損失は fast retransmit により約1 RTTで回復しますが、応答の最後のパケットの損失(後続がなく重複ACKが発生しない)は完全なRTOを待ちます — Linuxで最低200ms。約200msに硬いこぶを持つ二峰性のp99.9はRTOであり、あなたのアプリケーションではありません。テールに敏感なシステムがヘッジするのは正確にこの床のためです。

容量不足に化けたバッファブロート。 負荷下でレイテンシが登り、損失はほぼゼロのまま、帯域グラフは「健全で満杯」に見える。キューが再充填されるため帯域追加は効きません。負荷下レイテンシテストで診断し、プロビジョニングではなくAQM/BBR/ペーシングで直すこと。

0-RTTリプレイ。 細工された0-RTTの POST が捕獲され5回リプレイされる。エンドポイントが冪等でなく、エッジがメソッドを0-RTTからフィルタしていなければ、TLS設定の中にリプレイ攻撃を組み込んだことになります。CDNとゲートウェイが実際に何を許しているか監査を。

H3→TCPの静かなダウングレード。 ネットワーク変更(UDPの絞り、新しいファイアウォール)がH3トラフィックをTCPフォールバックに押し出す。何もエラーにならず、モバイルのテールレイテンシが静かにH2-over-lossy-TCPの挙動へ退行します。エラー率だけでなくプロトコル構成比にアラートを。

インキャスト崩壊。 scatter-gatherを50から200シャードに広げると、全シャードが速いにもかかわらずp99が沈む: 同期した応答がToRバッファを溢れさせます。修正はアーキテクチャ側(ファンアウト上限、ジッター、DCTCP/ECN) — 満杯のスイッチバッファを生き延びるホストチューニングはありません。


判断のフレームワーク

状況手を伸ばすもの
クロスリージョンのレイテンシ予算が破綻まず往復を数える: コネクション再利用、TLS 1.3+再開、0-RTT(冪等のみ)、リージョナル終端 — アプリケーションコードに触る前に
高RTTまたは損失の多い経路のスループットBBR。ソケットバッファ ≥ BDPを確認。それから経路自体を疑う
モバイル / 不安定なラストマイルHTTP/3 — ストリーム独立性+コネクションマイグレーションはまさにこのために作られた
内部RPCフリートgRPC/H2 + TCP_NODELAY、メッシュ管理のプール。MAX_CONCURRENT_STREAMS を監視。ここでH3は急務ではない
パブリックエッジH3有効化+TCPフォールバック監視。0-RTTは安全なメソッドに制限
DC内scatter-gatherのp99の崖ファンアウト上限、応答ジッター、DCTCP/ECN — 遅いサーバーではなくインキャスト
L4バランサ / DDoSフィルタ / パケットゲートウェイの構築まずXDP/eBPF、NICを所有する必要があるならDPDK — パケットの仕事であり、リクエストの仕事ではない
それ以外のほぼすべての構築カーネルスタック、epoll/io_uring、そして自分のコードに向けたプロファイラ
トンネル越しに「大きなリクエストはハング、小さいのは正常」MTU/PMTUDブラックホール: ICMPルール、MSSクランプ
長寿命アイドル接続の謎の死経路上のあらゆるミドルボックスのタイムアウトより短いアプリケーションハートビート

要点

  1. RTTが通貨であり、プロトコルはそれを消費する。 バイトの前に往復を数えること — コールドなクロスリージョンHTTPSリクエストは約4 RTTの純粋なプロトコルであり、それがコネクション再利用の全根拠です。
  2. スループットは window / RTT であり、損失はレイテンシと掛け算になる。 Mathisの上限は、DC内では見えない損失率がクロスリージョンのスループットを破壊することを意味します。BBRが存在するのは、損失が常に悪い輻輳シグナルだったからです。
  3. ヘッドオブラインブロッキングはスタックを下へ移動する。 HTTP/2はアプリケーション層を直してTCPのそれを相続し、QUICはトランスポートを直しました。チューニングの前に、自分のがどの層にあるか知ること。
  4. 満杯のバッファは成功指標ではなく障害モード。 バッファブロートとインキャストはキューイングの病理です — それを見つけるテストは負荷下レイテンシであり、帯域グラフではありません。
  5. 硬直化(ossification)は設計制約。 TCPはもはや野外で進化できません。QUICは進化可能であり続けるためにトランスポートヘッダを暗号化します。トランスポートの未来がUDP上のユーザー空間で出荷される理由です。
  6. 0-RTTはリプレイ面。 冪等なリクエストのみ — エッジで強制し、監査する。仮定しない。
  7. 仕事をパケットかリクエストかに分類する。 パケットの仕事(L4、DDoS、転送)はXDP/DPDKを正当化します。リクエストの仕事は決してしません — カーネルパスはボトルネックではなく、あなたのアプリケーションがそうです。

参考文献

  • Ilya Grigorik — High Performance Browser Networking(オンライン無料) — レイテンシ/ハンドシェイク/HTTPの正典的扱い
  • RFC 9000 (QUIC)、RFC 9114 (HTTP/3)、RFC 8446 (TLS 1.3)、RFC 6928 (IW10)、RFC 2308(ネガティブキャッシング、13章向け)
  • Mathis et al. — The Macroscopic Behavior of the TCP Congestion Avoidance Algorithm (1997) — √p の上限
  • Cardwell et al. — BBR: Congestion-Based Congestion Control (ACM Queue, 2016)
  • Alizadeh et al. — Data Center TCP (DCTCP) (SIGCOMM 2010); Nichols & Jacobson — Controlling Queue Delay (CoDel, 2012)
  • Langley et al. — The QUIC Transport Protocol: Design and Internet-Scale Deployment (SIGCOMM 2017) — Googleのデプロイ数値
  • Eisenbud et al. — Maglev: A Fast and Reliable Software Network Load Balancer (NSDI 2016); MetaのKatran (github.com/facebookincubator/katran)
  • Høiland-Jørgensen et al. — The eXpress Data Path (CoNEXT 2018)
  • Cloudflareブログ: How to receive a million packets per secondThe story of one latency spike — 模倣に値するカーネルパスのフォレンジクス

MITライセンスの下で公開。Babushkaiコミュニティが構築。