弁護士ドットコム株式会社 Creators’ blog

弁護士ドットコムがエンジニア・デザイナーのサービス開発事例やデザイン活動を発信する公式ブログです。

Compression Dictionary Transport の PHP 実装 - Web圧縮の次世代技術

ブログタイトル「Compression Dictionary Transport の PHP 実装 - Web圧縮の次世代技術」が記載されている。

はじめに

現代の Web アプリケーションにおいて、パフォーマンスはユーザー体験を左右する重要な要素です。これまで、gzip、Brotli、Zstandard といった優れた圧縮技術が、サーバーからクライアントへのデータ転送量を削減し、Web サイトの高速化に貢献してきました。

しかし、SPA(シングルページアプリケーション)や API 駆動型のアーキテクチャが主流となる中で、新たな課題が浮上しています。それは、数十 KB 程度の JSON データや動的に読み込まれる UI コンポーネントといった、比較的小さな HTTP レスポンスにおいて、圧縮効率が上がりにくいという問題です。既存の圧縮アルゴリズムは、データ内にある程度の繰り返しパターンが存在しなければ、その効果を十分に発揮できないという特性があるためです。

この課題を解決すべく登場したのが、新しい Web 標準仕様「Compression Dictionary Transport」です。

この記事では、この Compression Dictionary Transport の PHP への実装について解説します。

発想の転換:「繰り返しパターン」を事前に共有する

Compression Dictionary Transport の核心的なアイデアは、非常にシンプルです。

圧縮したいデータの中に繰り返しパターンが少ないなら、サーバーとブラウザの間で、あらかじめ「共通の辞書」を共有しておけば良い。

この「辞書」とは、サイトで頻繁に使われる文字列、HTML タグの構造、JavaScript の定型コード、CSS のクラス名など、予想される繰り返しパターンを集めたファイルです。 これを事前に共有することで、たとえ小さなデータであっても、辞書内のパターンと一致する部分を効率的に参照へと置き換え、驚異的な圧縮率を実現します。

Compression Dictionary Transport の仕組み

Compression Dictionary Transport1 は現在、IETF で標準化が進められており、主要なブラウザでの実装も始まりつつあります。

その基本的なワークフローは、Brotli 実装と Zstandard 実装で共通しており、HTTP ヘッダを介したサーバーとクライアントの連携によって成り立っています。

主要なHTTPヘッダー

ヘッダー 役割
Accept-Encoding クライアントが辞書圧縮 (dcb, dcz など) をサポートしていることをサーバーに通知します。
Use-As-Dictionary サーバーが辞書そのものを配信する際に使用し、ブラウザにこれを辞書としてキャッシュするよう指示します。
Available-Dictionary クライアントがキャッシュ済みの辞書のハッシュ値をサーバーに通知し、利用可能であることを伝えます。
Content-Encoding サーバーがレスポンスを辞書圧縮したことを示します (dcb または dcz)。
Vary Accept-Encoding, Available-Dictionary を指定し、プロキシキャッシュなどが正しく動作するようにします。

基本的なワークフロー

  1. 辞書の配布: サーバーが Use-As-Dictionary ヘッダーを付けて辞書リソースを配信する。
  2. 辞書の保存: ブラウザは辞書を保存し、その内容から SHA-256 ハッシュ値を計算する。
  3. 辞書の利用通知: 次回以降のリクエストで、ブラウザは Available-Dictionary ヘッダーに計算したハッシュ値を含めて送信する。
  4. 辞書圧縮の実行: サーバーは Available-Dictionary ヘッダーを受け取ると、自身が保持する辞書のハッシュ値と照合する。一致すれば、その辞書を使ってレスポンスを圧縮する。
  5. レスポンスの返却: サーバーは Content-Encoding ヘッダー(例: dcb, dcz)を付与して、辞書圧縮されたデータをクライアントに返す。
  6. データの伸張: レスポンスを受け取ったブラウザは、指定された共有辞書を使ってデータを正しく伸張(解凍)し、元のデータを復元する。

この仕組みは、特に以下のようなユースケースで絶大な効果を発揮します。

  • SPA: アプリケーションのバージョンごとに JS バンドルから辞書を生成し、API レスポンスの圧縮に利用する。
  • 頻繁に更新されるライブデータ: 共通するデータ構造を辞書として共有し、差分だけを効率的に転送する。
  • マイクロサービス間のAPI通信: サービス間で共通の辞書を用意し、通信ペイロードを削減する。

