Mirai Translate TECH BLOG

株式会社みらい翻訳のテックブログ

HTTPキャッシュの基礎の基礎

はじめに

こんにちは。プラットフォーム開発部のeinです。

このたび社内でWebアプリケーションを開発するにあたって、今更ながらHTTPキャッシュについて改めて学んだ内容をまとめます。

当記事では特に、Cache-Control ヘッダおよび ETag ヘッダについて解説します。

TL;DR

当記事は、MDNのドキュメントを私の理解のもと要約、解説を追加した内容になっています。

https://developer.mozilla.org/ja/docs/Web/HTTP/Caching

キャッシュの種類

大前提として、HTTPキャッシュはおおまかに3種類に分かれます。

  • プライベートキャッシュ
    • クライアントに保存されるキャッシュ
    • 要はブラウザキャッシュのこと
    • プライベートキャッシュのみを利用するときは Cache-Control: private を用いる
  • 共有キャッシュ1
    • リバプロやCDNが保存するキャッシュ
    • 製品やサービスにも寄るが、通常はプライベートキャッシュと同様に Cache-Control で保存期間をコントロールできる
  • ヒューリスティックキャッシュ
    • Cache-Control が指定されないときの自動的なキャッシュのこと
    • 制御しにくいため意図的な利用は推奨されない

Cache-Control によるキャッシュの制御

Cache-Control レスポンス2ヘッダは、主に キャッシュをするか否か および キャッシュの期間 を制御します。オリジンサーバ3がコンテンツをレスポンスする際に同ヘッダを付与することで、ブラウザやCDNはその内容を解釈してキャッシュの保存期間などを決めます。

ここでは Cache-Control レスポンスヘッダの主要な値とその意味を解説します。

Cache-Control: max-age

max-age はキャッシュが新鮮(fresh)である期間を秒単位で指定します。おおむねキャッシュの保存期間と理解して差し支えありません。

例えば Cache-Control: max-age=86400 とすると、ブラウザやCDNはオリジンからレスポンスを受け取った時刻から24時間の間、キャッシュを保存します。

正確には、HTTPキャッシュは古くなっても特定の条件下で再利用し続ける指示も可能であるため、新鮮である期間と保存期間(キャッシュが再利用される期間)は完全には一致しません。

Cache-Control: s-maxage

共有キャッシュに対するmax-ageです。一般にCDNmax-ages-maxage の両方を受け取ったとき、s-maxage を優先します。

Cache-Control: private

キャッシュをプライベートキャッシュでのみ許可し、共有キャッシュには保存しないよう要求します。

個人にパーソナライズされたコンテンツを共有キャッシュに保存させないために利用します。

Cache-Control: public

逆に、任意のキャッシュに保存してよい旨を明示します。プライベートキャッシュにも共有キャッシュにも保存されます。

共有キャッシュは通常 public を指定せずともキャッシュしますが、Authorization ヘッダが付与されいるときなど、コンテンツがパーソナライズされている恐れがあるときにはキャッシュしない実装である場合があります。public は、そういった制約を無視して共有キャッシュに保存してよいことを明示します。

Cache-Control: no-store

誰もキャッシュしないよう要求します。

Cache-Control: no-cache

ややこしいことに、キャッシュしないという意味ではありません。

ブラウザやCDNがキャッシュを利用する際に、常にキャッシュの 検証 をするよう求めます。検証については後述します。

Cache-Control: immutable

こちらは逆に、キャッシュが新鮮である間は検証しないように求めます。

ブラウザはリロードの際にmax-ageに関わらず検証を実行しますが、immutable はリロード時の検証すら不要である旨を意味し、キャッシュが新鮮である間はキャッシュを再利用し続けるよう要求します。

ただし immutable は未対応のブラウザも多いです。

ETag によるキャッシュの検証

検証は、キャッシュの更新を効率的に行うための仕組みです。

HTTPキャッシュは、古くなっても即時には破棄されません。クライアントはキャッシュが古くなると、コンテンツそのものを要求する前に、サーバーに対してコンテンツが更新されているかどうかを問い合わせます。更新されていなければコンテンツを再取得せずにキャッシュを再利用します。

この問い合わせの仕組みを 検証 と呼び、検証の際にクライアントから送信するリクエストのことを 条件付きリクエス と呼びます。

検証では主に ETag レスポンスヘッダおよび If-None-Match リクエストヘッダを用います4

ETag はコンテンツの特定のバージョンを一意に示す値で、一般にハッシュ値やバージョン番号を設定します。クライアントは初回リクエスト時に、コンテンツをキャッシュすると共に ETag を覚えておきます。そしてクライアントはキャッシュが古くなると検証を実施します。初回リクエスト時に覚えておいた ETag の値を If-None-Match に格納してコンテンツをリクエストします(条件付きリクエスト)。サーバは現在のコンテンツの ETagIf-None-Match と一致すれば 304 Not Modified を、不一致であれば 200 OK および最新のコンテンツをレスポンスします。レスポンスが 304 Not Modified であった場合は、クライアントは検証時点からさらに max-age の間、キャッシュを再利用し続けます。

キャッシュの検証のシーケンス例

