今回は既にCDNを利用している方も、利用していない方も今すぐ出来るHTTPヘッダチューニングをご紹介します

HTTPヘッダのお話では、レスポンスヘッダやリクエストヘッダの中に何があり、どういう役割なのか簡単にまとめてみましたが、非常に多くのデータがWEBのやりとりで利用されていることが分かったと思います。

CDN利用時に利用者側が出来るチューニングは、オリジンサーバー側のApacheやNginxなどでどのようなHTTPレスポンスヘッダを付与するのか、しないのかということを決めるヘッダチューニングです。

Last-Modified

Last-ModifiedヘッダはレスポンスヘッダのひとつでApacheやNginxなどのWEBサーバー側で適切な設定をすることによって、ブラウザ側にコンテンツの最終更新時刻を送信することができます。

ブラウザ側は、このコンテンツの最終更新時刻を覚えておき次回リクエストした際にリクエストヘッダの中に含めて送信します。サーバー側のコンテンツに変更がなければサーバーはステータスコード304という「コンテンツ未更新」ステータスコードを送ります。
ブラウザが304を受け取ると自分のブラウザにキャッシュされたコンテンツを表示させるため、オリジンサーバーもしくはCDNキャッシュサーバー側でコンテンツを配信することはなく、配信負荷が軽減されるというものです。

Last-Modifiedヘッダサンプル

Last-Modified

では、サーバー側はどのようにしてコンテンツが最新かどうかを判別しているのでしょうか。実は、ブラウザから送信されるIf-Modified-Sinceというヘッダに依存しています。ざっくりと流れをかくと以下の通りです。


初回のアクセス

1:ブラウザ
「WEBサイト見たいからデータちょうだい」(HTTPリクエストヘッダ)

