DNS と HTTP 基礎

curl https://api.example.com/user/42 を実行する。 最初のバイトがソケットに到達する前に、api.example.comは IP にならなければならない —— それが DNS。次にリクエスト自体は 3 つの非常に異なるアプリケーションプロトコルのいずれかに乗る:HTTP/1.1(TCP 上のテキスト)、HTTP/2(単一 TCP コネクション上のバイナリフレーミング、多重化ストリーム)、HTTP/3(UDP 上 QUIC 上のストリーム)。 5 セクションで全体像を組み立てる:DNS 解決—— リゾルバ、レコードタイプ、キャッシュ;HTTP/1.1—— keep-alive と HOL ブロッキング;HTTP/2—— バイナリフレーミングと HPACK + 対話的比較デモ;HTTP/3 / QUIC —— 0-RTT と接続マイグレーション; 最後にクイックリファレンス

01

DNS —— api.example.com を IP に変える

curl がソケットを開ける前に、api.example.com203.0.113.42 に翻訳しなければならない。その翻訳は インターネットを横切る 5 ホップの歩み、各ステップでキャッシュされる —— そして大半の「断続的な」障害が実際に住んでいる場所。

curl が走ると、libc が最初にやるのはgetaddrinfo("api.example.com", "443", ...)。 この関数は /etc/nsswitch.conf(通常「files dns」 —— 先に /etc/hosts、次に DNS リゾルバ)を相談し、その後 libc 内のスタブリゾルバ/etc/resolv.conf にある IP に UDP パケットを送る —— 現代の systemd-resolved ホストではよく127.0.0.53、上書きしていれば 1.1.1.1 /8.8.8.8

再帰的な歩み

その近くの IP が 再帰リゾルバ。キャッシュに答えがあれば、 <1 ms で応答が返る。なければ、代わりに DNS 階層を歩く:

  1. ルートサーバ(a.root-servers.net からm.root-servers.net までラベルされた 13 台、各々実際には 数百の物理インスタンスへの anycast)が .com をどこで 見つけるか教える。
  2. TLD サーバ(Verisign 運営の .com)が どのネームサーバが example.com の権威かを教える。
  3. 権威サーバ(example.com の)がapi.example.com の A レコードを返す。

冷キャッシュで最低 3 クエリ、それぞれ独自の RTT。再帰リゾルバは これを償却するため積極的にキャッシュする —— だから 2 回目の DNS ルックアップは 速く、新規マシンからの最初の 1 回は 100 ms+ かかり得る。

$ dig +trace api.example.com
;; .                  86400 IN NS a.root-servers.net.   ; ルート
;; com.               172800 IN NS a.gtld-servers.net.  ; TLD
;; example.com.        86400 IN NS ns1.example.com.     ; 権威
;; api.example.com.      300 IN A  203.0.113.42         ; 最終回答

重要なレコードタイプ

  • A —— IPv4 アドレス。最も一般的。
  • AAAA —— IPv6 アドレス。「quad-A」。
  • CNAME —— 正規名;別の名前へのエイリアス。同じ名前で 他のレコードと共存できない(RFC 制限)。だから apex レコード (example.com 自体)は CNAME にできない —— DNS ホストの ALIAS / ANAME や flatten が必要。
  • MX —— メール交換器。フォールバック順序の優先度フィールド付き。
  • TXT —— 任意のテキスト。SPF、DKIM、ドメイン所有権検証 (「あなたのものだと証明するため、この TXT レコードを置いて」)に使われる。
  • NS —— ネームサーバ委任。「example.com 下のものは ns1.example.com に聞け」と言う。
  • SOA —— start of authority。ゾーンメタデータ、負 TTL ——リゾルバが NXDOMAIN 応答をどれだけ キャッシュすべきか ——を含む。
  • SRV —— ポート付きサービス位置。SIP、XMPP、Kerberos、 Kubernetes サービスディスカバリで使われる。
  • CAA —— Certification Authority Authorization。 ドメインに証明書を発行できる CA をリストする。CA/Browser Forum は 発行前に CA がこれをチェックすることを要求する。

各層での TTL とキャッシュ

