WEBサイトを高速化し、オリジンサーバーの負荷も削減するという目的でCDNを新規導入するケースが増えています。そこで、今回はWEBサイトの高速化・オリジンサーバーの負荷削減ができる「CDNで行うコンテンツ圧縮」の概要と注意点について掲載致します。
コンテンツ圧縮(gzip)とは?
コンテンツ圧縮で有名なのはgzipによる圧縮です。gzipは「RFC 1952に記述されているファイル圧縮プログラムgzipで生成されたエンコーディングフォーマット」のことで、PCやMACでもZIPにしてファイルを提供したりするとおもいますが、WEBの世界でも同じようなことをしてコンテンツのサイズを小さくすることが出来ます。
圧縮の仕組み
gzip圧縮はテキストコンテンツ内で似たような文字列を見つけそれらの文字列を置換することによって、全体のコンテンツサイズを縮小します。 HTMLやCSS、JSは空白・タグのように似たような文字列の繰り返しをたくさん含んでいるため、通常のファイルよりもgzip圧縮の効果が高いといえます。
WEB世界でのコンテンツ圧縮
PCやMACではファイルの圧縮・解凍する場合、圧縮形式に対応した何かしらのソフトを利用しているとおもいます。WEBの世界ではこのソフトに置き換わる物がブラウザー(解凍専用)とWEBサーバー(圧縮専用)です。WEBサーバーの前段にCDNを置いた場合は、WEBサーバーの代わりにCDNがコンテンツを圧縮するようになります。
コンテンツ圧縮方式の種類
コンテンツ圧縮で有名なのはgzip形式ですが、gzipの他にも様々な種類が存在し圧縮率とCPUコストが異なります。
gzip
アルゴリズム:LZ77、ハフマン符号
フォーマット:Gnu zip
deflate
アルゴリズム:LZ77、ハフマン符号
フォーマット:zlib
bzip2
特徴:deflateやgzipよりも圧縮率が高いが非標準
アルゴリズム:Burrows-Wheeler transform (BWT)
フォーマット:bzip2
brotli
特徴:Googleが開発したアルゴリズム。deflateやgzipよりも高い圧縮率
アルゴリズム:LZ77、ハフマン符号、2nd order context modeling
フォーマット:brotli
オリジンサーバー側での圧縮対応形式
主要なミドルウェアはコンテンツ圧縮をサポートしています。ここでは詳しく触れませんがapache については mod_deflateによりサポートされ、nginx はgzip onと設定することにより圧縮が有効化されます。以前一部人気があったlighttpd は一風変ってbzip2にも対応しています。
圧縮対象となるコンテンツ
ApacheやNginxでは圧縮させる対象のコンテンツを設定することができます。そのため、どのようなコンテンツでも指定すれば圧縮配信することが可能といえますが、だからといって全てのコンテンツを圧縮してしまうことは避けるべきです。
圧縮はCPUリソースを消費する
サーバー側は圧縮をオンザフライで行う場合、当然ながらCPUリソースを消費します。そして、実は圧縮コンテンツをクライアント側で解凍展開する際にもCPUリソースを消費します。近年サーバー側についてはリソースが潤沢なケースが多いのでそれほど問題にはならないと思いますが、クライアント側はどのような端末で接続しているか不明なため、特にスマホでの閲覧が多いサイトは貴重なスマホのCPUリソースを消費させてしまう恐れがあります。これは、スマホのページレンダリングに少なからず影響させてしまうと言えるでしょう。
そのため、サーバー側にもクライアント側にも優しい設計を考慮すべきで、一般的には非圧縮ファイルであるHTML、CSS、JavaScriptなど圧縮効果が高いコンテンツをMime Typesで圧縮対象に指定します。そして、逆に圧縮しても効果が低い画像や動画などのコンテンツは圧縮対象から省くべきです。
圧縮対応形式を通知させる仕組み
CDNやサーバー側で行う圧縮形式にクライアントも同じように対応していなければ解凍ができません。そのため、HTTPヘッダーを利用してクライアントはどのような圧縮形式のファイルを解凍できるかをサーバーに通知します。
Accept-Encodingで伝える
クライアント側のブラウザーがHTTPリクエスト内の Accept-Encoding ヘッダーを用いて、サーバー側にこのような圧縮された形式のファイルを解凍できますよ!と通知します。複数対応している場合は、カンマ区切りで記述されます。
Content-Encodingで応える
サーバー側は、受け取ったAccept-Encoding ヘッダーの中身をみて自身が圧縮可能な形式があった場合、このような形式で圧縮しました!という情報をHTTPレスポンス内の Content-Encoding ヘッダーでクライアント側に通知します。
クライアント側リクエストヘッダー例
Accept-Encoding:gzip, deflate, br
CDN・サーバー側レスポンスヘッダー例
Content-Encoding: gzip
このようにして、まずクライアントが起点となり対応した形式を通知し、受け取ったサーバー側で圧縮形式に対応している場合は、コンテンツを圧縮し同時に圧縮形式をHTTPヘッダーで通知するというやりとりによって互いの対応形式を判別しています。
ブラウザの対応状況
サーバー・CDN(圧縮する側)のお話をしてきましたが、ブラウザー(解凍する側)もこれらの圧縮形式に対応していないとなんの意味もありません。とはいえ、通常はブラウザーが対応している形式をAccept-Encoding内に格納してリクエストするため、意識すべきところは、サーバーやCDNの圧縮する側といえるでしょう。
gzip対応状況
Can I USEで確認するとモダンブラウザではgzip圧縮に対応していることが分かると思います。
CDNでコンテンツ圧縮
さて、CDNサービスはWEBサーバーと同様にコンテンツ圧縮機能が標準搭載されているケースと、CDNでは圧縮せずオリジンサーバーに任せる2パターンがあります。CDN側でコンテンツ圧縮をサポートしていると、オリジンサーバー側は圧縮をおこなうCPUコストを気にすることが無くなりますしキャッシュできないリクエストが大量に来た場合にも効力を発揮するためとても有力といえます。
CDN側の圧縮対象コンテンツは様々
CDN側で圧縮対象となるコンテンツは予め定められていることが多く、弊社もエッジキャッシュCDNドキュメントに対応形式を掲載しています。そのため、CDNで対応形式以外のコンテンツを圧縮させたい場合はやはりオリジンサーバー側で圧縮対応が必要となりますが、大抵の主要コンテンツは網羅されているはずです。
CDNでコンテンツ圧縮時の配信問題
CDN側でコンテンツ圧縮をどのように扱うかというのは非常に重要です。これは設定や付与するヘッダーを誤ると以下の様な問題が発生します。ここではそれぞれのパターンで圧縮対応しているクライアントとそうでないクライアントが2つあることを前提とします。
非圧縮コンテンツを返却しレスポンス低下
- 圧縮非対応のクライアントがCDNへアクセスします。
- CDNは圧縮非対応のため、オリジンサーバーから受け取った非圧縮ファイルをCDN内部にキャッシュします。
- 圧縮対応しているクライアントがCDNへアクセスします。
- CDNは既にキャッシュ済の非圧縮ファイルをクライアントに転送してしまいます。
このパターンは圧縮対応のクライアント側に非圧縮である大きめのコンテンツが配信されるだけで普通に閲覧が可能です。しかし次のようなパターンはもっと悲惨なことになります。
クライアント側で認識エラー
- 圧縮対応しているクライアントがCDNへアクセスします。
- CDNは圧縮対応と判断しオリジンサーバーから受け取った非圧縮ファイルを圧縮しCDN内部にキャッシュします。
- 圧縮対応していないクライアントがCDNにアクセスすると、CDNは既にある圧縮済コンテンツを返却します。
- クライアント側は圧縮されたコンテンツを解凍できないため、コンテンツが見られません。
こちらは既に圧縮済コンテンツをキャッシュしているため、圧縮非対応のクライアントからのアクセスは全てエラーとなってしまいます。
Varyヘッダ-で問題回避
このような2つの悲惨な状況を回避するためには、いくつか方法がありますが一番簡単なのはオリジンサーバーでVaryヘッダーをレスポンスすることです。
Varyヘッダーの概要
HTTP / 1.1サーバーは、ネゴシエーションの対象となるキャッシュ可能なレスポンスとともにVaryヘッダフィールドを含めるべきである(SHOULD)。そうすることにより、キャッシュはそのリソースに対する将来の要求を適切に解釈し、そのリソースに対するネゴシエーションの存在についてクライアントに通知することができます。
Varyヘッダーは、CDNがキャッシュ済かどうかを判断する材料であるパスとホストヘッダー以外のリクエストヘッダーでどの部分を考慮すべきかというのをCDNに伝えることが出来ます。圧縮コンテンツの場合はAccept-Encoding が指定されますが、その他関連するヘッダーの名前を複数カンマ区切りでリスト指定が可能です。
CDNでキャッシュを分離
このように、CDNはVaryヘッダーによって同じ値のAccept-Encoding を保持している場合はキャッシュを利用し、それ以外の場合はオリジンサーバーにコンテンツを再取得しにいくようになります。よって、非圧縮キャッシュと圧縮済キャッシュ2つをCDNが保持しクライアントに合わせて適切にレスポンスすることが出来ます。
Varyヘッダーの罠を回避するCDNの正規化
Varyヘッダーがレスポンスされると、CDNはVaryヘッダーの内容別にキャッシュを保持し先ほどのような問題を回避します。しかし、世界中のブラウザーやアプリではAccept-Encoding の中身だけでも沢山の種類や記述順番があるため、全ての種類別にCDNがキャッシュを分けてしまうと逆にHIT率が大幅に低下するという新たな問題が発生します。
Accept-Encodingのパターン調査
弊社で10万リクエストのサンプルからAccept-Encoding の内容を調査した所、なんと40以上のパターンがありました。これは、何も対策をしなければCDNは40個のキャッシュをムダに保持してしまうことになります。そこで、CDNではAccept-Encoding の内容を正規化したり形式を絞りキャッシュHIT率を高める対応が必須といえます。弊社CDNも gzip > deflate という順番でリクエストヘッダーの内容を正規化し、これらの正規化した内容に従ってオリジンサーバーにリクエストしHIT率低下を削減しています。
まとめ
ページの読み込み速度を向上するためにできる対策は、リソースの最適化と合計ダウンロード サイズを最小限に抑えることです。
そのため、CDN側のコンテンツ圧縮によって転送量削減とレンダリングスピードを向上させることはマストといっても過言ではありません。しかし、だからといって圧縮・解凍コストを無視することや、CDNを通した場合の問題を軽視すると逆効果となります。適切なコンテンツのみ圧縮しCDNやプロキシーを考慮するためのVaryヘッダーで問題を未然に回避することを心がける必要があります。