もう少しだけmod_cacheを深追いしてみる
前のエントリーで書いた方法だとブラウザによってはページをリロードするたびに待たされることになります。
mt-search.cgiをmod_cacheで超高速化する!! - Ogawa::Memoranda
なんでかな?と思ったので、動的コンテンツ(この場合はURLにクエリ文字列を含む動的コンテンツ)へのリクエストに対する、(mod_cacheで実現された)サーバーサイドキャッシュの振る舞いをもう少し深追いしてみました。
サーバーサイドキャッシュがない場合
サーバーサイドキャッシュがない場合には、すべての動的コンテンツへのリクエストごとにレンダリングを行い、そのデータがレスポンスとして返されます。
![]()
動的コンテンツで、Last-Modified, ETagヘッダを含むレスポンスを行い、If-Modified-Since, If-None-Matchヘッダを含むリクエストに対して304 Not Modifiedを返すことでレンダリング処理やトラフィックを削減することもできます。できますが、先のエントリーで見たようにCGIのプロセス起動のオーバーヘッドが数100ミリ秒もかかってしまう場合にはほとんど意味がありません。FastCGIやモジュール版PHPなどの場合には効果が期待できます。
サーバーサイドキャッシュがある場合
先のエントリーで示したように、Expiresヘッダをレスポンスとして返す動的コンテンツは、サーバーサイドキャッシュによってキャッシュされます。
下図に示すように、ブラウザがアクセスした際にそのURLがキャッシュされていれば、その内容をそのままレスポンスとして返します。また、キャッシュされていない場合やそのキャッシュが有効期限を過ぎている場合にはバックエンドの動的コンテンツにアクセスし、レンダリング結果を得、それをキャッシュした上でブラウザに返します。

一方、ブラウザのリロードボタンを押した場合など、ブラウザからのリクエストがキャッシュ制御情報を示すヘッダ(例えば、Cache-Control: no-cache、Cache-Control: max-age=0、Pragma: no-cacheなど)を含む場合には、下図のように、mod_cacheはキャッシュのチェックを行わず、バックエンドシステムにアクセスし、レンダリング結果を得、それをキャッシュした上でブラウザに返します。

ただし、mod_cacheにはCacheIgnoreCacheControlというディレクティブが用意されており、値をOnにした場合にはリクエストに含まれるCache-Controlヘッダを無視して、キャッシュにヒットすればキャッシュ内の情報を返します。これを設定すると期限切れになるまでキャッシュが更新されなくなるため効率は良いですが、有効期限を長く設定しているとキャッシュデータと真のデータがinconsistentな状態が長く続いてしまいます。この点に関しては下で再度触れます。
サーバーサイドキャッシュとConditional GETの組み合わせ
Expiresヘッダに加え、Last-Modified、ETagヘッダの一方または両方をレスポンスとして返す動的コンテンツもまた、上記と同様にサーバーサイドキャッシュによってキャッシュされます。
ただし、ブラウザはLast-Modified、ETagヘッダを含むURLへ再度アクセスする場合には、If-Modified-Since, If-None-Matchヘッダ付きのリクエスト(Conditional GET)を行う場合があります。この場合には、キャッシュ上にあるレスポンスヘッダと対応が取れていれば304 Not Modifiedを返し、対応が取れていなければIf-Modified-Since, If-None-Matchヘッダなしのリクエストと同等に扱います。つまり、キャッシュが無効でなければキャッシュデータを返し、無効ならバックエンドに問い合わせます。

ページロード時のブラウザの振る舞いとCacheIgnoreCacheControlの設定
Last-Modified, ETagヘッダの付いた(より正確にはブラウザはローカルにキャッシュできる)ページのロード時にどういうリクエストヘッダを送るかはブラウザによって異なります。Firefox 1.5/2.0、IE7で調べてみたらこんな感じ(下表中、Firefoxの強制リロードはCtrl+Reload、IE7の強制リロードはShift+Reloadを行ったものです)。
| ヘッダ | If-Modified-Since | If-None-Match | Cache-Control | Pragma |
|---|---|---|---|---|
| Firefox 通常ロード | ○ | ○ | ― | ― |
| Firefox リロード | ○ | ○ | max-age=0 | ― |
| Firefox 強制リロード | ― | ― | no-cache | no-cache |
| IE7 通常ロード | ○ | ○ | ― | ― |
| IE7 リロード | ○ | ○ | ― | ― |
| IE7 強制リロード | ― | ― | no-cache | no-cache |
つまり、CacheIgnoreCacheControlをOff(デフォルト)にしている場合には、通常のリロード操作を行ったときIE7では304 Not Modifiedが返りますが、Firefox 1.5/2.0ではバックエンドへの問い合わせが発生します。リロード操作というのはWebブラウザでは案外普通にやってしまう操作なのでこの違いは大きいです。一方でCacheIgnoreCacheControlをOnにしている場合には、IE7でもFirefoxでも304 Not Modifiedが返ります。
ここで2つの戦略があり得ます。つまり、
- CacheIgnoreCacheControlはOffにする
- Firefoxユーザが不幸になることは看過する
- ただし、Expiresはキャッシュの量が限度に達するまでの任意の期間を指定できる
という戦略と、
- CacheIgnoreCacheControlはOnにする
- Firefoxユーザも幸せ
- ただし、ブラウザからサーバーサイドキャッシュを無効化する手立てがないので、Expiresに指定する時間も応分に短めに設定する
という戦略です。このサイトのタグアーカイブでは試しに下の戦略を採用してみています。
mt-search.cgiでLast-Modified, ETagヘッダを返すようにするパッチ
先のエントリーでは、mt-search.cgiのレスポンスにExpiresヘッダを追加するパッチを示しましたが、ここではLast-Modified, ETagヘッダも追加するためのパッチを示しておきます。
MTAppSearchPatch - Ogawa::Code - Trac
このパッチを当てれば、先のエントリーのBootstrap.pmへの修正は必要ありません。
Comments and Trackbacks