本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
QPACKヘッダー圧縮 – HTTP/3における革新的なヘッダー処理
背景
HTTP/3は、トランスポート層にQUICを採用することで、HTTP/2が抱えていた複数の問題を解決しました。特に、HTTP/2のヘッダー圧縮メカニズムであるHPACK (RFC 7541) は、異なるHTTPストリーム間で共有される圧縮コンテキストに依存していたため、単一のヘッダーブロックのパケットロスが、そのストリームだけでなく、他のすべてのストリームのヘッダーデコードを遅延させる「ヘッダー圧縮におけるHOL(Head-of-Line)ブロッキング」を引き起こす可能性がありました。
QUICは、TCPのHOLブロッキングを回避するために、多重化されたストリームを独立して転送する機能を備えています。このQUICの利点を最大限に活かすためには、ヘッダー圧縮メカニズムもストリーム間の依存関係を極力排除する必要があります。この課題を解決するために、HTTP/3 (RFC 9114) では、QPACK (RFC 9204) という新しいヘッダー圧縮方式が導入されました。
設計目標
QPACKは、HPACKの課題を解決し、HTTP/3の性能を最大限に引き出すために以下の設計目標を掲げました [1, 2]。
HOLブロッキングの回避: ヘッダー圧縮コンテキストの変更が、他のストリームのヘッダーデコードをブロックしないようにする。
高い圧縮効率の維持: HPACKが提供していた高い圧縮率を維持、あるいは改善する。
インクリメンタルな状態更新のサポート: 動的テーブルの更新を効率的に管理し、変更を徐々に適用できるようにする。
QUICの0-RTTハンドシェイクとの互換性: 0-RTTデータ送信時に、既知のヘッダーを効率的に圧縮・伸長できるメカニズムを提供する。
デコーダの複雑さの最小化: エンコーダ側の責任を増やし、デコーダ側での状態管理の複雑さを軽減する。
詳細
QPACK (RFC 9204) は、HTTPヘッダーフィールドを効率的に圧縮するためのメカニズムであり、以下の主要な要素で構成されます。
主要な概念
静的テーブル (Static Table):
HTTP/2のHPACKと同様に、よく使われるHTTPヘッダーフィールド名と値のペアが事前に定義された不変のテーブルです。QPACKとHPACKで一部内容が異なります [2]。
エンコーダとデコーダは、このテーブルを共有し、インデックス参照でヘッダーを圧縮します。
動的テーブル (Dynamic Table):
セッション中に動的に追加されるヘッダーフィールド(例:Cookie、User-Agentなど)を格納するテーブルです。
HPACKでは、この動的テーブルがすべてのストリームで共有されていたため、HOLブロッキングの原因となりました。
QPACKでは、動的テーブルはエンコーダとデコーダで共有されるグローバルなコンテキストとして扱われますが、その更新と利用方法に工夫が凝らされています。
制御ストリーム (Control Streams):
QPACKは、ヘッダーデータを運ぶ通常のHTTPストリームとは別に、動的テーブルの更新指示や確認応答を運ぶための特別な単方向制御ストリームを使用します [2]。
Encoder Instruction Stream (エンコーダ指示ストリーム): エンコーダからデコーダへ、動的テーブルへのエントリ追加や容量変更の指示を送信します。
Decoder Instruction Stream (デコーダ指示ストリーム): デコーダからエンコーダへ、動的テーブルの更新が正常に処理されたことの確認応答 (ACK) を送信します。これにより、エンコーダはデコーダがどの状態までテーブルを処理したかを把握できます。
QPACKヘッダーブロックの構造
QPACKで圧縮されたヘッダーブロックは、HTTP/3のHEADERSフレームやPUSH_PROMISEフレームのペイロードとして転送されます。ヘッダーブロックは以下の構造を持ちます [2]。
Header Block Prefix: Required Insert Count: variable-length integer (0-7 bits) Delta Base: variable-length integer (0-7 bits) Encoded Field Section: ... (Encoded Header Field representations) ...
Required Insert Count (RIC): ヘッダーブロックを完全にデコードするためにデコーダが処理済みである必要がある動的テーブルエントリの数をエンコーダが示します。これにより、デコーダは未処理の動的テーブル更新がある場合でも、その更新がRICを満たすまで待機することで、HOLブロッキングを回避しつつ整合性を保てます。
Delta Base (DB): ヘッダーブロック内で使用される「Post-Baseインデックス」の基準となる動的テーブルインデックスからの差分を示します。
圧縮方式
QPACKでは、以下の方法でヘッダーフィールドを表現します [2]。
Static Table Index: 静的テーブルのインデックスを直接参照。最も効率的。
Dynamic Table Index: 動的テーブルのインデックスを直接参照。
Post-Base Index:
Delta Baseからのオフセットで動的テーブルエントリを参照。0-RTTシナリオで特に有用。Literal Field: 圧縮せずにヘッダー名と値をそのまま送信。
Literal With Static Name Reference: 静的テーブルのインデックスでヘッダー名を指定し、値はリテラル。Literal With Dynamic Name Reference: 動的テーブルのインデックスでヘッダー名を指定し、値はリテラル。Literal With Post-Base Name Reference:Delta Baseからのオフセットで動的テーブルエントリのヘッダー名を指定し、値はリテラル。Literal With Literal Name: ヘッダー名も値もリテラル。
動的テーブルの同期プロセス
QPACKの大きな特徴は、動的テーブルの更新をヘッダーデータとは非同期かつアウトオブバンドで同期する点です。
sequenceDiagram
participant Client as エンコーダ (送信側)
participant Server as デコーダ (受信側)
Client->>Server: QUIC接続確立 / HTTP/3設定交換 (ALPN)
activate Server
Server->>Client: HTTP/3設定交換完了 (SETTING_QPACK_MAX_TABLE_CAPACITYなど)
deactivate Server
Note over Client: 動的テーブルは初期状態で空
Client->>Server: QPACK Encoder Instruction Stream: |SET_DYNAMIC_TABLE_CAPACITY| (MAX_CAPACITY)
activate Server
Server->>Client: QPACK Decoder Instruction Stream: |DYNAMIC_TABLE_CAPACITY_ACK| (ACK_VAL)
deactivate Server
Client->>Server: HTTP/3 HEADERSフレーム (Stream A) | Header Block Prefix: ΔBase=0, RIC=0, Fields: (literal)User-Agent:Chrome
Note over Client: このヘッダーは動的テーブルに依存しないため、ACK不要で送信可能。
activate Server
Server->>Client: QPACK Decoder Instruction Stream: |HEADER_ACK| (Stream A processed)
deactivate Server
Note over Server: User-Agentを動的テーブルに追加 (例: Index 62)。
Client->>Server: HTTP/3 HEADERSフレーム (Stream B) | Header Block Prefix: ΔBase=1, RIC=1, Fields: (static):method:GET, (dynamic)User-Agent (idx:62)
Note over Client: Stream Bは動的テーブル項目を参照。RIC=1 は、デコーダがUser-Agentをテーブルに追加済みであることを要求。
activate Server
Server->>Client: QPACK Decoder Instruction Stream: |HEADER_ACK| (Stream B processed)
deactivate Server動的テーブルの更新は、エンコーダ指示ストリームを通じてデコーダに送信されます。デコーダがその更新を処理し、動的テーブルに反映した後、デコーダ指示ストリームを通じてエンコーダにHEADER_ACKを返します。これにより、エンコーダはデコーダがどの動的テーブルエントリまで認識しているかを把握し、それ以降のヘッダーブロックでそのエントリを参照できるようになります。
flowchart TD
subgraph mermaid_group_1["QPACKエンコーダの状態管理"]
ENC_INIT["QPACKエンコーダ初期化"] --> ENC_DT_EMPTY{"動的テーブル: 空"}
subgraph mermaid_group_4["ヘッダーエンコード"]
ENC_HEADERS["HEADERSフレームエンコード"]
ENC_DT_EMPTY --> ENC_HEADERS
ENC_DT_STATE{"動的テーブルの状態"} -->|"利用可能な参照"| ENC_HEADERS
ENC_HEADERS -->|"新規エントリ追加"| ENC_ADD_DT_ENTRY["動的テーブル更新指示作成"]
ENC_ADD_DT_ENTRY --> ENC_DT_STATE
ENC_ADD_DT_ENTRY --> ENC_SEND_ENC_INS["エンコーダ指示ストリーム送信"]
end
ENC_SEND_ENC_INS --> ENC_RECV_DEC_ACK["デコーダACK受信待機"]
ENC_RECV_DEC_ACK -->|"HEADER_ACKを受信"| ENC_DT_STATE
ENC_RECV_DEC_ACK -->|"DYNAMIC_TABLE_CAPACITY_ACKを受信"| ENC_DT_STATE
end
subgraph mermaid_group_18["QPACKデコーダの状態管理"]
DEC_INIT["QPACKデコーダ初期化"] --> DEC_DT_EMPTY{"動的テーブル: 空"}
subgraph mermaid_group_21["ヘッダーデコード"]
DEC_HEADERS["HEADERSフレームデコード"]
DEC_DT_EMPTY --> DEC_HEADERS
DEC_DT_STATE{"動的テーブルの状態"} -->|"参照解決"| DEC_HEADERS
DEC_HEADERS -->|"更新適用"| DEC_APPLY_DT_UPDATE["動的テーブル更新適用"]
DEC_APPLY_DT_UPDATE --> DEC_DT_STATE
DEC_APPLY_DT_UPDATE --> DEC_SEND_DEC_INS["デコーダ指示ストリーム送信"]
end
DEC_RECV_ENC_INS["エンコーダ指示ストリーム受信"] --> DEC_DT_STATE
DEC_SEND_DEC_INS --> DEC_RECV_ENC_INS
end
ENC_SEND_ENC_INS <--> DEC_RECV_ENC_INS
ENC_RECV_DEC_ACK <--> DEC_SEND_DEC_INS既存プロトコルとの比較
HTTP/2のHPACKと比較して、QPACKは以下の点で進化しています。
HOLブロッキングの回避:
HPACK: 動的テーブルは全ストリームで共有される単一のコンテキスト。パケットロスによる動的テーブルの更新遅延が、すべてのストリームのヘッダーデコードをブロックする。
QPACK: 動的テーブルの更新は制御ストリームで非同期に処理され、ヘッダーブロックはRICを用いてデコーダの状態要求を示す。これにより、ヘッダー圧縮におけるHOLブロッキングを回避。
状態管理:
HPACK: 単一の共有状態。エンコーダとデコーダは常に同期した状態を維持する必要がある。
QPACK: グローバルな動的テーブルは存在するが、ヘッダーブロック自体はストリームごとに独立してデコード可能。デコーダが動的テーブルの特定の状態に達していることをRICで保証することで、柔軟なデコードを可能に。
制御ストリーム:
HPACK: 専用の制御ストリームはなし。ヘッダーブロック内にテーブル更新情報を含む。
QPACK: 動的テーブルの更新指示や確認応答のために専用の単方向制御ストリームを使用。
0-RTTサポート:
HPACK: 動的テーブルはセッション固有のため、0-RTTでは利用が困難。
QPACK: Post-Baseインデックスなどのメカニズムにより、0-RTTデータ送信時に過去のセッションで学習した動的テーブルエントリの一部を安全に参照できる仕組みを提供。
相互運用
QPACKの実装間での相互運用性には、以下の点に注意が必要です。
QPACK設定のネゴシエーション: HTTP/3の
SETTINGS_QPACK_MAX_TABLE_CAPACITYとSETTINGS_QPACK_BLOCKED_STREAMSを通じて、エンコーダとデコーダは動的テーブルの最大容量と、デコーダがブロックできる最大ストリーム数を通知し合います。これらの設定が正しく交換されることが重要です [2]。動的テーブルの同期プロトコル: エンコーダ指示ストリームとデコーダ指示ストリームのメッセージ(例:
INSERT_COUNT_INCREMENT,HEADER_ACK)がRFC 9204に厳密に従って実装されている必要があります。エラー処理: 動的テーブルの不一致や容量超過などのエラーシナリオにおいて、プロトコルが定義するエラーコード(例:
QPACK_DECOMPRESSION_FAILED)を用いて適切にエラーを通知し、接続を閉じるメカニズムが重要です。
セキュリティ考慮
QPACKはヘッダー圧縮に特化したプロトコルですが、ネットワーク通信の一部であるため、いくつかのセキュリティ上の考慮事項があります。
リプレイ攻撃: QPACK自体がリプレイ攻撃から保護するメカニズムは持ちません。しかし、QPACKはQUICとTLS 1.3上で動作するため、QUICのハンドシェイク中に確立されるセッションキーによって、トランスポート層でリプレイ保護が提供されます [1]。QPACKの動的テーブルの内容はセッションごとに確立されるため、過去のセッションのQPACK状態を再利用して攻撃することは困難です。
ダウングレード攻撃: HTTP/3プロトコル全体のネゴシエーションはALPN (Application-Layer Protocol Negotiation) によって行われるため、HTTP/3をHTTP/2やHTTP/1.1にダウングレードさせる攻撃は、ALPNのメカニズムによって防止されます。
キー更新: QPACKはTLS 1.3のセッションキーに依存するため、キーの更新はQUICとTLS 1.3の機能によって行われます。QPACKはヘッダーデータ自体の暗号化は行わず、QUICの暗号化ペイロードとして転送されます。
0-RTTの再送リスク: QPACKは0-RTTをサポートしますが、0-RTTで送信されるヘッダーブロックは、デコーダがまだ認識していない動的テーブルエントリを参照できません。つまり、0-RTTヘッダーは静的テーブルエントリやリテラル、または過去のセッションから永続的にキャッシュされた動的テーブルエントリ(Post-Baseインデックスで参照可能)のみを使用できます。0-RTTデータは冪等であるべきというQUICの原則に従い、QPACKヘッダーブロックも適切に構成される必要があります [1]。
情報漏洩: 圧縮辞書(動的テーブル)の内容がサイドチャネル攻撃(例えばCRIME/BREACH攻撃の亜種)に利用される可能性は、HPACKと同様に存在します。しかし、QPACKではストリームごとのHOLブロッキングが回避されるため、攻撃者が特定のヘッダーを挿入してレスポンスを監視するような攻撃の機会は減少します。それでも、機密情報を含むヘッダーを動的テーブルに長期間保持することには注意が必要です。
実装メモ
QPACKを効率的かつ堅牢に実装するためには、以下の点に留意する必要があります。
MTU/Path MTU (PMTU): QUICはPMTU Discoveryをサポートしており、QPACKヘッダーブロックもPMTU内に収まるようにフラグメント化されます。ただし、ヘッダーブロック全体を一つのQUICパケットに収めることが理想的であり、特に動的テーブル更新のACKは迅速に処理されるべきです。フラグメント化されたヘッダーブロックの再構成ロジックを適切に実装する必要があります。
HOL blocking回避: QPACKの最も重要な設計目標です。デコーダは、
Required Insert Countを満たすまでヘッダーブロックのデコードを遅延させることができますが、これはヘッダーブロックごとの遅延であり、ストリーム間のHOLブロッキングではない点に注意が必要です。デコーダがブロックできるストリーム数にはSETTINGS_QPACK_BLOCKED_STREAMSで上限があるため、デコーダは過度に多くのストリームをブロックしないよう、動的テーブル更新の処理を優先すべきです。キュー制御: エンコーダは、デコーダが処理を完了した動的テーブルエントリの数を示す
HEADER_ACKを定期的に受信することで、自身の動的テーブルの状態を更新します。未確認の動的テーブル更新が多数キューに溜まっている場合、エンコーダは新しいヘッダーを動的テーブルのインデックス参照で圧縮することを避け、リテラルとして送信するなどのフォールバックメカニズムを持つべきです。これにより、デコーダが過負荷になるのを防ぎ、遅延を最小限に抑えられます。優先度: HTTP/3はストリームごとに優先度を設定できます。QPACKのエンコーダとデコーダは、優先度の高いストリームのヘッダーを優先的に処理し、動的テーブルの更新指示やACKも、優先度の高いストリームからのリクエストに対応するものを優先的に送信・処理するよう設計すべきです [3]。
まとめ
QPACKヘッダー圧縮 (RFC 9204) は、HTTP/3 (RFC 9114) がQUIC上で動作する上で不可欠な要素です。HTTP/2のHPACKが抱えていたヘッダー圧縮におけるHOLブロッキングの問題を、制御ストリームと非同期な動的テーブル更新メカニズムによって解決しました。これにより、HTTP/3はQUICのストリーム多重化の利点を最大限に活かし、低遅延かつ高効率なWeb通信を実現します。実装においては、動的テーブルの厳密な同期管理、エラー処理、そして0-RTTや優先度といったQUICの特性との連携が重要となります。
[1] Mike Bishop, et al. “HTTP/3”. RFC 9114. IETF. 2022年6月. [2] Martin Thomson, et al. “QPACK: Header Compression for HTTP/3”. RFC 9204. IETF. 2022年6月. [3] Mozilla Wiki Contributors. “HTTP3 FAQ”. Mozilla Wiki. 2024年2月14日.

