1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

apacheのmod_cacheでお手軽キャッシュ機構

ユースケース

下記のような条件下にあるが、どうしてもキャッシュを使ってみたい場合に、mod_cacheは有効なのではというお話。

  • Amazon CloudFrontAkamaiなどのCDNが諸事情によって使えない場合
    • お金をこれ以上かけられない
    • ログを特定のフォーマットで出力する必要がある
  • 既にアプリケーションの前段にApache(サーバ or コンテナ)がいる

キャッシュをさせる上で知っておく必要があること

developer.mozilla.org

上記の記事からいくつか掻い摘まんで読んでみます。
今回はブラウザのキャッシュではなく、プロキシのキャッシュ(共有キャッシュ)が主題になります。

キャッシュを制御する上で、必要になるのはCache-controlヘッダです。リクエストおよびレスポンスでキャッシュ機能に関するディレクティブを指定するために使用します。

このヘッダは、レスポンスに付与された時にはプロキシでどのくらいキャッシュを保持するか(もしくはしないか)ということを設定できます。逆にリクエストに付与した場合には、キャッシュの鮮度を指定して、キャッシュから取得するのかオリジンから取得するのかが分かれます。

このヘッダが付与されていないリクエストはクエリパラメータが付いているリクエストをキャッシュの対象にしないことがRFCに明記されており、Apacheのmod_cacheの実装も同じです。

  • Cache-Control: no-store
    • キャッシュストレージを一切利用しない = キャッシュに保存してはいけない
  • Cache-Control: no-cache
    • いちどキャッシュに記録されたコンテンツは、現在でも有効か否かを本来のWebサーバに問い合わせて確認がとれない限り再利用してはならない
  • Cache-Control: privateCache-Control: public
    • publicの場合は共有キャッシュでキャッシュさせて良い
    • privateの場合にはブラウザのプライベートキャッシュでしかキャッシュしない
  • Cache-Control: max-age=31536000
    • リソースの鮮度を保証するもの
    • 単位は秒数
    • responseTime + freshnessLifetime - currentAge の時刻までキャッシュを保持することになる
    • もしこのヘッダーが与えられない場合には、Expiresヘッダーを見に行くことになる
  • Cache-control: must-revalidateが付与されていると、キャッシュはリソースを使用する前に陳腐化の状態を検証しなくてはならない。ブラウザの再読み込みをしても、同じく検証が走る
    • キャッシュされた文書の有効期限に達すると
  • HTTP/1.0との互換性を担保したい場合には、pragmaヘッダーを利用する(完全な大体にはならない)

キャッシュ機構を扱う上で重要となるレスポンスヘッダにVaryがあります。キャッシュを提供する際に考慮すべきヘッダを指定します。指定したヘッダのvalueがキャッシュしたオブジェクトとリクエストで同一だった場合にはオリジンに新しくリクエストの発行を行わず、キャッシュを返却します。

例えばUser-Agentを指定した場合には、モバイルに対して誤ってデスクトップ版の画面を表示することがなくなります。

バイスごとにキャッシュさせる

例えば、Amazon CloudFrontであれば、内部でデバイス判断ロジックを持っており、デバイスタイプに基づいてキャッシュを返却します

Apacheのmod_cacheはデフォルトだとシンプルなプロキシキャッシュを提供するので、判断ロジックなどはこちらで設定してあげる必要があります。 

CloudFrontと同じような動作をmod_cacheにさせるためには以下のことを設定してあげる必要があります。

  • レスポンスヘッダにCache-Controlを付与する
  • リクエストヘッダに独自定義のデバイスタイプのヘッダをセットする
  • レスポンスヘッダにVaryヘッダを付与する(指定するヘッダは独自に定義したデバイスタイプ)

2つ目のリクエストヘッダの付与は、cacheハンドラが動く前に行う必要性があります。

mod_cacheはデフォルトだと、一番キャッシュ効率が良くなるように、設定ファイルへの記述順に関わらず一番最初に処理される作りになっています。

CacheQuickHandler offと宣言することで、一番最初に処理されるのを避けることが出来ます。日本語の方のドキュメントには載っていないためハマりどころです。

最低限の記述としては下記のようになるかと思います

# 独自のデバイスタイプ用ヘッダを付与する
    RequestHeader set x-custom-device-type pc
    SetEnvIf User-Agent "<regexp>" device_tablet
    SetEnvIf User-Agent "<regexp>" device_mobile
    RequestHeader set x-custom-device-type mobile env=device_mobile
    RequestHeader set x-custom-device-type tablet env=device_tablet

  <IfModule mod_cache.c>
      <IfModule mod_cache_disk.c>
        CacheRoot /var/www/html/cache
        # リクエストヘッダのCache-Controlを無視
        CacheIgnoreCacheControl On
        CacheDetailHeader on
        CacheEnable disk /
        CacheDirLevels 5
        CacheDirLength 3
   # mod_cacheが一番最初に処理を行わないようにする
        CacheQuickHandler off
      </IfModule>
  </IfModule>

  <Location "/">
    Header append Vary x-custom-device-type
    Header append Cache-Control max-age=60

# 以下は省略