HTTP/3とQUIC
HTTP/3 ・ えいちてぃーてぃーぴーすりー
TCPの代わりにUDP上のQUICで運ぶHTTP。接続の確立を速め、パケット欠落による全体停止をなくした。
✦ 30秒で試す — 航海「届く保証のない」UDPの上に、Webの最新版が載っているそのあいだ、すでに届いたCSSと画像のデータは?予想してみる概要
HTTP/3 は HTTP の最新のメジャーバージョンで、2022年に RFC 9114 として標準化されました。いちばんの特徴は、30年以上 Web を運んできた TCP を使わないことです。代わりに UDP の上に「QUIC(クイック、RFC 9000)」という新しいトランスポートプロトコルを作り、その上で HTTP を動かします。HTTP/3 は「QUIC に載せ替えた HTTP」なので、2つはほぼセットで語られます。
QUIC は、TCP が受け持っていた再送・順序の保証・輻輳制御(回線の混み具合に合わせて送る量を調整すること)を UDP の上で作り直し、そのうえ TLS 1.3 による暗号化をプロトコルの中に組み込んでいます。Google 検索や YouTube、主要な CDN はすでに HTTP/3 で配信しており、電波が弱い・移動中といった不安定な回線ほど効果が大きい設計です。
なぜ生まれたか
HTTP/2 は1本の TCP 接続に多数のやり取りを多重化しましたが、TCP に由来する2つの限界が残りました。1つ目は接続を始めるまでの待ち時間です。TCP のハンドシェイクで1往復、その上の TLS のハンドシェイクで1往復(TLS 1.2 では2往復)と、データを送り始める前に何度も往復(RTT)が必要でした。遠くのサーバなら1往復だけで100ミリ秒を超えます。2つ目が TCP の層の Head-of-Line(HOL)ブロッキングです。TCP は接続全体を「順番どおりに届く1本のバイト列」として扱うので、パケットが1つ失われると、そのパケットと無関係なストリームのデータまで再送を待って止まります。
どちらも原因は TCP そのものにあります。ところが TCP は OS のカーネルに実装されているうえ、途中のルーターや NAT などの機器が「TCP はこういうものだ」という前提でパケットを見ているので、新しい拡張を入れても通らないことがありました(ミドルボックスによる硬直化と呼ばれます)。TCP を直すには世界中の OS と機器の更新を待つ必要があり、何年、何十年とかかります。
そこで Google は発想を変えました。ほぼどの機器も素通しする UDP の上に、アプリやライブラリの中で動く新しいトランスポートを作れば、OS を待たずにアプリの更新だけで改良を続けられます。しかも中身をほぼすべて暗号化しておけば、途中の機器が中を覗いて前提を固めることもできません。2012年ごろから Google が自社のブラウザとサーバで実験した QUIC を IETF が引き取って標準化し、その上の HTTP が HTTP/3 と名付けられました。
詳細
何がどの層に移ったか
HTTP/2 と HTTP/3 を層で並べると、変わったところが見えてきます。HTTP/2 では、ストリームの多重化は HTTP の層、再送と順序の保証は TCP の層、暗号化はその間の TLS の層と、3つの層に仕事が分かれていました。HTTP/3 では、ストリーム・再送・輻輳制御・暗号化がすべて QUIC という1つの層にまとまり、その下の UDP は「パケットを運ぶだけ」になります。
接続確立 — ハンドシェイクの統合と 0-RTT
TCP と TLS の組み合わせでは、「まず TCP でつなぎ、次に TLS で鍵を決める」という二段構えのため、初回はリクエストを送れるまでに2往復(TLS 1.2 なら3往復)かかりました。QUIC はトランスポートのハンドシェイクと TLS 1.3 の鍵交換を1つにまとめ、初回でも1往復でリクエストを送れます。
一度つないだことのあるサーバへの再接続では「0-RTT」が使えます。前回の接続で受け取った鍵の情報を使い、ハンドシェイクの完了を待たずに最初のパケットにリクエストを同梱する仕組みです(TLS 1.3 自体の機能なので TCP の上でも使えますが、QUIC ではトランスポートの往復まで省けます)。ただし 0-RTT で送ったデータは、盗聴者に録られて再送(リプレイ)されても区別できません。そのため GET のように冪等性のあるリクエストに限って使うのが原則です。
ストリームごとの再送 — HOL ブロッキングが構造的に消える理由
QUIC では、ストリームを HTTP の層ではなくトランスポートの層が直接持ちます。パケットが失われたとき、再送を待つのはそのパケットを含むストリームだけで、ほかのストリームのデータは届いたものからアプリへ渡されます。TCP のように「接続全体を1本のバイト列」として順番を守るのではなく、順番をストリームごとにしか守らないので、1つの欠落が全体を止めることがありません。
具体的に追ってみます。HTML・CSS・画像の3本のストリームが1本の接続を流れていて、CSS のパケットが1つ落ちたとします。TCP の受信側は、落ちたパケットより後ろの番号のデータを受け取っても、欠けた番号が埋まるまでアプリに渡しません。HTML や画像のパケットは届いているのに、受信バッファで待たされます。QUIC の受信側は、CSS のストリームだけを待たせ、HTML と画像はそのまま渡します。
下の部品で確かめてください。「流す」を押すと、同じパケットが上下の回線を同じ時刻に流れます。流れているパケットを1つクリックして落とすと、上の TCP と下の QUIC で、待たされるストリームの数が変わります。
「流す」を押すと、3本のストリームのパケットが上下の回線を同じ時刻に流れます。流れているパケットを1つクリックして落としてください(上下どちらを押しても、両方の回線で同じパケットが失われます)。
順番がストリームごとになったことで、HTTP/2 のヘッダ圧縮 HPACK はそのまま使えなくなりました。HPACK は「送った順に辞書が更新される」ことを前提にしているからです。そこで、辞書の更新を専用のストリームに分けた QPACK(RFC 9204)に置き換えられています。
コネクションマイグレーション — 接続が IP アドレスに縛られない
TCP の接続は「送信元と宛先の IPアドレスとポート番号の組」で見分けられるので、スマートフォンが Wi-Fi からモバイル回線に切り替わると必ず切れました。QUIC は接続を「コネクション ID」という値で見分けるので、ネットワークが変わって IP アドレスが変わっても、同じ接続を続けられます。移動しながら使う端末で、動画やダウンロードが途切れにくくなります。
普及と運用の落とし穴
Chrome・Firefox・Safari はすべて対応済みで、大手の CDN なら設定で有効にできます。一方で、企業のネットワークや一部のファイアウォールは UDP の443番ポートを遮断していることがあり、HTTP/3 は「必ず使える」前提にはできません。そこでサーバは、HTTP/1.1 や HTTP/2 で返すレスポンスに Alt-Svc ヘッダを付けたり、DNS の HTTPS レコードに載せたりして「HTTP/3 も話せます」と知らせ、ブラウザは使えなければ TCP に戻ります。TCP の経路は、これからも残しておく必要があります。
運用の面でも変化があります。QUIC はカーネルではなくユーザー空間で動くので、TCP に比べて CPU の負荷が高くなりがちです(カーネルやネットワークカードの最適化の蓄積がまだ少ないためで、改善が続いています)。また、ヘッダまでほぼ暗号化されているので、これまで TCP のヘッダを覗いて行っていたネットワークの監視や障害の切り分けがそのままでは使えません。代わりに、通信の両端で QUIC の内部のできごとを共通の形式で記録する qlog や、TLS の鍵をファイルに書き出して解析ツールで復号する方法を使います。「速くなった代わりに、途中から見えにくくなり、少し重くなった」というトレードオフを理解したうえで、遅延やパケットの欠落が多い回線の利用者が多いサービスから導入していくのが現実的です。
試問
HTML・CSS・画像の3本のストリームのパケットが、上の HTTP/2+TCP と下の HTTP/3+QUIC の回線を同じ時刻に流れます。「流す」を押し、流れているパケットをタップすると、上下の両方で同じパケットが失われ、しばらくして再送されます。
「流す」を押すと、3本のストリームのパケットが上下の回線を同じ時刻に流れます。流れているパケットを1つクリックして落としてください(上下どちらを押しても、両方の回線で同じパケットが失われます)。