2:WEBサーバー
「別にいいけど、、、」(HTTPレスポンスヘッダ ステータスコード200
※このときWEBサーバーはレスポンスヘッダにコンテンツの最終更新時刻 (Last-Modified)を添付して送信する。

3:ブラウザ
「Thanks! WEB見られました。」
※サーバーから送られてきたレスポンスヘッダを受信し、WEBサイトが閲覧できる。

2回目以降のアクセス

1:ブラウザ
前回リクエストした際にサーバーから最終更新時刻 (Last-Modified)を受信してる場合は、Last-Modifiedの内容をIf-Modified-Sinceヘッダに含めてWEBサーバーに送信します。
(If-Modified-Since: Sat,22 Feb 2015 12:01:19 GMT)

「この日付でキャッシュしてるファイルがあるんだけど最新?」

2:WEBサーバー
WEBサーバーがIf-Modified-Sinceを受信したら、サーバー内にあるコンテンツの最終更新時刻と比較する。変更がなければ、ブラウザに304 (Not Modified)ステータスコードを送信する。

「ああ、それは最新だからそれつかって。」

3:ブラウザ
ブラウザはサーバーから304 (Not Modified)ステータスコードを受信したら自分自身のキャッシュを読み込んでブラウザに表示させる。

「わかった、じゃあ自分のブラウザキャッシュつかうね。」


という風に、ブラウザから送られてきたIf-Modified-Sinceについてる時刻と自分自身が持っているコンテンツの最終更新時刻を比較して200を返すか304を返すかサーバー側で判断しています。このように、オリジンサーバー側にLast-Modifiedヘッダを一つつけるだけでサーバー側の配信負荷も減らせるわけです。

304レスポンス=負荷ゼロではない

Last-Modifiedヘッダを付与するようにしておけばある程度配信負荷は下げられます。しかし304レスポンスはサーバーに全く負荷がかからないというわけではないことに注意してください。

WEBサーバーはIf-Modified-Sinceを受信すると送られてきた日付と自分自身が持っているコンテンツの最終時刻を比較して304を返答するか判断してます。
この比較しているという部分が見えない負荷になります。

304の見えない負荷をCDNでカバーする

サーバーが304を返答していると実ファイルは転送されないため見かけ上のトラフィックはあまり発生しません。にもかかわらず、比較する処理でDISKをガリガリやるiowaitがオリジンサーバーで発生し、細かいファイルを大量に配信しているケースでは思わぬトラブルになるという場合があります。
このような状態はCDNを利用しているとオリジンの問題なのかCDNベンダー側の問題なのか、なかなか判別が付きにくいですね。

そのため、弊社のエッジキャッシュCDNではIf-Modified-Sinceがついたヘッダが来た場合、以前オリジンから取得したレスポンスヘッダ時間と内容を比較後コンテンツに変更がなければ304をキャッシュサーバーから返答し、極力オリジンに問い合わせないようにしています。(厳密には全てのケースでこのような動作を行うわけではなく極力行うようにしています。)

CDNベンダーによって304はそのままオリジンにProxyするケースがほとんどですので、チェックしておくべきポイントです。

こちらの問題は、そもそもオリジンサーバー側のスペック的な問題でもありますが、304負荷軽減はCDNサービス側では全く無関係な内容というわけではなく、CDN側でもカバーできる要素です。

Etag

Entityタグ(ETags)はWebサーバーやブラウザが、ブラウザのキャッシュされたコンポーネントとWebサーバ上のオリジナルが一致しているかどうかを決定するためのメカニズムです。Etagも先ほどのLast-Modifiedヘッダと同じくオリジンサーバー側のレスポンスヘッダに含める値となりApacheやNginxなどのWEBサーバー側で生成します。

ブラウザーは初回リクエスト時にWEBサーバーのレスポンスヘッダにEtagが付与されていた場合は、次にリクエストする際にIf-None-Matchヘッダーと一緒に以前受け取ったETagをWebサーバーに送ります。
WEBサーバー側でETagが一致していればステータス304を返しコンテンツを配信しません。大まかな流れは以下のとおりです。

初回のアクセス

1:ブラウザ
「WEBサイト見たいからデータちょうだい」(HTTPリクエストヘッダ)

2:WEBサーバー
「別にいいけど、、、」(HTTPレスポンスヘッダ ステータスコード200
※このときWEBサーバーはレスポンスヘッダにコンテンツごとにユニークに生成したETAGを添付して送信する。

3:ブラウザ
「Thanks! WEB見られました。」
※サーバーから送られてきたレスポンスヘッダを受信し、WEBサイトが閲覧できる。

2回目以降のアクセス

1:ブラウザ

前回リクエストした際にサーバーからETAGを受信してる場合は、ETAGの内容をIf-None-Matchヘッダに含めてWEBサーバーに送信します。
(ETag: “3b-60273fc0”)

「前回のリクエスト時ETAGもらってたので送信しておきます。」

2:WEBサーバー

WEBサーバーがIf-None-Matchを受信したら、サーバー内にあるコンテンツと比較する。同じETAGの場合はブラウザに304 (Not Modified)ステータスコードを送信する。

「自分のキャッシュをつかっちゃって。」

3:ブラウザ

ブラウザはサーバーから304 (Not Modified)ステータスコードを受信したら自分自身のキャッシュを読み込んでブラウザに表示させる。

「わかった、じゃあ自分のブラウザキャッシュつかうね。」

こちらのやり取りを見ていただくとわかるとおり、ETAGはLast-Modifiedと動作が似ていますね。サーバー側は同じETAGがあれば304を返答するということで負荷軽減を行います。

ETAGの優れている点

結局ETAGも「Last-Modified」と「If-Modified-Since」の組み合わせと同じであれば、わざわざ使う必要はないのではないか?と思われるかもしれませんが、Last-Modifiedでは出来ない制御がETAGではできるんです。

ETAGの利用が適しているケース

少し限定的なケースですが、http://blog.redbox.ne.jpという URL があり、このページは日本語環境と英語環境でそれぞれ異なるページが返ってくるようなサイト設定となっている場合、ファイルの最終更新日は同じでもブラウザ言語によってページの内容が違うことになります。そのような場合、それぞれに違う ETag を付けておけばブラウザ側でどちらのキャッシュを持っているかを明示できます。

ETAGに潜む罠

ETAGはコンテンツごとにユニークなタグをつけておき、なるべくブラウザキャッシュを利用させます。ETAGはLast-Modifiedより優先順位が高いため、Last-ModifiedとETAGを同時に出力していた場合は、ETAGが優先されます。

If-Modified-SinceよりもIf-None-Matchが優先されます。
If-Modified-SinceはIf-None-Matchが存在しない場合にのみ利用されます。
If-Modified-Sinceの値が同一でも、If-None-Matchの値が変更されていた場合にはコンテンツを配信します。

そのため、ETAGをつけておけばキャッシュコントロールは全てOKといえるかというと、それはあくまで配信するサーバーが1つの場合の話です。ETAGはロードバランサー配下にオリジンサーバーを複数台構成で運用しているケースではETAGには思わぬ落とし穴になることがあります。
これは、ETAGというランダムなタグを生成するメカニズムに起因します。

ETAGの生成順序

そもそもETAGというランダムな文字列はどのように生成されるのでしょうか。実はWEBサーバー側のミドルウェアにもよりますが代表的なApacheの場合以下のような決まりがあるんです。

「iNode」 「ファイルサイズ」 「更新日時」で生成

同じディレクトリ構成の複数のサーバーの場合、ファイルサイズや更新日時は一致しても、iNodeは必ず違う値になります。つまり1台目のサーバーと2台目のサーバーとではETAGが異なってしまうということです。そうなるとせっかくキャッシュを利用しようとしてETAGをつけているのに逆効果になってしまいますね。

これらの問題を回避するために、ETag自体を出力しない方法と、ETagの生成にinode番号を利用させない方法があります。

ETag自体の出力をしない

ETagの生成にinode番号を利用しない

しかし、この場合どちらの設定でも「最終更新日」が重要な要素になってきます。

ETagを出力しない場合→Last-Modified(最終更新日)での判定
inode番号を利用しない場合→ファイルサイズと最終更新日がベースとなったEtagで判定

ETAGの扱い方

ETAGをとりあえず付けておくというのは先ほどの説明の通りNGです。ETAGはサーバーの構成状況によって以下のとおり付与するかしないのかを決めるべきです。

ETAGを付与してもいいケース

1台構成の場合
ソースIPロードバランサ配下の複数台構成の場合

ETAGを付与してはいけないケース

WEBサーバーが複数台構成でどちらのWEBサーバーにもアクセスがくる場合

しかしながら、弊社CDNを利用時はオリジンサーバー側がどのような構成であってもEtagは削除することをオススメしております。オリジンサーバーの設定がいろいろな政治的理由で変更できない・・という場合でも弊社キャッシュサーバー側でETAGを削除して配信する設定が可能です。

まとめ

今回はキャッシュコントロールができる、Last-ModifiedヘッダとETAGのお話をしました。これらのタグを付与することによってクライアント側は極力ブラウザキャッシュを利用してもらうようにし、結果サーバー側の配信負荷を下げられるということがわかったと思います。

また、Last-ModifiedとETAGの付与は、利用状況に応じて分ける必要があります。トラフィックを抑えることが最優先であればETAGは出力しないほうがよく、ファイル管理を厳密に行いたいのであれば出力するほうが良いでしょう。

このようなキャッシュコントロールはいろいろな要素が絡み合い更にCDNがフロントに入ってくると、なにが正解なのかなかなか判断しづらい部分があるとおもいますが、弊社ではこのようなキャッシュコントロールのサポートも積極的に行っています。