記事を書くに至った経緯
carrierwaveによる画像アップロードの際に困ったことが起こりました。
キャッシュが効くことでS3上のファイル名とキャッシュ上のファイル名の相違が発生し、画像が表示されない問題が浮上しました。
carrierwave.rb
には以下のように記述していました。
config.fog_attributes = {'Cache-Control' => 'public, max-age=86400', expires: 1.day.from_now.httpdate}
この時にCache-Controlとexpiresというキーが存在していましたが、両者の役割とその違いを理解しておらず、上記問題を解決する前に止まってしまいました。
ここでは、それらの違いと、問題の解決方法を記していきます。
キャシュとは?
キャッシュの説明としてIT用語辞典を引用します。
キャッシュとは、使用頻度の高いデータを高速な記憶装置に蓄えておくことにより、いちいち低速な装置から読み出す無駄を省いて高速化すること。また、その際に使われる高速な記憶装置や、複製されたデータそのもののこと。
Webシステム作りにおいては、ズバリ「Webサイトに初回訪問以降に再度訪問した際に、コンテンツを素早く提供するブラウザの仕組み」です。
Cache-Controlとexpiresとは?
HTTPプロトコルでは、HTTPヘッダにさまざまな情報を格納することができる。そのうちいくつかの情報は、キャッシュ制御のためのヘッダです。
リクエスト(クライアント→サーバ)用のものと、レスポンス(サーバ→クライアント)用、リクエスト/レスポンス共通のものが存在します。
▼リクエスト用
If-Modified-Since
日時を指定します。指定した日時より新しいコンテンツの場合のみデータを返却するようにサーバに指示します。主にローカルキャッシュの最新確認に使用されます。
If-None-Match
指定したエンティティタグに一致しない場合のみコンテンツを返却するようにサーバに指示します。最新情報の取得や競合の排除のために指定されます。
▼レスポンス用
Expires
コンテンツの有効期限を示します
Last-Modified
コンテンツの最終更新時刻を示します。If-Modified-Sinceと対で使用されます。
ETag
コンテンツの全体や一部を特定する固有値を示します。If-None-Matchと対で使用されます。
▼リクエスト/レスポンス共通
Cache-Control
キャッシュのコントロールに関する指示や情報を示します。
Pragma
関連するクライアント/Proxy/サーバそれぞれに認識させるための特殊な追加情報を記述します。例えば、「no-cache」を指定することで無条件に最新リソースを転送させることができます。
Googleのdevelopersページでは、以下のように言及されており、キャッシュの有用性を再認識できます。
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control
パフォーマンスの最適化の観点で、最良のリクエストとは、サーバーへの通信を必要としないリクエストです。レスポンスのローカルコピーがあれば、ネットワークによる待ち時間を完全に排除でき、データ転送のためにデータを読み込む必要もありません。
Cache-Controlに関して
▼「no-cache」と「no-store」の違い
no-cacheは、同じURLに対する後続のリクエストへのレスポンスとして、以前返されたレスポンスを使用するには、まずサーバーに問い合わせてレスポンスに変更があったかどうかを確認する必要があることを示します。
そのため、適切な検証トークン(ETag)がある場合、no-cacheを指定してもキャッシュされたレスポンスを検証するためのラウンドトリップは発生しますが、レスポンスに変更がなければダウンロードを省略できます。
一方、no-storeはより単純で、返されたレスポンスのバージョンにかかわらず、ブラウザのキャッシュやすべての中間キャッシュはそのレスポンスを一切格納できません。たとえば、個人の機密データや銀行データが含まれているレスポンスなどです。ユーザーがこのアセットをリクエストするたびに、リクエストがサーバーに送信され、完全なレスポンスが毎回ダウンロードされます。
▼「public」と「private」の違い
レスポンスがpublicとマークされている場合は、レスポンスに HTTP 認証が関連付けられているとしても、さらにレスポンスのステータス コードが通常キャッシュ可能になっていない場合でも、レスポンスをキャッシュに保存できます。通常は、明示的なキャッシュ情報(max-ageなど)によってレスポンスがキャッシュ可能であることが指定されているためpublicは必要ありません。
一方、privateレスポンスは、ブラウザのキャッシュには格納できます。しかし、対象ユーザーは1人のため、中間キャッシュに格納することは認められません。たとえば、個人的なユーザー情報を含む HTMLページはそのユーザーのブラウザでのみキャッシュに格納でき、CDNでは格納できません。
▼max-age
このディレクティブでは、取得したレスポンスを再使用できる最大時間を、リクエストの時刻を起点とする秒数で指定します。たとえば、"max-age=60" は、レスポンスを60秒間キャッシュに格納して再使用できることを示します。
最適な Cache-Control ポリシーの定義
こちらより引用
場面に合わせた用途
▼キャッシュさせたくない場合
ブラウザにキャッシュさせたくない場合は、Expiresヘッダは付与しないほうが良いですね(ETAGやLast-Modifiedも含む)。
手軽に実現出来る一番簡単な方法はキャッシュコントロールヘッダにCache-Control "no-cache"
を付与することです。
no-cache
を付与することによって、毎回最新のコンテンツを取りに行くことになります。こちらはアクセス毎に内容が変更されたりサーバーにアクセスしてもらわないと困るようなコンコンテンツの場合、特にスクリプト言語等で生成する動的コンテンツなどに適しています。
参考記事:https://blog.redbox.ne.jp/http-header-expires.html
上記ブログでは、追加で注意点も記載されていました。
最後にExpiresヘッダの注意点をいくつかまとめます。
Expiresの日付は1週間以上に設定すること。
RFCのガイドラインに違反するので、1年以上先には設定しないこと。
EtagやLast-Modifiedより強いキャッシュなので更新の多いページや動的更新されるページでは利用しない。
サーバーに問い合わせないためアクセスログも収集出来なくなる。
特に最後のアクセスログが収集出来ないというのは状況によって致命的になります。しっかり検討してExpiresヘッダを付与しましょう。
✔Expiresの日付は1日で設定していたので見直ししてみようと思います。
▼cloudfrontのキャッシュを手動で削除する場合
参考記事