Brotli による実装 (Dictionary-Compressed Brotli)

php-ext-brotli では、Content-Encoding: dcb として Compression Dictionary Transport を実装します。

DCB バイナリフォーマット

dcb で圧縮されたデータは、仕様に従ったフォーマットで構築されます。

+--------------+-------------------------------+------------------+
| Magic Header | Dictionary SHA-256 Hash       | Compressed Data  |
| (4 bytes)    | (32 bytes)                    | (variable)       |
+--------------+-------------------------------+------------------+
| 0xff444342   | SHA-256 of dictionary content | Brotli compressed|
|              |                               | with dictionary  |
+--------------+-------------------------------+------------------+

PHP での実装詳細 (php-ext-brotli)

実装は、PHP の出力バッファリング (ob_start) をフックする形で行われます。

1. 設定と有効化

php.ini または ini_set で辞書へのパスを指定し、ob_start でハンドラを登録します。

<?php
// 辞書圧縮 (dcb) を使うための辞書ファイルへの絶対パスを指定
ini_set('brotli.output_compression_dictionary', '/path/to/your/shared.dict');

// スクリプトの冒頭でハンドラを登録
ob_start('ob_brotli_handler');

// これ以降の出力が自動的に辞書圧縮の対象となる
echo "Hello";
?>

2. C言語レベルのコアロジック

内部では、以下の処理が実行されます。

  • エンコーディングの解析: Accept-Encoding ヘッダを解析し、dcb が指定されているかをビットフラグで管理する。
  • 辞書の検証: HTTP_AVAILABLE_DICTIONARY ヘッダの値と、サーバー側で指定された辞書から計算した SHA-256 ハッシュ値を比較する。ハッシュが一致しない場合は、安全に通常の Brotli 圧縮 (br) にフォールバックする。
  • Brotliエンジンへの辞書アタッチ: Brotli のエンコーダーに辞書をセットする。
  • DCBフォーマットの構築: 圧縮後、レスポンスの先頭にマジックヘッダーと辞書ハッシュを追加して、最終的な dcb フォーマットのデータを作成する。
/* brotli.c: 辞書検証のロジック(簡略版) */
zval *available = zend_hash_str_find(..., "HTTP_AVAILABLE_DICTIONARY", ...);
if (available) {
    // サーバー側辞書の SHA-256 ハッシュを計算
    PHP_SHA256_CTX context;
    PHP_SHA256Init(&context);
    PHP_SHA256Update(&context, ...);
    PHP_SHA256Final(ctx->dict_digest, &context);

    // Base64 エンコードしてクライアントからのハッシュと比較
    zend_string *b64 = php_base64_encode(ctx->dict_digest, 32);
    if (memcmp(ZSTR_VAL(b64), Z_STRVAL_P(available) + 1, ...)) {
        // 不一致の場合は dcb フラグを解除し、br 圧縮にフォールバック
        BROTLI_G(compression_coding) &= ~PHP_BROTLI_ENCODING_DCB;
    }
}

/* brotli.c: 圧縮コンテキストへの辞書適用(簡略版) */
if (dict) { // dict は読み込まれた辞書データ
    // 辞書データを準備
    ctx->dictionary = BrotliEncoderPrepareDictionary(BROTLI_SHARED_DICTIONARY_RAW, ZSTR_LEN(dict), ZSTR_VAL(dict), BROTLI_MAX_QUALITY, NULL, NULL, NULL);
    // 準備した辞書をエンコーダーにアタッチ
    if (ctx->dictionary == NULL
        || !BrotliEncoderAttachPreparedDictionary(ctx->encoder,
                                                  ctx->dictionary)) {
        // エラー処理
        return FAILURE;
    }
}

このフォールバック機構により、Compression Dictionary Transport が利用できない環境でもアプリケーションは問題なく動作します。

Zstandard による実装 (Dictionary-Compressed ZStandard)

php-ext-zstd では、Content-Encoding: dcz として Compression Dictionary Transport を実装します。

DCZ バイナリフォーマット

dcz で圧縮されたデータは、仕様に従ったフォーマットで構築されます。

+--------------------+-------------------------------+------------------+
| Magic Header       | Dictionary SHA-256 Hash       | Compressed Data  |
| (8 bytes)          | (32 bytes)                    | (variable)       |
+--------------------+-------------------------------+------------------+
| 0x5e2a4d1820000000 | SHA-256 of dictionary content | ZSTD compressed  |
|                    |                               | with dictionary  |
+--------------------+-------------------------------+------------------+

