仕組みから学ぶ Web
応用読了目安 15#キャッシュ#Cache-Control#ETag#無効化

キャッシュ戦略 — いつ捨てるかを設計する

キャッシュを置くのは簡単で、正しく捨てるのが難しい。max-age・ETag・stale-while-revalidate の使い分けを判断軸から組み立てる。

この記事の進み方

What — キャッシュがどう判断されるか

ブラウザは、リクエストを出す前にキャッシュを見ます。ここでの分岐が体感速度を決めます。

Figureブラウザがリクエストを出す前に通る判断
  1. 1① キャッシュを探すURL をキーに、手元の保存を探す軽い
  2. 2② 鮮度を判定するmax-age の秒数を過ぎていないか軽い
  3. 3③ 期限切れなら再検証ETag を付けてサーバーに問い合わせる
  4. 4④ フル取得本体をまるごとダウンロードする重い

max-age が効いている間は ② で完結し、サーバーには一切問い合わせが行かない。だから「サーバーを直したのに反映されない」が起きる。冒頭の事故はここ。

1/4
① キャッシュを探すブラウザのディスクキャッシュやメモリキャッシュを見る。無ければ ④ へ直行する。

ネットワークに出ない(①で完結する)のが最速。次が 304(③)。フル取得(④)は最も遅い。max-age と ETag は、この分岐のどこで止めるかを決めている。

ヘッダの意味を1つずつ確定させる

max-age=N
N 秒間は再検証せずに使ってよい。この間サーバーには一切問い合わせが行かない。最も強く、最も危険な指定。
no-cache
保存はするが、使う前に必ず再検証する。名前に反して「キャッシュするな」ではない。
no-store
一切保存するな。本当にキャッシュを禁止したいのはこちら。
private
ブラウザは保存してよいが、CDN などの共有キャッシュは保存するな。個人向けレスポンスに付ける。
immutable
有効期間中は絶対に変わらない。リロードしても再検証させない。ハッシュ付きファイル名とセットで使う。
ETag
コンテンツの指紋。変わっていなければ 304 を返せる。本体の転送だけを省く仕組み。
stale-while-revalidate=N
期限切れ後 N 秒間は、古いものを即座に返しつつ裏で更新する。待たせずに新しくできる。

no-cacheno-store の取り違えは実務で頻出します。キャッシュしてほしくないときに no-cache を書くと、保存はされます。

2つの更新戦略を比べる

Compareキャッシュをどう更新するか

中身が変わったら 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 を付けておくと何が変わりますか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ「捨て方」が難しいのか

キャッシュの難しさは、元データの変更をキャッシュ側が知る手段がないことに尽きます。

元データを持っている側(サーバー)は、いつ変わったかを知っています。しかしキャッシュを持っている側(ブラウザ・CDN)は、自分から見に行かない限り分かりません。そして見に行くなら、キャッシュの意味が薄れます。

この矛盾に対する答えは、原理的に3つしかありません。

  1. 時間で諦める — 「N 秒間は変わらないことにする」と決め打つ。これが max-age。速いが、その間の変更は届かない。
  2. 毎回確認する — 使う前に必ず聞く。これが no-cache + ETag。確実だが往復が発生する。
  3. そもそも同じ名前を使わない — 変わったら別物にする。これがバージョニング。確実で速いが、URL を自分で決められる対象にしか使えない。

なぜ「1年キャッシュ」が推奨されるのか

一見すると危険な max-age=31536000 が定石とされるのは、ハッシュ付きファイル名とセットのときだけです。この組み合わせでは、そもそも中身が変わった時点で URL が変わるので、「古いものが返る」という事象が定義上発生しません。

冒頭の事故は、この組み合わせの片方だけを採用したために起きました。1年キャッシュはファイル名を変える仕組みへの依存であって、単体の設定ではない。 ここを分離して覚えていると、いつか同じ事故を踏みます。

確認 — ここまで読めたか

stale-while-revalidate は、利用者に何を見せている間に何をしていますか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

このリソースそれぞれに、どのキャッシュ設定を与えますか?

与えられた条件
  • ニュースサイト。記事は公開後も誤字修正などで編集されることがある
  • ① /assets/main.8f3a1b.css — ビルド時にハッシュが付く
  • ② / — トップページ HTML。5分おきに記事一覧が入れ替わる
  • ③ /articles/12345 — 記事 HTML。編集は稀だが、修正時はできるだけ早く反映したい
  • ④ /api/breaking-news — 速報の有無を返す API。1分間隔でポーリングされる
  • ⑤ /account/settings — ログインユーザーの設定画面
  • 編集部から「誤字を直したら5分以内に反映してほしい」と言われている
この軸で考える
  • · その URL の中身を、自分の意思で変えたくなることがあるか
  • · 変更が何秒遅れて届いてよいか
  • · 共有キャッシュ(CDN)に置いてよいものか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「キャッシュを長くすれば速くなる」と言う同僚に、それだけでは決められない理由を説明してください

与えられた条件
  • 相手は max-age を伸ばす提案をしている
  • 対象のファイルは名前が固定(/js/checkout.js)
  • 相手はパージできるから大丈夫だと考えている

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。