Cache-Control について考える (1) : no-store, no-cache, must-revalidate

投稿日:

Webサイトを高速化するにせよ、あるいはCDNを利用するにせよ、キャッシュの制御は避けて通れない。それは Cache-Control ヘッダを介して設定するが、やや技巧的な面がある。

Cache-Control: s-maxage=604800, max-age=604800, must-revalidate

このような Cache-Control ヘッダでよく見かける no-store, no-cache, must-revalidate を中心に、「オリジンサーバのレスポンスにどれを設定すればいいのか」を考える。

事前に知っておくべきこと

  1. キャッシュには複数の種類があるが、ここではCDNとブラウザのキャッシュを扱う。
    (※厳密には リバースプロキシやCDNなどの「共有キャッシュ」と ブラウザなどの「非共有キャッシュ」という区別になるが、ここでは「CDN」と「ブラウザ」に簡易化して考える)
  2. 上記のキャッシュの生成・維持の条件を指定するのが Cache-Control ヘッダである。他に Expires ヘッダなどもあるがここでは扱わない。
  3. Cache-Control ヘッダはクライアント側とオリジンサーバ側がそれぞれキャッシュに関する指示として追加できる。ここでは冒頭の記載通り後者のみを扱う。
  4. Cache-Control ヘッダの各ディレクティブは複雑怪奇なネーミングをしているので、名前から機能を想像すべきではない
  5. Cache-Control ヘッダの各ディレクティブがどう働くかは、ブラウザとCDNで同じ場合もあれば異なる場合もある
  6. Cache-Control ヘッダの各ディレクティブがCDNにおいてどう働くかは、利用しているCDNサービスによってかなり異なる

ここの記載は個人による調査結果(それも一般化されたもの)として参照して、注意深く確認しながら利用してほしい。

no-store と no-cache と must-revalidate の使い分け (ケース別)

よく見かけるこれらのディレクティブだが、キャッシュ制御で最も根幹的な「キャッシュの保存の可否」と「キャッシュの利用の可否」を指示するものになっている。それでいてネーミングがトンチキなので罠が多い。

キャッシュさせたくない

このケースが no-store だ。

これはブラウザに対してもCDNに対してもキャッシュを保存しないよう指示する。保存させないという点が他のディレクティブと明確に異なる。

管理画面やECサイトのカートなど、プライベートなページそのものに対して指定するなら、このディレクティブが正しい。

このディレクティブについては複数の補足がある

キャッシュして欲しいが、コンテンツの更新を確認してからキャッシュを使って欲しい

このケースが no-cache だ。 must-revalidate ではない。

no-cache は(その名前に反して)キャッシュを保存することを許す。しかしキャッシュされたコンテンツを利用する前に、必ずコンテンツの更新を確認するように指示する。

ブラウザはキャッシュされたコンテンツを利用する前に、サーバに対して「条件付きリクエスト」を行い、新鮮なコンテンツの取得を試みる。もしサーバが 304 Not Modified をレスポンスした場合は、キャッシュされたコンテンツを利用する。

コンテンツに更新がない場合は確認だけで済むので、no-store よりも高速に表示される。キャッシュを効かせつつ更新を届けたい場合に利用できる。

CDNが no-cache をどう扱うかはサービスによって異なる。調べた限り「無視する(単純にキャッシュする)」「ブラウザと同様(キャッシュは保存するが毎回更新を確認)」「ミスユース対策としてキャッシュしない」ものがあった。

ややこしいが、しかしそもそもCDNに最新のコンテンツを配信させいのであれば、コンテンツ更新と連動してCDNのキャッシュパージAPIを叩けばいい(のでCDNが no-cache に従う必要性を私は感じない)。

キャッシュして欲しいが、期限が切れたキャッシュは絶対に表示しないで欲しい

このケースが must-revalidate だ。

must-revalidate は期限切れのキャッシュを利用してはならず、その場合は必ず再取得するよう指示する。

なぜそうしたディレクティブがあるのかといえば、例えばネットワークに問題があってサーバからレスポンスが返らなかった場合に、期限切れのキャッシュコンテンツを利用してもよいとhttpが規定しているためだ。しかし雑に調べた限り、現在そのような実装のブラウザは見当たらなかった。CDNに対して利用した場合については後述する。

名前から誤解してはならないが、例え must-revalidate を指定しても、キャッシュが有効期間内であればブラウザはコンテンツの更新を確認しない。このディレクティブが効果を発揮するのは、キャッシュの有効期限が切れた後の話である。

CDNが must-revalidate をどう扱うかはサービスによって異なる。まず期限切れキャッシュの利用は前述のとおり、特定の条件でのみ許される。逆に言えば通常時はキャッシュ期限切れのコンテンツを即座に破棄するので、その働きを求めて指定する必要はない。しかしオリジンサーバの応答が無い場合や50xエラーを返している場合などで、期限切れキャッシュで配信を継続するタイプのCDNサービスがあるようだった。

must-revalidate を指定した場合の挙動もサービスによって異なるが、「前述の期限切れキャッシュの配信を抑制する」「no-cache のように毎回更新を確認する」「無視する」ものがあるようだ。

なお昔は no-cache ディレクティブが無かったため、代わりに max-age=0, must-revalidate を指定していたらしい。

publicとprivate

ここまでに挙げたディレクティブの外にも、任意のキャッシュに保存可能であることを強く明示する public や、CDNなどの共有キャッシュに保存すべきでないことを示す private ディレクティブがある。これらは現代のブラウザに対して使用する意味はあまりないはずだ(結局 no-store がなければキャッシュするのだから)。利用しているCDNで特に要求された場合のみ指定すればいい。代表的な例として Fastly がある。

ちなみに過去のブラウザに対する public ディレクティブの有効性については 補足 ページを参照。

次回: キャッシュ期間の制御と実際のユースケース

ここまではキャッシュの許可・禁止に関わる指示についてを述べた。

上記のとおり、CDNにおけるディレクティブの扱いはサービスによってまちまちになっている。CDNは純粋なリバースプロキシと異なり「既存の配信システムに挟み込んで良い感じに高速デリバリーさせたい」というニーズからスタートしているためか、「なるべく既存の設定を崩さずいい感じに動く」よう苦慮しながらディレクティブを扱っているようだ。

次回max-ages-maxage という「キャッシュ期間を制御するディレクティブ」と、今回の内容も含めた実際のユースケースについて考える。

さらに補足ページも作成した。


references

  • MdN Web docs (en) : Cache-Control
    https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cache-Control
  • MdN Web docs: Using Firefox 1.5 caching
    https://developer.mozilla.org/ja/docs/Mozilla/Firefox/Releases/1.5/Using_Firefox_1.5_caching
  • F5 Networks Whitepaper Webブラウザのキャッシュの動作
    https://www.f5.com/ja_jp/services/resources/white-papers/caching-behavior-of-web-browsers

updates

  • no-cache のCDNでの扱いを修正(2022/12/25)
  • public/privateの記載を修正 (2022/12/26) (2022/12/27)
  • リクエスト時とレスポンス時の補足を前提に追加 (2022/12/27)
  • must-revalidate の微修正 (2022/12/27)
  • no-store の例外例を追加 (2022/12/28)
  • 補足ページを追加 (2022/12/31)
  • 表現をリファクタリング (2023/02/01)