PHP での実装詳細 (php-ext-zstd)

こちらも ob_start を利用した出力ハンドラとして実装されています。

1. 設定と有効化

php.ini または ini_set で辞書へのパスを指定し、ob_start でハンドラを登録します。

<?php
// 辞書圧縮 (dcz) を使うための辞書ファイルへの絶対パスを指定
ini_set('zstd.output_compression_dict', '/path/to/your/dictionary.txt');

// スクリプトの冒頭でハンドラを登録
ob_start('ob_zstd_handler');

// これ以降の出力が自動的に辞書圧縮の対象となる
echo "Hello";
?>

2. C言語レベルのコアロジック

基本的な流れは Brotli 実装と似ていますが、Zstandard ライブラリの機能を使って実装されています。

  • エンコーディングの解析: 同様に Accept-Encoding を解析し dcz フラグを管理する。
  • 辞書の検証: HTTP_AVAILABLE_DICTIONARY ヘッダとサーバー側辞書のハッシュ値を比較する検証ロジックも同様である。不一致の場合は通常の zstd 圧縮にフォールバックする。
  • Zstandardエンジンへの辞書適用: ZSTD_createCDict で辞書オブジェクトを作成し、ZSTD_CCtx_refCDict で圧縮コンテキストにセットする。
  • DCZフォーマットの構築: 圧縮ストリームの先頭に、仕様で定められた 8 バイトのマジックヘッダーと 32 バイトの辞書ハッシュを書き込む。
/* zstd.c: 辞書検証のロジック(簡略版) */
zval *available = zend_hash_str_find(..., "HTTP_AVAILABLE_DICTIONARY", ...);
if (available) {
    // サーバー側辞書の SHA-256 ハッシュを計算
    PHP_SHA256_CTX context;
    PHP_SHA256Init(&context);
    PHP_SHA256Update(&context, ...);
    PHP_SHA256Final(ctx->dict_digest, &context);

    // Base64 エンコードしてクライアントからのハッシュと比較
    zend_string *b64 = php_base64_encode(ctx->dict_digest, 32);
    if (memcmp(ZSTR_VAL(b64), Z_STRVAL_P(available) + 1, ...)) {
        // 不一致の場合は dcz フラグを解除し、zstd 圧縮にフォールバック
        ZSTD_G(compression_coding) &= ~PHP_ZSTD_ENCODING_DCZ;
    }
}

/* zstd.c: 圧縮コンテキストへの辞書適用(簡略版) */
if (dict) { // dictは読み込まれた辞書データ
    ctx->cdict = ZSTD_createCDict(ZSTR_VAL(dict), ZSTR_LEN(dict), (int)level);
    if (!ctx->cdict) {
        // エラー処理
        return FAILURE;
    }

    ZSTD_CCtx_refCDict(ctx->cctx, ctx->cdict);
}

テスト

compression-dictionary-transport-shop-demo というデモを使用してテストを行います。

1. ファイルの準備

まず、デモ用のファイルをいくつか変更・作成します。

public/index.html の末尾にある辞書のリンクを、PHP で配信するように変更します。

- <link rel="dictionary" href="/dictionary" />
- <link rel="compression-dictionary" href="/dictionary" />
+ <link rel="compression-dictionary" href="./dictionary.php" />

public/dictionary.php を以下の内容で作成します2

<?php
// 辞書の配布用のレスポンスを生成
$file = __DIR__ . '/items/shop.dict'; // 辞書ファイルのパス
header('Use-As-Dictionary: match="/items/*"'); // この辞書を適用するパスを指定
header('Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT');
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($file));
header('Accept-Ranges: bytes');
readfile($file);

