地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【HTTP】キャッシュあたりを整理してみる

概要

HTTPのキャッシュについて色々調べてみたのでメモ的な意味も兼ねて書いた。

動機としては以下の本を読む上で先に事前知識を入れておきたかったなというのがあります。キャッシュだけで400ページ超え。とても楽しみです。

gihyo.jp

目次

それぞれのヘッダーのもつ意味

Expiresヘッダー

If modified Sinceも送られないため、304レスポンス負荷が発生することもない。

HTTPレスポンスにCatch-Controlのmax-ageディレクティブが含まれる場合は、Expiresは無視されます

blog.redbox.ne.jp

Apacheでやるならこんな感じで設定を書くことで適用することができます。キャッシュ先はユーザのブラウザとなるのでキャッシュヒット時にはユーザとしてはサービスが取りうる最高のレイテンシでサービスを使うことができます。

<ifModule mod_expires.c>
 ExpiresActive On
 ExpiresByType image/png "access plus 1 months"
 ExpiresByType image/jpeg "access plus 1 months"
 ExpiresByType image/gif "access plus 1 months"
 ExpiresByType text/css "access plus 1 months"
</ifModule>

注意点としては

  • RFCガイドラインに違反するので、1年以上先には設定しない。
  • 強制的なキャッシュなので更新の多いページにはなるべく使わない。

Cache-Control

public

レスポンスが通常はキャッシュ可能でなくても、レスポンスをどのキャッシュにも格納することができます。

private

Webサーバから返されるコンテンツがただ一人のユーザのためのものであることを示す。このコンテンツは、複数のユーザが共有されるキャッシュに記録されるべきではないことを表している。

この設定はCDNの設定で以下の流出の事件で有名なやつですね。

engineering.mercari.com

no-cache

「キャッシュを使うな」のように見えるこのヘッダが実際に意味するところは少々ニュアンスが異なる。このヘッダの意味は、いちどキャッシュに記録されたコンテンツは、現在でも有効か否かを本来のWebサーバに問い合わせて確認がとれない限り再利用してはならない、という意味である。

no-store

このヘッダは、Webサーバから返されてくるコンテンツをキャッシュに記録するな、という指示である。

developer.mozilla.org

「no-cache」と「no-store」の違い

no-cacheは、同じURLに対する後続のリクエストへのレスポンスとして、以前返されたレスポンスを使用するには、まずサーバーに問い合わせてレスポンスに変更があったかどうかを確認する必要があることを示します。

no-storeはより単純で、返されたレスポンスのバージョンにかかわらず、ブラウザのキャッシュやすべての中間キャッシュはそのレスポンスを一切格納できません。たとえば、個人の機密データや銀行データが含まれているレスポンスなどです。ユーザーがこのアセットをリクエストするたびに、リクエストがサーバーに送信され、完全なレスポンスが毎回ダウンロードされます。

Last-ModifiedとIf-Modified-Since

Last-Modifiedヘッダーでは、最後に更新された日時をもとにリソースが同じかどうかを判断します。

また、リクエストにIf-None-Matchヘッダーが含まれる場合には、If-Modified-Sinceヘッダーが無視されます。つまり、Last-ModifiedヘッダーとETagヘッダーを併用した場合は、ETagヘッダーが優先されます。

qiita.com

satoyan419.com

EtagヘッダーとIf-None-Match

Etagヘッダーでは、「エンティティタグ」と呼ばれるリソースを特定するために割り当てられる固有の値(文字列)を用いて、リソースの内容が同じかどうかを判断します。

  • Last-Modified オブジェクトが最後に変更された日時を示します。
  • エンティティタグ (ETag) コンテンツの一意の識別子を提供します。

Cache-ControlもExpireもない場合はどうなるの

NginxもApacheも明示しなければ上記のヘッダーは自動ではつきません。自動でつかないなら全てのリクエストはオリジンへ飛ぶのかといういうとそんなこともなくブラウザの実装次第にはなりますが概ね以下のようなキャッシュがブラウザで行われます。

ヒューリスティックなアプローチを使用してキャッシュを利用する。レスポンスの生成時間とLast-modifiedの時間とコンテンツの時間を使って経験則から導き出した時間でキャッシュをする。ブラウザというかhttpクライアントごとに細かくは違うんだろう。

expirationTime = responseTime + freshnessLifetime - currentAge

正確な話はRFCに以下の記述があります。Ageヘッダーがない場合とかその辺はどうやってるのかは定義されていないのでこれもブラウザ依存でしょうか。

tools.ietf.org

ブラウザにキャッシュはさせたいけど都度確認はしてきて欲しいとき

cache-control: public, max-age=0
ETag: "aaaaaa"

max-ageに0を指定する。クライアントは次回リクエスト時にEtagを使ってサーバに更新がないかどうかを問い合わせることでキャッシュが古くならないようにする仕組み。

from service workerって何

f:id:ryuichi1208:20210307123940p:plain

Etagとか使ったブラウザキャッシュはfrom disk cacheとかになるのかなとservice workerもキャッシュを持つらしいことを知った。上はtwitterにアクセスした際の静的ファイルのキャッシュ。

Service Worker とは、Webページとは別にバックグラウンド(別スレッド)で動作するJavascript環境のことでブラウザキャッシュとは違うところに保存されるらしい。

そしてどうやらClear Cacheとかのブラウザキャッシュをクリアするツールだけではこの領域はクリアされないらしい。

chrome.google.com

まとめ

HTTPレイヤでのキャッシュ、主にクライアントキャッシュについて調べてみた。サーバサイドのキャッシュと違ってクライアントの実装依存な部分があったりキャッシュの鮮度を意識したりとTTLだけの制御だけでは足りない世界を垣間見ることができました。

この辺の知識はRedis 6.0だかでリリースされた新機能のクライアントサイドキャッシュの理解にも役立ちそうでよかったです。