HTTP/2の多重化とHPACKヘッダ圧縮の原理
1本のTCP接続で多数のリクエストを同時に流し、ヘッダ重複も削れる。ストリーム多重化・フレーム・優先度・HPACK・残るHOLブロッキングまで原理から理解できる。
- 1.HTTP/2は通信をフレーム単位に分割し、ストリームIDで識別して1接続に多重化することで、HTTP/1.1の並列接続とHOLブロッキングを解消する。
- 2.HPACKは静的/動的テーブルによる索引参照とハフマン符号化でヘッダの冗長を圧縮し、毎回ほぼ同じヘッダを再送する無駄を消す。
- 3.アプリ層のHOLは消えるが、HTTP/2は1本のTCPに依存するためパケットロス時はTCP層でHOLブロッキングが残り、これがHTTP/3でQUICへ移る動機になる。
HTTP/2が解決したかった問題
HTTP/1.1の根本的な制約は、1本のTCP接続上でリクエストとレスポンスを1往復ずつ直列に処理する点にあります。先行リクエストの応答が返るまで後続が待たされる、いわゆるHTTPレベルのHOL(Head-of-Line)ブロッキングです。ブラウザはこれを並列接続(オリジンあたり最大6本程度)で緩和してきましたが、接続ごとにTCPの3ウェイハンドシェイクとTLSハンドシェイク、独立した輻輳制御が走り、コストが嵩みます。
HTTP/2はこの構造を、1本の接続を多数の論理的な通信路へ分割する多重化で置き換えました。テキストベースだったHTTP/1.1をバイナリのフレーム単位に再設計したことが、その土台です。
フレームとストリーム
HTTP/2の通信単位はフレームです。各フレームは固定9バイトのヘッダ(長さ24ビット・タイプ8ビット・フラグ8ビット・ストリームID31ビット)と、タイプごとのペイロードから成ります。主要なフレームタイプは次の通りです。
| フレーム | 役割 |
|---|---|
| HEADERS | リクエスト/レスポンスのヘッダ(HPACK圧縮済み)を運ぶ |
| DATA | ボディ本体を運ぶ |
| SETTINGS | 接続パラメータ(最大同時ストリーム数など)を交換 |
| WINDOW_UPDATE | フロー制御のウィンドウ拡張を通知 |
| RST_STREAM | 特定ストリームのみを中断 |
| PRIORITY | ストリームの優先度・依存関係を指定 |
ストリームは、1つのリクエスト/レスポンスのやり取りに対応する論理的な双方向の流れです。各フレームはヘッダ内のストリームIDで「どのストリームに属するか」を示します。受信側はストリームIDを見てフレームを正しいストリームへ振り分けるため、複数リクエストのフレームを任意の順序で交互に(インターリーブして)送れます。これが多重化の正体です。1本の接続に多数のリクエストが同時に流れ、応答も到着した順に組み立てられます。
クライアントが開くストリームIDは奇数、サーバーが開く(サーバープッシュなど)IDは偶数で、いずれも単調増加します。
HTTP/1.1の並列はTCP接続そのものを複数張る物理的な並列でした。HTTP/2の多重化は、1本の接続の中でフレームを混在させる論理的な並列です。接続が1本になることで、ハンドシェイクやTCPスロースタートの繰り返しが消え、輻輳制御も1つにまとまります。
優先度ツリー
多重化すると「限られた帯域をどのストリームに先に割り当てるか」という問題が生じます。RFC 7540はこれを依存関係ツリーと重み付けで表現しました。各ストリームは親ストリームへの依存と1〜256の重みを持ち、PRIORITYフレームで宣言します。
- ある子が親に依存するなら、親が完了するまで子の処理を待たせる(例: CSSを描画前に先取りする)。
- 同じ親に兄弟として並ぶストリーム同士は、重みの比に応じて帯域を分配する。
例えば兄弟2本が重み {16, 8} なら帯域はおおよそ2対1で配分されます。ただしこの優先度はあくまでサーバーへの助言であり、強制ではありません。実装差が大きく扱いも難しかったため、後のRFC 9113ではこの方式は非推奨化され、より単純な仕組みへ移行が進んでいます。
HPACKによるヘッダ圧縮
HTTPリクエストは、user-agent・accept・cookie のようにほぼ同じヘッダを毎回繰り返し送ります。多重化で1接続あたりのリクエスト数が増えるほど、この冗長は無視できません。HPACK(RFC 7541)はこれを圧縮する専用方式で、3つの仕組みを組み合わせます。
(1) 静的テーブルは、頻出ヘッダを定数として番号付けした固定表です。例えば :method: GET は索引2、:path: / は索引4のように、61エントリが規格で定義されています。送信側は名前と値の組をまるごと1つの索引番号に置き換えられます。
(2) 動的テーブルは、その接続で実際に現れたヘッダを送受信双方が同じ手順で追記していく可変表です。一度送ったヘッダは動的テーブルに登録され、次回以降は索引参照だけで済みます。
1回目: cookie: session=ab12... → 値を本文として送り、動的テーブルへ登録(索引 62 とする)
2回目: 同じ cookie → 索引 62 を送るだけ(値の再送が不要)
(3) ハフマン符号化は、テーブルに無い文字列リテラル(新規のURLパスや値など)を、HTTPヘッダで頻出する文字ほど短いビット列に割り当てる静的ハフマン表で圧縮します。送信側は「リテラルをそのまま送る」か「ハフマン符号化して送る」かをフラグで選べます。
動的テーブルは送信側と受信側がまったく同じ操作で更新するため、明示的な辞書の送付なしに索引を共有できます。サイズには上限があり、超えると古いエントリから追い出されます(FIFO的な退避)。だからこそ索引番号だけで値を復元でき、CPU負荷の小さい圧縮が成立します。
前身のSPDYはヘッダにDEFLATE(gzip相当)を使い、CRIME攻撃で「暗号化された圧縮データの長さ変化から秘密値を推測される」脆弱性を抱えました。HPACKは汎用圧縮をやめ、テーブル索引と静的ハフマン表に限定することで、攻撃者が圧縮率を操作して秘密を漏らす経路を断っています。試験では「HPACK=CRIME対策の固定方式」を押さえましょう。
それでも残るHOLブロッキング
HTTP/2はアプリ層のHOLブロッキングを解消しました。しかし1本のTCP接続に全ストリームを載せる設計ゆえに、より下の層で別のHOLブロッキングが残ります。
TCPはバイト列を順序通りに上位へ渡すことを保証します。途中の1パケットが失われると、再送が届くまでそれ以降に届いた全パケットを保留します。HTTP/2から見ると、ロスは特定のストリームのフレームに起きただけでも、TCPは接続全体のデータ供給を止めてしまう。つまりTCP層のHOLブロッキングです。多重化でストリームを論理的に分離しても、それらが共有する1本のTCPが詰まれば全ストリームが巻き添えになります。
損失率の高いモバイル回線などでは、HTTP/2の単一接続が1つのロスで全体停止する一方、HTTP/1.1の複数接続は「詰まった1本以外は流れ続ける」ため、結果的にHTTP/1.1が有利になることがあります。多重化が常に万能ではない、という点が上級者に問われやすい論点です。
この限界が、トランスポートをTCPからUDPベースのQUICへ置き換えるHTTP/3の動機です。QUICはストリームをトランスポート層で独立管理するため、あるストリームのパケットロスが他のストリームを止めません。HTTP/2の多重化が「アプリ層まで」だったのを、「トランスポート層まで」徹底したのがHTTP/3だと整理できます。
まとめ
HTTP/2は、通信をフレームに分解しストリームIDで識別する多重化により、1接続で多数のリクエストを同時に捌けるようにし、HPACKでヘッダの冗長も削りました。ただし多重化はアプリ層に閉じており、TCPの順序保証に由来するHOLブロッキングは残ります。配信を速くする全体像は Web パフォーマンス を、応答再利用の観点は HTTP キャッシュ を、双方向通信の選択肢は WebSocket を併せて読むと、プロトコル選定の判断がつながります。
Web/フロントエンド Article
HTTP/2の多重化とHPACKヘッダ圧縮の原理を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
HTTP/2
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5
導入後に効く点
HPACKは静的/動的テーブルによる索引参照とハフマン符号化でヘッダの冗長を圧縮し、毎回ほぼ同じヘッダを再送する無駄を消す。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 5
判断チェックリスト
- 自社の用途が「HTTP/2 / HPACK」に近いか確認する。
- 強みである「HTTP/2は通信をフレーム単位に分割し、ストリームIDで識別して1接続に多重化することで、HTTP/1.1の並列接続とHOLブロッキングを解消する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。