次に、デモ用の辞書とコンテンツを配置します。

  • data/shop.dictpublic/items/shop.dict にコピーする。
  • data/*.htmlpublic/items/*.php としてコピーする。

コピーした各 PHP ファイル(1f3bf.phpなど)の先頭に、設定を読み込むための include 文を追加します。

+ <?php include __DIR__ . '/header.php'; ?>
  <!DOCTYPE html>
  <!--
   Copyright 2022 Google LLC

圧縮設定を記述する public/items/header.php を以下の内容で作成します。

<?php
// Brotli (dcb) による辞書圧縮の設定
//
// クライアントが dcb をサポートしている場合のみ、辞書ファイルを指定します。
// これにより、dcb 非対応のクライアントには通常の Brotli(br)圧縮が適用されます。
if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'dcb') !== false) {
    ini_set('brotli.output_compression_dict', __DIR__ . '/shop.dict');
}
ob_start('ob_brotli_handler');

// // Zstandard (dcz) を試す場合はこちらを有効化します
// if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'dcz') !== false) {
//     ini_set('zstd.output_compression_dict', __DIR__ . '/shop.dict');
// }
// ob_start('ob_zstd_handler');

最終的に、public ディレクトリは以下のような構成になります。

public
|-- about.html
|-- dictionary.php
|-- index.html
|-- items
|   |-- 1f3bf.php
|   |-- 1f45e.php
|   |-- (他のアイテムPHPファイル)...
|   |-- header.php
|   `-- shop.dict
`-- static
    |-- (省略)...

この public ディレクトリをドキュメントルートとして、PHP が動作する Web サーバーを起動します。

2. 動作確認

Compression Dictionary Transport は Chrome が先行してサポートしているため、Chrome ブラウザで確認します。

まず index.html にアクセスします。ページと共に dictionary.php が読み込まれ、辞書がブラウザに登録されます。登録された辞書は chrome://net-internals/#sharedDictionary で確認できます。

sharedDictionary

次に、商品アイテムページ(items/1f45e.phpなど)にアクセスします。Chrome の開発者ツールでネットワークリクエストを確認すると、以下のように動作していることがわかります。

  • リクエストヘッダに辞書圧縮をサポートすることを示す Accept-Encoding: ..., dcb, ... が含まれている。
  • リクエストヘッダに利用可能な辞書を通知する Available-Dictionary が含まれている。
  • レスポンスヘッダに dcb で圧縮されたことを示す Content-Encoding: dcb が返されている。

items-header

Content-Encoding が適用された後のコンテンツサイズを比較すると、その効果は一目瞭然です。

items-size

通常の Brotli (br) 圧縮と比較しても、辞書圧縮 (dcb) ではデータサイズが大幅に削減されていることがわかります。

課題と展望

課題

  1. ブラウザサポート: 2025 年現在、Compression Dictionary Transport はまだ実験的な段階にあり、Chrome が先行している状況である。普及には他のブラウザでの標準サポートが待たれる。
  2. 辞書管理: サイトのコンテンツに合わせて最適な辞書をどのように生成し、更新していくかという運用戦略が重要になる。
  3. キャッシュ戦略: Vary: Accept-Encoding, Available-Dictionary ヘッダーを正しく扱える CDN やプロキシの設定が必要である。
  4. セキュリティ: 辞書のハッシュ検証は、意図しないデータ展開を防ぐための重要なセキュリティ機構である。実装ではこの検証が必須となる。

展望

  • 自動辞書生成: AI や機械学習を用いて、サイトのコンテンツから最適な辞書を自動生成するようなツールの登場が期待される。
  • 標準化の進展: IETF での標準化が完了し、主要なブラウザや CDN でのサポートすることで、普及が進む。
  • さらなる応用: Web コンテンツ配信だけでなく、マイクロサービス間の通信ペイロード削減など、活用の幅が広がっていくだろう。

まとめ

Compression Dictionary Transport は、Web パフォーマンスを次のレベルへ引き上げる可能性を秘めた、非常に有望な技術です。

今回、PHP の拡張機能である php-ext-brotliphp-ext-zstd の両方にこの機能が実装されたことで、PHP ユーザーがこの最先端技術の恩恵を受ける道筋が拓かれました。

実装の技術的ハイライト:

  • 透過的な実装: ob_startphp.ini の設定により、既存のアプリケーションコードをほぼ変更することなく導入できる。
  • 堅牢なフォールバック: 辞書圧縮が使えない環境でも、自動的に通常の Brotli/Zstandard 圧縮に切り替わる安全な設計である。
  • 仕様準拠: IETF で策定中の仕様に基づいた、正確なバイナリフォーマットとヘッダー処理が実装されている。

ぜひこの新しい圧縮技術を試し、その効果を体感してみてください。


  1. MDN Web Doc: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Compression_dictionary_transport
  2. 辞書ファイルを配布すればよいだけなので PHP のような動的処理ではなくて Web サーバーから配布するようにしても問題ない(Use-As-Dictionary ヘッダは必要)