この検証の通信容量はコンテンツそのものよりも大幅に小さいため、max-age の短いキャッシュであっても、効率的にキャッシュを利用することができます。

結局どうすればいいの?

たくさん説明しましたが、気になるのは「結局どういう場合にどのヘッダを付ければいいの?」だと思います。

以下に、ごく一般的なユースケースと設定を簡単にまとめます。

  • ETag は常にレスポンスする
    • いかなる max-age のキャッシュであっても、基本的に ETag を付けておく方が望ましいと思います。
    • どんなフレームワーククラウドサービスを利用しているかにも寄るかもしれませんが、通常難しい実装にはならないため、付けて損はないでしょう。
  • 常に最新のコンテンツを提供したい場合は Cache-Control: no-cache を利用する
    • エンドユーザがアクセスするたびに検証が実行されるため、常に最新のコンテンツを配信できます。
  • ほぼ最新のコンテンツを提供したい場合は Cache-Control: max-age=60 などの短いmax-ageを指定する
    • 「常に検証してほしい」要件は多くないと思います。たいていのWebサービスでは最長60秒〜数分のキャッシュであれば許容可能でしょう。
  • Cache Busting を利用する際には Cache-Control: max-age=31536000 などの長いmax-ageを指定する
    • サーバ側から能動的にキャッシュを削除する手法のことを Cache Busting と呼びます。端的には、HTTPキャッシュのキーはURLであることを利用して、キャッシュ削除したいコンテンツのファイル名を変更してしまう手段を取ります。
    • Cache Busting を利用する場合には、能動的に削除するまでキャッシュを再利用し続けていてほしいため、長いmax-ageを指定します。
  • どうしてもキャッシュされたくない場合は Cache-Control: no-store を利用する
    • 最新版を提供したいユースケースではETagとno-cacheを利用すればよいので、no-storeを利用するケースはセキュリティ要件でどうしてもキャッシュを許容できない場合などに限られるかと思います。

設定例

文章ではイメージが湧きにくいので、例を挙げます。

  • index.htmlETagCache-Control: max-age=60 を設定します
  • main.v1.cssETagCache-Control: max-age=31536000 を設定します
  • 新しいページをリリースする際には、CSSのファイル名を main.v2.css に変更して、Cache Busting します

このとき ①初回アクセス ②2回目以降のアクセス ③リリース後のアクセス におけるキャッシュヒットと通信の流れを簡単にシーケンス図を示します。

Cache Busting のシーケンス例

このように、index.html にのみ短いキャッシュを設定することで、CSSでは長期間のキャッシュを利用しつつも、リリース後長くとも60秒後には最新版をエンドユーザに届けることができます。

一方、この例では「ETag があるならCSSも短いキャッシュにしても大差ないのでは?」と思われるかもしれません。ですが、実際のWebシステムではCSSをはじめとするサブリソースの量は膨大になります。0.1秒でも早い読み込みを求められる現代のWebシステムでは、条件付きリクエストすら削減して通信回数を減らす意義は十分にあるはずです。

また、最近のビルドツール、例えばViteなどを利用すればビルド時にファイル名にハッシュ値を付与するのは簡単なので、Cache Busting の実装は比較的容易だと言えるでしょう。

(おまけ)ヒューリスティックキャッシュの期間

みなさまが関わるWebアプリ/サイトの中には、HTTPキャッシュを意識せずに構築されており、Cache-Control などのHTTPキャッシュを制御するヘッダを一切付与していないものもあるかもしれません。

そのとき気になるのは、ヒューリスティックキャッシュ(自動的なブラウザキャッシュ)の保存期間はどの程度なのか?です。

ヒューリスティックキャッシュの保存期間はRFCに規定はないため、各ブラウザの実装に依存します。具体的な値を調べてみたところ、少なくともChromiumWebkitにおけるキャッシュ期間は、対象オブジェクトが最後に変更されてから現在までの期間の10% (Dateの値 - Last-Modifiedの値) * 0.1 だそうです5

例えば、1ヶ月(30日)前に更新されたコンテンツを更新した場合、最長で 2592000秒 * 0.1 = 72時間 の間エンドユーザに最新版が届かないことになります。1年前に更新されたコンテンツであれば 31536000秒 * 0.1 = 36.5日 になりますね。

こうした状態を避けるためにも、あらかじめキャッシュ制御ヘッダを付けておくことはとても重要です。

We are hiring!

みらい翻訳では、私たちと一緒にプロダクト開発を進めてくれるエンジニアを募集しています!ご興味のある方は、ぜひ下記リンクよりご応募・お問い合わせをお待ちしております。

miraitranslate.com


  1. 共有キャッシュはさらに、プロキシによるプロキシーキャッシュとリバプロやCDNによるマネージドキャッシュに分類されます。
  2. Cache-Control ヘッダはリクエストヘッダとしても利用され、レスポンス時とはまた意味が異なります。当記事ではリクエストヘッダとしての Cache-Control の説明は割愛します。
  3. コンテンツを所持しレスポンスするサーバのこと。
  4. Last-Modified ヘッダおよび If-Modified-Since ヘッダを用いた検証も可能ですが、当記事では割愛します。
  5. https://paulcalvano.com/2018-03-14-http-heuristic-caching-missing-cache-control-and-expires-headers-explained/