各レコードに TTL がある —— 「N 秒キャッシュ」。そして各層がキャッシュする: ブラウザ(~分)、OS リゾルバ(systemd-resolved や nscd)、再帰リゾルバ (1.1.1.1 が TTL の間保持)、権威サーバ自身。300 秒 TTL は 「DNS を変えた後、世界はあなたの古い IP を次の 5 分間見るかもしれない」を意味する。

SOA レコードの負 TTLも同じくらい重要。ホスト名をタイポすると、 NXDOMAIN もキャッシュされる —— よく 5+ 分。誤設定された DNS デプロイの最初の兆候は、 修正をプッシュした後 1 時間の間「一部のユーザが間違ったレコードを見、 残りは何も見ない」。

UDP vs TCP、EDNS0、DoH / DoT

クラシック DNS はポート 53 の UDP、元 RFC で応答制限 512 バイト。 それより大きいとサーバが truncate フラグ(TC)を立て、 クライアントは TCP で再試行 —— EDNS0(RFC 6891)まで、 これがクライアントに大きな UDP 応答(通常 4096 バイト)のサポートを宣言させる。 ほとんどの現代デプロイは EDNS0 を使い、通常クエリで TCP にフォールバックしない。

DNS over HTTPS(DoH)DNS over TLS(DoT)は DNS クエリを TLS で包み、ISP が観察したり書き換えたりできないようにする。 DoH はポート 443 を使う(普通の HTTPS トラフィックに見える)、Firefox と Chrome が cloudflare-dns.comdns.google に ネイティブに話すのがこれ。DoT はポート 853 を使う(専用ポート —— 企業ファイアウォールで簡単にブロックでき、これがまさに DoH が勝った理由)。

Anycast —— なぜ 1.1.1.1 はどこでも速いか

Cloudflare の 1.1.1.1 と Google の 8.8.8.8 は 1 つのデータセンタには住まない。 同じ IP が数百の PoP から BGP anycast で告知される; BGP トポロジで最も近い PoP が勝つ。東京から 1.1.1.1 へのパケットは東京 PoP へ; ロンドンからはロンドン PoP へ。同じ IP、異なる物理サーバ。これが「パブリックリゾルバ」が ISP のと張り合えるほど速い理由。

本番の落とし穴

  • JVM はデフォルトで DNS を永久にキャッシュする。Java の networkaddress.cache.ttl は SecurityManager が設定されているとき-1(無限)、それ以外は 30 秒がデフォルト。長寿命の JVM がラウンドロビン DNS のロードバランサに解決すると、1 つの IP に固定され、バックエンドが死んでも気づかない。 実際の TTL に設定する。
  • musl libc は /etc/resolv.conf をリロードしない。Alpine ベースのコンテナは起動時にリゾルバ IP をキャッシュする。DNS サーバの IP が 変わると(または /etc/resolv.conf が populated される前にコンテナが起動すると)、 詰まる。プロセスを再起動する。
  • TTL 0 は「キャッシュするな」を意味しない。ブラウザや一部のライブラリは、レコードが何と言おうと内部最小値(よく 60 秒)で キャッシュする。
  • ハードコードされたリゾルバ IP。アプリを 1.1.1.1 に直接指すと ローカルキャッシュをバイパスし、1 つの外部プロバイダに固定する。 2020 年の 1.1.1.1 障害時、死んだのはハードコードされたスタック。

要点。「DNS は冷キャッシュフォールパス:スタブリゾルバ → 再帰リゾルバ → ルート → TLD → 権威、各層で積極的にキャッシュ。 レコードタイプ(A、AAAA、CNAME、MX、TXT、NS、SOA、SRV、CAA)は語彙。 TTL がワールドビューの古さを制御する。通常クエリは UDP、大きいものは TCP、 プライバシーは DoH / DoT。古典的な本番バグは何か —— JVM、musl、ブラウザ —— が TTL の言う以上に長くキャッシュすること。」

02

HTTP/1.1 —— TCP 上のテキストプロトコル

1 つの TCP コネクション。1 つのリクエスト、次に 1 つのレスポンス、 そして次のかもしれない。線上はテキスト、行指向、人間にパース可能。 ブラウザが同じオリジンに 6 つのコネクションを開く理由でもある。

