共有辞書で圧縮効率を向上

データ圧縮は、実績のあるパフォーマンス最適化手法で、対象となるページリソースのサイズを削減します。これまでは、主にウェブサーバーで 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 のみです。これは、辞書を使用するリソースパスを指定する URLPattern 構文を受け入れます。

Use-As-Dictionary: match="/dist/styles.*.css"

Use-As-Dictionary ヘッダーは、指定されたパターンに一致するリソースの将来のバージョンに適用されるメカニズムと考えてください。たとえば、ウェブサイトですべてのスタイルが 1 つの CSS ファイルで配送されているとします。わかりやすくするため、そのリソースの最初のバージョンが /dist/styles.v1.css にあり、/dist/styles.*.cssmatch 値を含む Use-As-Dictionary レスポンス ヘッダーとともに送信されるとします。

一定期間が経過したら、ウェブサイトの CSS を更新し、/dist/styles.v2.css にある新しいバージョンの 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 をサポートしています。Zlib を使用するバンドラは通常、オプションを Zlib に直接渡すインターフェースを提供し、Zlib は辞書を利用した圧縮をサポートしています。辞書の使用をサポートするバンドラをいくつか紹介します。

なお、リソースの任意のバージョンの利用可能なディクショナリで、以前のバージョンのリソースの 1 つを使用できることに注意してください。つまり、ユーザー トラフィックを分析し、それに応じて計画を立てる必要があります。バランスをとり、できる限り多くのリピーターに利益をもたらすリソースを生成しましょう。CDN プロバイダは現在、共有辞書圧縮の試験運用を行っています。公開可能な実装はまだありませんが、今後変更される可能性があります。

試してみましょう。

共有辞書の圧縮をブラウザの既存の圧縮機能と統合すると、更新された本番コードを頻繁に配信し、リピーターから大量のトラフィックを受け取るウェブサイトの読み込みパフォーマンスを大幅に改善できる可能性があります。共有辞書圧縮を試したい場合は、次の 2 つの方法があります。

  1. 共有辞書圧縮を自力で試してその動作を確かめたい場合は、chrome://flags ページで圧縮辞書転送の試験運用版機能を有効にできます。
  2. 本番環境用ウェブサイトでこれをお試しになり、共有辞書圧縮が実際のユーザーにどのように役立つかをご覧になりたい場合は、オリジン トライアルに登録してトークンを取得してください。また、オリジン トライアルの仕組みをご確認ください。

おわりに

ウェブにおける圧縮技術の大幅な進歩と、ユーザーが毎日使用する既存のアプリケーションがどれほど高速化されるかについて、Google は大きな期待を寄せています。ぜひお試しいただき、ぜひ皆様のご意見をお寄せください。バグを見つけた場合は、crbug.com で報告してください。その他のリソースやツールについては、use-as-dictionary.com をご覧ください。最後に、この仕組みについて詳しく知りたい場合は、解説をご覧ください。