キャッシュ戦略 — いつ捨てるかを設計する
キャッシュを置くのは簡単で、正しく捨てるのが難しい。max-age・ETag・stale-while-revalidate の使い分けを判断軸から組み立てる。
先に読んでおくとよい記事
この記事の進み方
What — キャッシュがどう判断されるか
ブラウザは、リクエストを出す前にキャッシュを見ます。ここでの分岐が体感速度を決めます。
- 1① キャッシュを探すURL をキーに、手元の保存を探す軽い
- 2② 鮮度を判定するmax-age の秒数を過ぎていないか軽い
- 3③ 期限切れなら再検証ETag を付けてサーバーに問い合わせる中
- 4④ フル取得本体をまるごとダウンロードする重い
max-age が効いている間は ② で完結し、サーバーには一切問い合わせが行かない。だから「サーバーを直したのに反映されない」が起きる。冒頭の事故はここ。
ネットワークに出ない(①で完結する)のが最速。次が 304(③)。フル取得(④)は最も遅い。max-age と ETag は、この分岐のどこで止めるかを決めている。
ヘッダの意味を1つずつ確定させる
- max-age=N
- N 秒間は再検証せずに使ってよい。この間サーバーには一切問い合わせが行かない。最も強く、最も危険な指定。
- no-cache
- 保存はするが、使う前に必ず再検証する。名前に反して「キャッシュするな」ではない。
- no-store
- 一切保存するな。本当にキャッシュを禁止したいのはこちら。
- private
- ブラウザは保存してよいが、CDN などの共有キャッシュは保存するな。個人向けレスポンスに付ける。
- immutable
- 有効期間中は絶対に変わらない。リロードしても再検証させない。ハッシュ付きファイル名とセットで使う。
- ETag
- コンテンツの指紋。変わっていなければ 304 を返せる。本体の転送だけを省く仕組み。
- stale-while-revalidate=N
- 期限切れ後 N 秒間は、古いものを即座に返しつつ裏で更新する。待たせずに新しくできる。
no-cache と no-store の取り違えは実務で頻出します。キャッシュしてほしくないときに no-cache を書くと、保存はされます。
2つの更新戦略を比べる
中身が変わったら URL を変える。古い URL は誰も参照しなくなるだけ。
<!-- ビルドのたびにハッシュが変わる -->
<script src="/assets/app.a3f9c2.js"></script>
Cache-Control: public, max-age=31536000, immutable- 更新の確実さ
- 確実(別 URL になる)
- 再検証
- 不要
- 適用できる対象
- URL を自由に決められるもの
- 無効化の操作
- 不要
- –この URL を参照する HTML 側は、短い TTL にしておく必要がある。HTML が古いままだと新しいファイル名に切り替わらない。
- –immutable を付けると、リロードしても再検証されない。ハッシュ付きでない限り付けてはいけない。
どちらが優れているかではなく、URL を自分で変えられるかどうかで決まる。JS や CSS はビルドで名前を変えられるが、HTML やAPI は URL が仕様なので変えられない。
max-age が切れたあと、ETag を付けておくと何が変わりますか?
Why — なぜ「捨て方」が難しいのか
キャッシュの難しさは、元データの変更をキャッシュ側が知る手段がないことに尽きます。
元データを持っている側(サーバー)は、いつ変わったかを知っています。しかしキャッシュを持っている側(ブラウザ・CDN)は、自分から見に行かない限り分かりません。そして見に行くなら、キャッシュの意味が薄れます。
この矛盾に対する答えは、原理的に3つしかありません。
- 時間で諦める — 「N 秒間は変わらないことにする」と決め打つ。これが
max-age。速いが、その間の変更は届かない。 - 毎回確認する — 使う前に必ず聞く。これが
no-cache+ETag。確実だが往復が発生する。 - そもそも同じ名前を使わない — 変わったら別物にする。これがバージョニング。確実で速いが、URL を自分で決められる対象にしか使えない。
なぜ「1年キャッシュ」が推奨されるのか
一見すると危険な max-age=31536000 が定石とされるのは、ハッシュ付きファイル名とセットのときだけです。この組み合わせでは、そもそも中身が変わった時点で URL が変わるので、「古いものが返る」という事象が定義上発生しません。
冒頭の事故は、この組み合わせの片方だけを採用したために起きました。1年キャッシュはファイル名を変える仕組みへの依存であって、単体の設定ではない。 ここを分離して覚えていると、いつか同じ事故を踏みます。
stale-while-revalidate は、利用者に何を見せている間に何をしていますか?
演習 — まず自分で判断する
解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。
このリソースそれぞれに、どのキャッシュ設定を与えますか?
- ニュースサイト。記事は公開後も誤字修正などで編集されることがある
- ① /assets/main.8f3a1b.css — ビルド時にハッシュが付く
- ② / — トップページ HTML。5分おきに記事一覧が入れ替わる
- ③ /articles/12345 — 記事 HTML。編集は稀だが、修正時はできるだけ早く反映したい
- ④ /api/breaking-news — 速報の有無を返す API。1分間隔でポーリングされる
- ⑤ /account/settings — ログインユーザーの設定画面
- 編集部から「誤字を直したら5分以内に反映してほしい」と言われている
- · その URL の中身を、自分の意思で変えたくなることがあるか
- · 変更が何秒遅れて届いてよいか
- · 共有キャッシュ(CDN)に置いてよいものか
「キャッシュを長くすれば速くなる」と言う同僚に、それだけでは決められない理由を説明してください
- 相手は max-age を伸ばす提案をしている
- 対象のファイルは名前が固定(/js/checkout.js)
- 相手はパージできるから大丈夫だと考えている
読み終わりましたか?
読了にすると、これを前提とする記事がロードマップで開放されます。