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 でハンドラを登録します。



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 フォーマットのデータを作成する。
zval *available = zend_hash_str_find(..., "HTTP_AVAILABLE_DICTIONARY", ...);
if (available) {
    
    PHP_SHA256_CTX context;
    PHP_SHA256Init(&context);
    PHP_SHA256Update(&context, ...);
    PHP_SHA256Final(ctx->dict_digest, &context);

    
    zend_string *b64 = php_base64_encode(ctx->dict_digest, 32);
    if (memcmp(ZSTR_VAL(b64), Z_STRVAL_P(available) + 1, ...)) {
        
        BROTLI_G(compression_coding) &= ~PHP_BROTLI_ENCODING_DCB;
    }
}


if (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 でハンドラを登録します。



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 バイトの辞書ハッシュを書き込む。
zval *available = zend_hash_str_find(..., "HTTP_AVAILABLE_DICTIONARY", ...);
if (available) {
    
    PHP_SHA256_CTX context;
    PHP_SHA256Init(&context);
    PHP_SHA256Update(&context, ...);
    PHP_SHA256Final(ctx->dict_digest, &context);

    
    zend_string *b64 = php_base64_encode(ctx->dict_digest, 32);
    if (memcmp(ZSTR_VAL(b64), Z_STRVAL_P(available) + 1, ...)) {
        
        ZSTD_G(compression_coding) &= ~PHP_ZSTD_ENCODING_DCZ;
    }
}


if (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 で配信するように変更します。

- 
- 
+ 

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



$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 文を追加します。

+ 
  
  




元の記事を確認する

関連記事