DNS が 203.0.113.42 をくれたら、curl は203.0.113.42:443 への TCP コネクションを開き、TLS ハンドシェイクをし (次の primer でカバー)、その後 HTTP を話す。HTTP/1.1 では TLS の後に 線上に乗るのはまさにこれ:

GET /user/42 HTTP/1.1
Host: api.example.com
User-Agent: curl/8.4.0
Accept: */*
Accept-Encoding: gzip, deflate
Cookie: session=abc123; csrf=xyz789

2 つの改行(\r\n\r\n)がリクエストヘッダを終わらせる。 POST であればボディが続き、Content-Length: N がサーバに 何バイト読めばよいかを伝える。サーバはその後、類似のテキストフレームメッセージで応答する:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 47
Cache-Control: private, max-age=60
ETag: "abc123"
Date: Wed, 27 May 2026 12:00:00 GMT

{"id":42,"name":"Ada","email":"ada@example.com"}

Keep-alive —— TCP コネクションを再利用する

HTTP/1.0 では、各リクエストが新しい TCP コネクションを開き、レスポンス後に閉じた。 それはリクエストごとに TCP ハンドシェイク(1 RTT)と TLS ハンドシェイク(1-2 RTT) を意味し —— レイテンシの罰。HTTP/1.1 は keep-alive を デフォルトにした:レスポンス後、コネクションは開いたままで、次のリクエストが再利用する。 サーバはタイムアウト(よく 60-75 秒)とコネクションあたりの最大リクエスト数を告知する。

だから、シェルから curl https://a/x の後 curl https://a/ycurl https://a/x https://a/y よりずっと遅い —— 前者は 2 つの コネクションを開き、後者は 1 つを再利用する。

Pipelining —— そして誰も使わない理由

HTTP/1.1 は技術的には pipelining を許す: レスポンス 1 が到着する前にリクエスト 2 を送り、レスポンス 2 の前にリクエスト 3 を送る、 そしてサーバは順番に応答しなければならない。実際にはほとんど何も有効にして出荷していない、 2 つの問題のため:

  1. Head-of-line(HOL)ブロッキング。レスポンス 1 が遅い場合 (200ms DB クエリ)、レスポンス 2 と 3 はその後ろに詰まる、 個別には瞬時であっても。クライアントは改善を得ず、サーバが考えている間 コネクションは無駄になる。
  2. 壊れたプロキシ。野生の多くの透過プロキシが pipelined リクエストで誤動作する —— それらはバッファ、再順序付け、またはレスポンスを落とす。 ブラウザは 2000 年代半ばに pipelining を有効化しようとし、普遍的にロールバックした。

なぜブラウザはオリジンごとに 6 コネクションを開くか

pipelining が死に、1 つの HTTP/1.1 コネクションが一度に未完了リクエストを 1 つしか 運べないので、複数のリソースを並行ロードする唯一の方法は同じオリジンへの 複数の TCP コネクションを開くこと。すべての主要ブラウザによって強制される 事実上の制限はオリジンあたり 6 並列コネクション。 その数は「アセットを速くロード」と「サーバを DDoS しない」の妥協。

これも古いパフォーマンストリックが「ドメインシャーディング」だった理由 ——img1.example.comimg2.example.com、... からアセットを提供し、 ブラウザが 6 × N コネクションを開くようにする。現代の HTTP/2 はそのトリックを 積極的に有害にする(1 つのコネクションが 6 つに勝つ)が、古いアドバイスはまだ記事に潜んでいる。

Chunked transfer encoding —— ストリーミングレスポンス

時にサーバは事前にレスポンスサイズを知らない —— データベースクエリ、long-poll、 または server-sent-events ストリームから出力を生成している。Content-Length: N の代わりに、サーバはTransfer-Encoding: chunked を設定し、サイズプレフィックス付きチャンクを発する:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/event-stream

5\r\n
hello\r\n
6\r\n
 world\r\n
0\r\n
\r\n            ← ゼロ長チャンク = 「ストリーム終了」

誰も逃れられないヘッダ

  • Host: —— HTTP/1.1 以降必須;1 つの IP が複数の仮想ホストを 提供できる唯一の方法。Host なしでは、名前ベースの仮想ホスティング (現代のすべて)は機能しない。
  • Cookie: / Set-Cookie: —— ステートレスプロトコルへの元のステートフルアドオン。 マッチングドメインへのすべてのリクエストで送られる。
  • Cache-Control:ETag:If-None-Match:—— ブラウザが「キャッシュされたコピーはまだ良いか?」と尋ねる方法と、 サーバが「304 Not Modified」と答える方法。
  • Accept-Encoding: —— 「gzip / br / deflate を受け取れる」。 サーバは 1 つ(またはなし)を選び、Content-Encoding: で告知する。
  • Connection: keep-alive / Connection: close—— 痕跡的だがまだ周りにある;keep-alive はデフォルトで、 もはやめったに明示的に送られない。

要点。「HTTP/1.1 は TCP 上のテキスト、コネクションあたり 1 つの未完了リクエスト。Keep-alive がハンドシェイクを償却する; pipelining はリクエスト-レスポンス RTT を償却しようとする運命の試みで、 HOL ブロッキングと壊れたプロキシによって殺された。ブラウザは オリジンあたり ~6 並列コネクションを開いて補償する。Cookie、Cache-Control、 ETag、Accept-Encoding、Transfer-Encoding: chunked が、すべてのワイヤトレースで 見るヘッダ。」

03

HTTP/2 —— バイナリフレーミングと多重化

1 つの TCP コネクション。多くのストリーム、 フレームレベルでインターリーブ。共有動的テーブルで圧縮されたヘッダ。 1997 から 2022 の間に HTTP が見た最大のプロトコル再設計 —— それでもまだ TCP の囚人。

HTTP/1.1 の「オリジンあたり 6 並列コネクション」は常に回避策だった。 HTTP/2(RFC 7540、2015)はそれをオリジンあたり 1 コネクションに置き換え、 内部的に多くの並行ストリームに分割する。同じ HTTP セマンティクス —— メソッド、パス、ステータスコード、ヘッダ —— だがワイヤフォーマットは テキストからバイナリフレームに変わる。

バイナリフレーミングレイヤ

すべての HTTP/2 メッセージはフレームのシーケンス。 各フレームには 9 バイトのヘッダ(length、type、flags、stream id)とペイロードがある。 重要なフレームタイプ:

+----+--------------------------------------------------+
| 24 | length(フレームペイロードサイズ、最大 16 MB)     |
|  8 | type(HEADERS、DATA、SETTINGS、WINDOW_UPDATE...)  |
|  8 | flags(END_STREAM、END_HEADERS、...)              |
|  1 | reserved                                          |
| 31 | stream id(0 = コネクション、奇数 = クライアントストリーム) |
+----+--------------------------------------------------+
|    | payload                                          |
+----+--------------------------------------------------+
  • HEADERS —— リクエスト/レスポンスヘッダ(HPACK エンコード)。
  • DATA —— リクエスト/レスポンスボディバイト。
  • SETTINGS —— コネクションごとのつまみ(初期ウィンドウサイズ、 最大並行ストリーム、最大フレームサイズ、ヘッダテーブルサイズ)。
  • WINDOW_UPDATE —— フローコントロールクレジット。 ストリームごとと接続ごとの両方で送られる。
  • RST_STREAM —— ストリームをキャンセル。
  • PING / GOAWAY —— keepalive とグレースフルシャットダウン。

多重化が機能するのは、各フレームが所属する stream id を持っているから。 サーバはストリーム 1、3、5、7 からのバイトを自由にインターリーブできる —— 受信者は stream id でデマックスし、各ストリームのフレームを順番に再組み立てする。

HPACK —— なぜ HTTP/2 ヘッダは小さいか

HTTP/1.1 は毎リクエストで同じ Cookie:User-Agent:Accept: を送る —— よく数百バイト、よく完全に同じ。HPACK(RFC 7541)は共有動的テーブルでヘッダを圧縮する。

両ピアが同期された(名前、値)ペアのテーブルを維持する。一般的なヘッダは一度送られ、 その後インデックスで参照される —— 200 ではなく 1 バイト。一般的なヘッダの 固定静的テーブル(:method GET:status 200)と リテラル値の Huffman エンコーディングもある。Cookie の重いリクエストでの 典型的な節約:80-95%。

リクエスト 1:"cookie: session=abc123" を送る → インデックス 62 でテーブルに追加
リクエスト 2:単に "62" を送る → 22 の代わりに 1 バイト
リクエスト 3:単に "62" を送る → まだ 1 バイト

HPACK にはセキュリティ皺がある:TLS 圧縮への CRIME 攻撃は精神的に適用するので、 HPACK は攻撃者制御と秘密値を混合するストリーム境界をまたいで圧縮することは決してない。 仕様は注意深いが、実用的な実装はその落とし穴を知る必要がある。

ウィンドウによるフロー制御

HTTP/2 は TCP のフロー制御の上に独自のものを持つ。各ストリームは64 KB の受信ウィンドウで始まる。バイトが消費されるにつれて、 受信者が WINDOW_UPDATE フレームを送ってより多くのクレジットを与える。 これは 2 つの意味で重要:

  1. 速いレスポンス(ビデオセグメント)に対して、デフォルトの 64 KB ウィンドウは 1 ウィンドウ分後に送信者を停止させる —— WINDOW_UPDATE のために RTT 待たなければならない。 スループットを気にするクライアントは SETTINGS 経由で初期ウィンドウを数 MB に成長させる。
  2. 遅い受信者に対して、フロー制御は受信者がデータを落とさずに速い送信者を絞ることを許す —— ストリームが 1 つの TCP コネクションに多重化されるとき必須。

Server push —— 誰もうまく出荷しなかった機能

HTTP/2 は server push を追加した:サーバは index.htmlへのリクエストに応答して app.css を先制的に送れる。実際にはフロップだった。 サーバはブラウザがすでに何をキャッシュしているか知らないので、多くの無駄を押した; ブラウザサポートは一貫していなかった;キャンセル周りのルールは複雑だった。 Chrome は 2022 年に push を無効にした。現代の代替は HTML レスポンスでのLink: rel=preload ヒントで、ブラウザに決めさせる。

消えなかった HOL ブロッキング

HTTP/2 はアプリケーションレベルの HOL ブロッキングを修正した: ストリームは HTTP レイヤで独立。だが HTTP/2 はまだ TCP 上で動作し、 TCP は単一の順序付けされたバイトストリームを配信する。1 つの失われたパケットは、 コネクションを共有する 6 つのストリームのうち 5 つのデータがすでに受信者にあっても、 再送のために全コネクションを一時停止する。これが TCP レベル HOL ブロッキング、 HTTP/3 が存在する最大の単一理由。1% パケット損失下では、HTTP/2 は 6 並列 HTTP/1.1 より 遅い可能性がある —— 並列 HTTP/1.1 コネクションは少なくとも 1 ストリームの損失を 別のものから分離するから。

ページロード —— 1 HTML + 5 サブリソース、HTTP バージョン全体でフレーム 0 —— 6 リクエストが待機中(1 HTML + 5 サブリソース)req 1待機中req 2待機中req 3待機中req 4待機中req 5待機中req 6待機中
6 つの論理リクエスト:index.html、app.css、app.js、logo.png、hero.jpg、font.woff2。各々受信後 20 ms で完了。問題は基底トランスポートがそれらを 50 ms RTT リンク上でどう運ぶか。
1 / 7
各フレームは同じページロード(1 HTML + 5 アセット)を異なるトランスポートで示す。 RTT = 50 ms;アセットあたり処理 = 20 ms。HTTP/1.1(1995)から HTTP/3(2022)へ 移行するにつれて、ハンドシェイクカウントと HOL ブロッキングが縮小するのを見よ。 大きなジャンプは HTTP/2 → HTTP/3 の帯域幅ではなく —— パケット損失下のテール レイテンシで、HTTP/3 の独立した QUIC ストリームが互いに出血するのを止める。

要点。「HTTP/2 = 1 TCP コネクション、多くのインターリーブされた バイナリストリーム。フレーミングレイヤ(HEADERS、DATA、SETTINGS、WINDOW_UPDATE)が 多重化を機能させる;HPACK が共有動的テーブル経由で繰り返しヘッダで 80%+ 節約; フローコントロールウィンドウが遅い受信者を保護する。2 つの損失: server push がフロップした、そして TCP レベル HOL ブロッキングが残る —— 1 つの落ちたパケットがすべてのストリームを停止させる。」

04

HTTP/3 —— UDP 上の QUIC 上のストリーム

TCP を QUIC に置き換えると、HTTP のカーネルへの最後の共有依存が消える。 ストリームがトランスポートのファーストクラスオブジェクトになる。 TLS はもうオプションではない。あなたの携帯は Wi-Fi から LTE にホップして 同じセッションを保てる。

HTTP/2 はずっと使ってきた同じ TCP 上の HTTP の偉大な書き直しだった。 HTTP/3(RFC 9114)はさらに進む:アプリケーションレイヤフレーミングは類似に保つが、 TCP と上の TLS を分離したレイヤとして捨てる。QUIC(RFC 9000) で置き換える —— エンドツーエンドで設計されたトランスポートプロトコルで、 多重化、TLS 1.3、接続マイグレーションが同じハンドシェイクに焼き込まれている。

なぜ UDP か、そして QUIC が「ただの」 UDP ではない理由

QUIC は UDP の上で動作する、なぜならインターネット上のすべてのミドルボックス —— キャリアの NAT ボックス、企業ファイアウォール、キャプティブポータル —— が UDP と TCP を理解し、他は何も理解しないから。新規のトランスポートプロトコルは 静かに落とされる。だから QUIC は「UDP 税」(小さな UDP ヘッダ)を払い、 信頼性、輻輳制御、順序付き配信、ストリーム多重化をユーザ空間で再構築する。

重要なのは、QUIC のストリームはトランスポートレベルで独立している。 各ストリームは独自のシーケンス番号を持つ。ストリーム 3 で失われたパケットは、 そのストリームのバイトだけの再送を強制する —— 後続のパケットが到着した瞬間に、 ストリーム 1、2、4、5、6 は流れ続ける。これが HTTP/2 の TCP レベル HOL ブロッキングを 完全に殺す。

TLS 1.3、焼き込まれた

QUIC は TLS を上に重ねない —— TLS 1.3 をハンドシェイクに統合する。 暗号交換とトランスポートパラメータ交換は同じパケットフライトで起こる。 正味効果:新規サーバへの新規接続は 1 RTT(vs TCP の 1 RTT + TLS の 1 RTT = HTTP/2 の 2 RTT)。以前話したサーバには、0 RTT —— 最初のリクエストはハンドシェイクと同じパケットで送られ、 再開シークレットは以前のセッションから派生される。

0-RTT には微妙な注意点がある:それらの初期バイトはリプレイ攻撃に脆弱。 サーバは 0-RTT リクエストを冪等(GET のみ)として扱うか、アプリケーションレベルの リプレイ保護を持つ必要がある。CDN があなたのために行う; 生の QUIC サーバは明示的な注意が必要。

接続マイグレーション

TCP 接続は 4 タプル(送信元 IP、送信元ポート、宛先 IP、宛先ポート)で識別される。 IP が変わると —— Wi-Fi から LTE への移動、NAT 再バインド、ローミングハンドオフ —— 接続が死ぬ。すべての長寿 HTTP/2 接続が落ちる;ブラウザは新しいものを開き、 ユーザはストールを見る。

QUIC は各パケットに運ばれるConnection ID で接続を識別する。 あなたのパケットは異なる IP から来ても、サーバはあなたのセッションと関連付ける。 あなたの携帯はカフェの Wi-Fi から LTE に移動するとき、再接続なしで動画ストリーミングを続ける。 これがモバイルのキラー機能。

QPACK —— HPACK の順序外れのいとこ

HPACK はフレームの順序通り配信を仮定した —— HTTP/2 over TCP では問題ない。 QUIC ストリームは順序外に到着するので、HPACK の動的テーブル更新は 前のフレームが処理されていることに依存できない。QPACK(RFC 9204)はヘッダエンコーディングを制御ストリーム(テーブル更新)と リクエストストリーム(ヘッダ参照)に分離し、明示的な同期を持つ。 同じ圧縮率、QUIC の順序外現実のために修正された。

ALPN —— クライアントが h3 を話すようになる方法

curl は curl https://api.example.com/user/42 を実行する。 クライアントはどうやって HTTP/1.1、h2、h3 のどれを話すか知るのか?

  1. HTTP/1.1 vs h2 について:TLS ハンドシェイク中、 クライアントが ALPN(Application-Layer Protocol Negotiation) TLS 拡張でサポートするプロトコルをリストする。サーバは 1 つを選び、 ServerHello に含める。TLS が完了するまでに、両側はどの HTTP バージョンを 話すか知る。
  2. HTTP/3 について:クライアントは最初に TCP+TLS で接続し、Alt-Svc 応答ヘッダを取得する: Alt-Svc: h3=":443"; ma=86400。 これがクライアントに「次回、ポート 443 で QUIC を試せ」と伝える。 クライアントは max-age の間それをキャッシュする。後続の訪問で、 QUIC ハンドシェイクを TCP と競争させ、最初に接続したものを使う。

採用 —— どこで見るか

Cloudflare の 2024 数値:彼らのネットワーク全体で HTTPS トラフィックの ~30% が HTTP/3。 Google、Meta、Akamai、Fastly すべてが提供する。すべての主要ブラウザがサポートする。見ない場所:大半の内部インフラストラクチャ。 ロードバランサの後ろの自分のバックエンドはほぼ確実に h2。 サーバ間のミドルボックスは QUIC を知らない;モニタリングツールはパースできないかもしれない; トレースライブラリは TCP を仮定する。HTTP/3 は CDN エッジプロトコル、 h2 はすべての内部に残る。

要点。「HTTP/3 は QUIC 上で、QUIC は UDP 上で動作する。 ストリームはトランスポートレベルで独立 —— TCP HOL ブロッキングなし。 TLS 1.3 がハンドシェイクに統合された:新規 1 RTT、既知サーバへは 0 RTT。 Connection ID 経由の接続マイグレーションが、携帯に Wi-Fi → LTE をまたいで セッションを保たせる。QPACK が順序外配信のために HPACK を置き換える。 クライアントは Alt-Svc ヘッダ経由で h3 を選ぶ。インターネットトラフィックの ~30% が 使うが、CDN エッジでのみ —— 内部サービスは h2 のまま。」

05

クイックリファレンス

冷たく説明できる価値のある 6 つの核心問題と、コードレビューで一目で見抜くべき 5 つの赤旗。

DNS 解決は実際何をする、どこでキャッシュするか?

libc 内のスタブリゾルバが /etc/resolv.conf 内の再帰リゾルバに UDP クエリを送る。キャッシュミスでは再帰リゾルバが ルート → TLD → 権威サーバを歩き、 TTL 付きで回答を返す。各層でキャッシュが起こる:ブラウザ、OS (systemd-resolved / nscd)、再帰リゾルバ、権威サーバ自身さえも。 300 秒 TTL は、変更後最大 5 分間、世界が古いレコードを見られることを意味する。 負の応答(NXDOMAIN)も SOA の負 TTL に従ってキャッシュされる。

Head-of-line ブロッキングとは何で、どの HTTP バージョンが影響を受けるか?

HOL ブロッキングは、キューの先頭の遅いまたは失われたメッセージが 後のメッセージの処理を妨げる現象。HTTP/1.1 pipelining はアプリケーションレベル HOL(遅いレスポンス 1 がレスポンス 2-N をブロック)に苦しむ。 HTTP/2 は多重化でそれを修正したが、まだ TCP 上で動作する —— だから 1 つの 失われたパケットがコネクションを共有するすべてのストリームを一時停止する (TCP レベル HOL)。HTTP/3 は免疫、なぜなら QUIC ストリームはトランスポートレベルで 独立しているから。

HPACK は何をして HTTP/2 ヘッダを小さくするか?

HPACK は両ピアで同期された(ヘッダ名、値)ペアの動的テーブルを維持する。 最初のリクエスト後、cookie: session=abc... がテーブルに入る; 後続のリクエストは 200 バイト文字列を再送する代わりに 1 バイトインデックスで参照する。 一般的なヘッダ(:method GET:status 200)の 静的テーブルとリテラルの Huffman エンコーディングと組み合わせると、 Cookie の重いリクエストで 80-95% の節約。QPACK は HTTP/3 用の QUIC フレンドリな変種。

HTTP/3 が本番で HTTP/2 に測定可能に勝つのはいつか?

3 つのシナリオ。(1)損失のあるネットワーク:1% パケット損失下では HTTP/3 は 劇的に速い、なぜなら失われたパケットは自分のストリームだけを停止させ、6 つすべてではないから。 (2)既知サーバへの新規接続:0-RTT は最初のリクエストがハンドシェイクと共に出ることを意味する —— フル RTT を節約、モバイルコールドスタートの大勝。(3)ネットワークを変えるモバイルクライアント: 接続マイグレーションが Wi-Fi → LTE をまたいでセッションを生かしたまま再接続なしで保つ。 クリーンな有線リンク上ではゲインは控えめ。

なぜブラウザは HTTP/1.1 で 1 つのオリジンに 6 つのコネクションを開くか?

各 HTTP/1.1 コネクションは一度に 1 つの未完了リクエストを運べる(pipelining は死んだ)。 ページの 50+ アセットを並列にロードするため、ブラウザは同じオリジンに複数の TCP コネクションを開く。事実上の制限は 6 —— ロード速度とサーバを DDoS しないことの妥協。 HTTP/2 ではこの数は 1 に下がる、なぜなら多重化が並列コネクションを不要にする (そして積極的に有害 —— ドメインシャーディングは h2 を傷つける)から。

ALPN とは何で、クライアントはどうやって h3 を話すようになるか?

ALPN(Application-Layer Protocol Negotiation)は TLS 拡張で、 クライアントがサポートするプロトコル(h2http/1.1)をリストし、 サーバが ServerHello で 1 つを選ぶ。それが HTTP/1.1 vs HTTP/2 をカバーする。 HTTP/3 については、クライアントは最初に TCP+TLS で接続し、応答で Alt-Svc: h3=":443"; ma=86400 ヘッダを見て、キャッシュし、 次の訪問で QUIC ハンドシェイクを TCP と競走させる。最初に接続したものが勝つ。

コードレビューの赤旗

  • JVM で networkaddress.cache.ttl=-1DNS を永久にキャッシュする。ロードバランサを指すラウンドロビン DNS が 1 IP に固定される; そのバックエンドが死んでも JVM は気づかない。実際のレコード TTL に設定する (典型的に 30-300 秒)。
  • ハードコードされたリゾルバ IP。アプリを 1.1.1.1 や 8.8.8.8 に直接 指すとローカルキャッシュをバイパスし、1 つの外部プロバイダに固定する。 そのプロバイダに障害があると(起こる)、スタックも一緒に死ぬ。 システムリゾルバを使う。
  • アプリが HTTP/2 server push が機能することを期待する。Chrome は 2022 年に push を無効にした;他のブラウザは何年も一貫しなかった。 代わりに HTML 応答で Link: rel=preload ヒントを使う —— ブラウザに実際に何を fetch する必要があるかを決めさせる。
  • すべてのリクエストで大きな Cookie: ヘッダ、 HPACK 以前のコードパスまたは HTTP/2 バックエンドなしで。すべてのアセットリクエストで送られる 4 KB Cookie はすぐに積み上がる —— 50 アセット × 4 KB = HTTP/1.1 でページロードあたり 200 KB のオーバーヘッド。 HPACK 付きの HTTP/2 では、同じ Cookie は最初の送信後 ~1 バイトのコスト。
  • HTTP/1.1 pipelining 上のカスタムプロトコル。Pipelining はプロキシ破壊と HOL ブロッキングのためデプロイされていない。 1 コネクション上で 1000 リクエストを送り、レスポンスを順序外でストリームバックできると 仮定する「賢い」設計は、あなたとサーバの間に壊れたプロキシがある瞬間に失敗する。 HTTP/2 ストリームまたは WebSocket を使う。