HTTP キャッシュ
Cache-Control, ETag, Last-Modified を使った HTTP レベルのキャッシュ制御
HTTP キャッシュとは
HTTP キャッシュは、Cache-Control、ETag、Last-Modified ヘッダーを使って、ブラウザや CDN にレスポンスをキャッシュさせる仕組みである。同じリソースへの再リクエストを削減し、ページの表示速度を向上させ、サーバーの負荷を軽減する。
Cache-Control ヘッダー
レスポンスヘッダーは次のように書く。
Cache-Control: public, max-age=31536000, immutable
| ディレクティブ | 意味 |
|---|---|
public | 既定では共有キャッシュに保存できない応答も、保存してよいと明示する |
private | 共有キャッシュには保存させず、ブラウザなど単一の利用者のキャッシュにだけ保存させる |
max-age=N | 応答を受け取ってから N 秒間は鮮度があるものとして再利用してよい |
no-cache | 保存はしてよいが、再利用の前に必ずオリジンで検証する |
no-store | リクエストもレスポンスも一切保存させない |
must-revalidate | 期限切れ後は検証に成功するまで再利用しない (検証できなければエラーを返す) |
immutable | 鮮度期間内は条件付きリクエストを送らせない (利用者の強制リロードでは覆る) |
stale-while-revalidate=N | 期限切れ後 N 秒間は古いキャッシュを返しつつ裏で更新する |
public は誤解されやすい。max-age を付けた通常の応答は public が無くても共有キャッシュに保存できるので、足しても挙動は変わらない。効いてくるのは Authorization ヘッダー付きの応答のように、既定では共有キャッシュが保存できないものをあえて保存可と宣言する場面である。
immutable も「再検証が止まる」と読むと期待を外す。鮮度期間内のキャッシュはそもそも再検証されない。この指定が減らすのは、利用者がリロードしたときに走る条件付きリクエストと 304 の往復である。期限切れ後は指定が無い場合と同じように再検証される。
コンテンツ種別ごとの設定
判断軸は 2 つである。URL が内容に対して一意かどうかと、利用者ごとに中身が違うかどうか。前者が満たされていれば期限を伸ばせ、後者に当たれば共有キャッシュから外す。
| コンテンツ | Cache-Control | 理由 |
|---|---|---|
| JS/CSS (ハッシュ付き) | public, max-age=31536000, immutable | ファイル名にハッシュを含むため、内容が変われば URL が変わる |
| 画像 | public, max-age=2592000 | 更新頻度が低く、差し替えるときは URL を変えられる (2592000 秒 = 30 日) |
| HTML | public, max-age=0, must-revalidate | デプロイごとに中身が変わるのに URL は変わらないため、毎回オリジンで検証させる |
| API レスポンス | no-store | 値が短時間で変わり、古い値を返すと実害が出る |
| ユーザー固有データ | private, no-store | 他人に渡ると事故になるため、保存させない。no-store だけでも足りるが private を併記して意図を明示している |
ETag による条件付きリクエスト
初回の応答で受け取った ETag を、次のリクエストで If-None-Match として送り返す。
1回目のリクエスト:
GET /api/users/123
→ 200 OK
→ ETag: "abc123"
→ Cache-Control: no-cache
2回目のリクエスト:
GET /api/users/123
If-None-Match: "abc123"
→ 304 Not Modified (ボディなし、帯域節約)
or
→ 200 OK + 新しい ETag (データが変更された場合)
ETag は表現を区別するための不透明な値であって、ハッシュ値である必要はない。更新カウンタでもリビジョン番号でも、サーバーが同一性を判断できるなら形式は自由である。既定はバイト単位の同一性まで保証する強い検証子で、意味的に同じ程度しか保証しないときは W/"abc123" のように W/ を前置して弱い検証子であることを示す。変更が無ければ 304 が返り、レスポンスボディの転送を省略できる。
Last-Modified を使う場合は、前回受け取った更新時刻を If-Modified-Since に入れて送る。ヘッダーの時刻は秒までしか表せないため、同じ秒の内に 2 回更新されると変更を取りこぼす。取りこぼしが許されないリソースでは ETag を併用する。
CloudFront でのキャッシュ制御
CloudFront はオリジンが返した Cache-Control の max-age に従ってエッジにキャッシュする。ただしキャッシュポリシーの Minimum TTL と Maximum TTL が上下限として効き、max-age がその範囲を外れると下限か上限の値へ丸められる。オリジンがキャッシュ用のヘッダーを何も返さなかった場合は Default TTL が使われる。コンソールで作るときの既定値は 24 時間なので、ヘッダーを付け忘れたリソースが 1 日エッジに残り続ける事故が起きやすい。
stale-while-revalidate
ヘッダーは次のように書く。
Cache-Control: public, max-age=300, stale-while-revalidate=60
300 秒間は鮮度があるものとしてキャッシュを返す。期限切れから 60 秒、つまり 300 秒から 360 秒までの間は、古いキャッシュをすぐに返しながら裏でオリジンへ問い合わせて差し替える。待たされるのは、この 60 秒の窓を過ぎてから最初にアクセスした利用者だけになる。
よくある失敗
HTML に長い max-age を設定
HTML に max-age=31536000 を設定すると、デプロイしても古い HTML がキャッシュから返り続ける。HTML は max-age=0 にし、JS/CSS はファイル名ハッシュ + 長い max-age にする。
理論と実装の両面から学ぶなら関連書籍が参考になる。
この記事は役に立ちましたか?
関連用語
キャッシュ
頻繁にアクセスされるデータを高速なストレージに保存し、読み取り性能を向上させる手法
キャッシュ無効化とは - TTL / イベント駆動 / パージ戦略の比較
キャッシュ無効化は古くなったキャッシュデータを最新に更新する仕組み。TTL 方式 / Write-through / イベント駆動パージの使い分けと実装パターンを解説
TTL
データやキャッシュの有効期限を設定し、自動的に削除 / 更新する仕組み
CDN
世界中のエッジロケーションにコンテンツをキャッシュし、低レイテンシで配信するネットワーク
PWA
Service Worker やマニフェストを活用し、Web 技術でオフライン対応やプッシュ通知などネイティブアプリに近い体験を提供するアプローチ
ElastiCache
AWS のマネージドインメモリキャッシュサービスで、Valkey / Redis OSS / Memcached をサポートする
関連する記事
技術書がエンジニアのキャリアを変える - 読書習慣と年収の関係
技術書の読書習慣がエンジニアのキャリアアップに影響する 3 つの経路と、ジュニア / ミドル / シニア各段階に応じた読書戦略を具体的に解説します。
年収を上げた 1 冊 - エンジニアのキャリアを変えた本に共通する 3 つの特徴
エンジニアのキャリアに転機をもたらした本にはどんな共通点があるのか。キャリアを変える本に共通する 3 つの特徴を整理します。
コードを書かずに技術力を上げる休日の過ごし方
休日にコードを書く気力がないとき、それでも技術力を伸ばす方法があります。読書、設計スケッチ、技術記事の執筆など、キーボードに触れずにスキルアップする具体的な過ごし方。