データ圧縮は、対象となるページ リソースのサイズを削減する、実績のあるパフォーマンス最適化手法です。しばらくの間、ウェブサーバーで主に gzip を使用して、HTML、CSS、JavaScript ファイルなどの一般的なテキストベースのページ リソースを圧縮し、クライアントに送信して解凍するのが一般的でした。その結果、ページの想定される動作に影響を与えることなく、リソースの読み込み時間が短縮されます。
gzip は単体で非常に効果的ですが、近年、ウェブでの圧縮がさらに改善されています。2016 年に Brotli アルゴリズムが Chrome に導入され、対象リソースの圧縮率が全体的に向上しました。2017 年末までに、すべての最新ブラウザが Brotli をサポートし、サーバーでのサポートも広まり始めました。最近、Chrome では ZStandard 圧縮がリリースされました。
ただし、それだけでは終わりません。Chrome チームは、共有辞書をウェブで使用できるように取り組んできました。この機能は、Brotli と ZStandard の両方のオリジン トライアルで利用できるようになります。共有辞書は、Brotli と ZStandard の圧縮を補完して、コードの更新頻度が高いウェブサイトの圧縮率を大幅に高めることができます。場合によっては、90% 以上の圧縮率を実現できます。この投稿では、共有辞書の仕組みと、オリジン トライアルに登録してウェブサイトで Brotli と ZStandard に使用する方法について詳しく説明します。
共有辞書の説明
圧縮とは、入力内の冗長なシーケンスを検出し、その情報を使用してはるかに小さい出力を作成するプロセスです。この出力は後で元に戻すことができます。圧縮は、リソースの読み込み時間を大幅に短縮できるため、ウェブで効果的です。Brotli と ZStandard のどちらも、圧縮辞書を使用すると効果をさらに高めることができます。圧縮辞書とは、これらのアルゴリズムが圧縮中に使用できる追加のパターンのコレクションです。実際、Brotli の高い効率性は、内部辞書を使用することである程度実現されています。
ただし、ユーザーが作成したカスタム辞書は、特定のリソースに固有のパターンを含む Brotli と ZStandard で使用できます。実際には、カスタム辞書は任意の入力に適用できる外部ファイルです。辞書は、アプリケーションの本番環境コードに固有のものである場合もあれば、あらゆるコンテンツに固有のものである場合もあります。特定の辞書が入力にどれだけ適しているかは、全体的な圧縮効率に大きな影響を与える可能性があります。入力の内容と非常に類似した辞書を使用すると、汎用的な内容や類似性のない内容の辞書を使用する場合よりも、圧縮率の高い出力が得られます。
カスタム圧縮辞書が効果的である例を次に示します。ウェブサイトが Angular フレームワークを使用しており、現在使用しているバージョンが 1.7.9 であるとします。このバージョンの Angular フレームワークは、圧縮されていない状態で約 172 KiB です。Brotli のデフォルト設定で圧縮すると、サイズは約 53 KiB になります。これにより、圧縮率はほぼ 70% になります。ただし、後で Angular 1.8.3 にアップグレードする場合は、このバージョンの Angular はバージョン 1.7.9 とほぼ同じサイズであるため、圧縮率は以前のバージョンとほぼ同じと見込まれます。
ここで、差分圧縮と呼ばれるプロセスを使用してカスタム辞書が役立ちます。これは、以前のバージョンのリソースの辞書を使用して、後続のバージョンを圧縮できる場合です。前のサンプルで、バージョン 1.7.9 を辞書として使用して Angular バージョン 1.8.3 を圧縮すると、出力は 4 KiB 強になります。これは、ほぼ 98% の圧縮率を表します。圧縮辞書は読み込みのパフォーマンスに大きな影響を与えることは明らかであり、その効果はすでに実際のアプリケーションで実証されています。
ただし、このフローをウェブで機能させるには課題があります。ただし、辞書を使用してリソースを圧縮する場合は、その辞書を使用してリソースを解凍する必要があります。このフロー(SDCH)は以前ウェブで試みられましたが、安全に実装することは困難でした。共有辞書圧縮に関する最新の提案では、静的リソースと動的リソースの両方に大きなメリットをもたらしながら、こうした懸念に対処しています。
Chrome が共有辞書のサポートをアドバタイズする方法
すべてのブラウザは、サポートしている圧縮アルゴリズムを Accept-Encoding
リクエスト ヘッダーでアドバタイズします。ヘッダーの内容は、サポートされているエンコードのカンマ区切りのリストです。
Accept-Encoding: gzip, br, zstd
この Accept-Encoding
ヘッダーは、リソースをリクエストしているブラウザが gzip、Brotli、ZStandard の圧縮アルゴリズムをサポートしていることを示します。リクエストに応答するウェブサーバーは、リクエストに応答する際に使用するアルゴリズムを決定できます。
共有辞書のサポートが有効で、リソースに関連する辞書が使用可能な場合、Accept-Encoding
ヘッダーに追加のトークンが追加されます。これらのトークンは、Brotli の場合は br-d
、Zstandard の場合は zstd-d
です。Chrome には、使用可能な辞書のハッシュも含まれます。これについては後述します。
Accept-Encoding: gzip, br, zstd, br-d, zstd-d
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
ウェブサーバーがこのトークンを認識するように構成されており、辞書を認識した場合、該当するエンコードの辞書を使用して圧縮されたリソースでそのリクエストに応答できます。実際にこの処理を行う方法は、リクエストが静的リソースと動的リソースのどちらを対象としているかによって異なります。
静的リソースの共有辞書圧縮
静的ページ リソースは、リクエストされた URL に対して常に同じレスポンスを生成するリソースです。圧縮可能な静的ページ リソースの一般的な例としては、JavaScript ファイルや CSS ファイルなどがあります。通常、これらのリソースは、キャッシュ目的で何らかの方法でバージョニングされます。ファイル名にファイルの内容のハッシュ(styles.abcd1234.css
など)を追加したり、リソースのフィンガープリントを取得する方法でバージョニングしたりします。静的リソースは長期間キャッシュに保存され、頻繁に更新される傾向があるため、これらのリソースタイプは、共有辞書が提供する差分圧縮に適しています。
静的リソースに辞書を指定するには、Use-As-Dictionary
レスポンス ヘッダーを設定します。ヘッダーにはいくつかの Key-Value ペアのいずれかを指定できますが、必須なのは match
のみです。match
には、辞書を使用するリソースパスを指定する URLPattern
構文を使用できます。
Use-As-Dictionary: match="/dist/styles.*.css"
Use-As-Dictionary
ヘッダーは、その中に指定されたパターンに一致するリソースの将来のバージョンに適用されるメカニズムと考えてください。たとえば、ウェブサイトのすべてのスタイルが 1 つの CSS ファイルに含まれているとします。わかりやすくするために、そのリソースの最初のバージョンが /dist/styles.v1.css
にあり、/dist/styles.*.css
の match
値を含む Use-As-Dictionary
レスポンス ヘッダーとともに送信されるとします。
しばらくして、ウェブサイトの CSS を更新し、/dist/styles.v2.css
に新しいバージョンを配置します。以前のバージョンの Use-As-Dictionary
レスポンス ヘッダーで使用された match
値がこのリクエストに適用されるため、ブラウザは、構造化フィールドのバイト シーケンスとしてエンコードされた辞書のハッシュを含む Available-Dictionary
ヘッダーを送信します。
Accept-Encoding: gzip, br, zstd, br-d, zstd-d
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
この時点で、一致する辞書が使用されるように、サーバーで圧縮を構成するのはサーバー側の責任です。その辞書で圧縮されたリソースが送信され、ユーザーのブラウザ キャッシュにある利用可能な辞書を使用して解凍されます。
ウェブサイトに新しいコードを頻繁に送信する場合は、差分圧縮が非常に役立ちます。ただし、このプロセスは柔軟です。ブラウザがユーザーのブラウザ キャッシュで辞書が利用可能であると判断しなかった場合、Accept-Encoding
ヘッダーに追加の br-d
トークンまたは zstd-d
トークンは指定されません。その場合、標準の圧縮フローが適用されます。
動的リソースの共有辞書圧縮
動的リソースは、共有辞書圧縮のメリットも享受できます。動的リソースは、コンテキストに基づいて変化するリソースです。たとえば、ニュース速報に応じてメインページが頻繁に更新されるニュース ウェブサイトなどです。HTML ドキュメントは多くの場合、動的リソースです。このような場合、辞書にはサイトの一般的な HTML 構造とテンプレート コードのほとんどを含めることができ、各ページの一意の部分のみが送信される圧縮されたページを作成できます。
動的に生成されるリソースの性質上、後で使用するために辞書をクライアントに読み込む必要があります。辞書を事前に読み込むということは、動的リソースに共有辞書圧縮を適用することが推測的であるということです。このような場合は、ウェブサイトに十分なトラフィックが得られ、辞書の費用を多数のナビゲーションに分散して償却できることが望まれます。試す場合は、まずページの HTML で <link>
要素を使用して辞書の場所を指定します。
<link rel="dictionary" href="/dictionary.dat">
Chrome はこの <link>
要素を検出すると、ページがアイドル状態になったときに辞書をフェッチすることがあります。帯域幅の競合を回避するため、優先度は低くなります。辞書自体のレスポンスでは、Use-As-Dictionary
ヘッダーを指定し、適用される動的リソースパスを指定する必要があります。
Use-As-Dictionary: match="/product/*"
以降のフローは、静的リソースの場合とほぼ同じです。ブラウザは、辞書自体が一致するリソースに適用されることを認識し、辞書の内容のハッシュを含む Available-Dictionary
ヘッダーをリクエストに付加します。これは、前述の静的リソースのフローと同様です。
ビルド時に静的リソースを圧縮する
バンドラーに精通している場合は、ビルド時にリソースを圧縮し、圧縮されたリソースを提供するバンドラーのさまざまなプラグインに精通しているかもしれません。たとえば、Apache では、リクエスト時にディレクティブを使用して、事前に圧縮されたリソースを提供することができます。
圧縮をサポートする Node.js ベースのバンドラーのほとんどは、Node の組み込み Zlib ライブラリを使用します。Zlib は Brotli をサポートしており、Brotli を使用するバンドルツールは通常、オプションを Zlib に直接渡すインターフェースを提供します。Zlib は辞書支援圧縮をサポートしています。辞書の使用をサポートするバンドルツールをいくつか紹介します。
- webpack の
CompressionWebpackPlugin
(compressionOptions
インターフェースを介して)。 rollup-plugin-brotli
はoptions
構成を提供します。この構成は、辞書を指定できる Node.js の Zlib に直接渡されます。- esbuild の
esbuild-plugin-compress
サードパーティ プラグインも、Node.js の Zlib オプションにアクセスできます。
特定のバージョンのリソースで使用可能な辞書は、リソースの以前のバージョンのいずれかを使用できます。そのため、ユーザー トラフィックを分析し、それに応じて計画を立てることが重要です。バランスを重視し、できるだけ多くのリピーターにとって有益なリソースを作成します。CDN プロバイダは現在、共有辞書圧縮をテストしています。まだ一般公開されていない実装もありますが、今後公開される予定です。
試してみる
共有辞書圧縮をブラウザの既存の圧縮機能と統合すると、更新された本番環境コードを頻繁にリリースし、リピーターから大量のトラフィックを受信するウェブサイトの読み込みパフォーマンスを大幅に改善できます。共有辞書圧縮を試すには、次の 2 つの方法があります。
- 共有辞書圧縮を自分でいじって動作を把握したい場合は、
chrome://flags
ページで圧縮辞書転送試験運用版機能を有効にします。 - 本番環境のウェブサイトでこの機能を試して、共有辞書圧縮が実際のユーザーにどのように役立つかを確認したい場合は、オリジン トライアルに登録してトークンを取得し、オリジン トライアルの仕組みをご覧ください。
まとめ
Google は、ウェブ上の圧縮技術のこの大きな進歩と、ユーザーが日常的に使用する既存のアプリケーションをどれだけ高速化できるかに大きな期待を寄せています。ぜひお試しください。また、お試しいただいた場合は、ご意見をお聞かせください。バグを見つけた場合は、crbug.com で報告してください。その他のリソースやツールについては、use-as-dictionary.com をご覧ください。さらに詳しく仕組みを知りたい場合は、説明動画をご覧ください。