HTTP キャッシュ

Cache-Control, ETag, Last-Modified を使った HTTP レベルのキャッシュ制御

HTTPパフォーマンス

HTTP キャッシュとは

HTTP キャッシュは、Cache-ControlETagLast-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 日)
HTMLpublic, max-age=0, must-revalidateデプロイごとに中身が変わるのに URL は変わらないため、毎回オリジンで検証させる
API レスポンスno-store値が短時間で変わり、古い値を返すと実害が出る
ユーザー固有データprivate, no-store他人に渡ると事故になるため、保存させない。no-store だけでも足りるが private を併記して意図を明示している

ETag による条件付きリクエスト

初回の応答で受け取った ETag を、次のリクエストで If-None-Match として送り返す。

1回目のリクエスト:
  GET /api/users/123200 OK
  → ETag: "abc123"
  → Cache-Control: no-cache

2回目のリクエスト:
  GET /api/users/123
  If-None-Match: "abc123"
  → 304 Not Modified (ボディなし、帯域節約)
  or200 OK + 新しい ETag (データが変更された場合)

ETag は表現を区別するための不透明な値であって、ハッシュ値である必要はない。更新カウンタでもリビジョン番号でも、サーバーが同一性を判断できるなら形式は自由である。既定はバイト単位の同一性まで保証する強い検証子で、意味的に同じ程度しか保証しないときは W/"abc123" のように W/ を前置して弱い検証子であることを示す。変更が無ければ 304 が返り、レスポンスボディの転送を省略できる。

Last-Modified を使う場合は、前回受け取った更新時刻を If-Modified-Since に入れて送る。ヘッダーの時刻は秒までしか表せないため、同じ秒の内に 2 回更新されると変更を取りこぼす。取りこぼしが許されないリソースでは ETag を併用する。

CloudFront でのキャッシュ制御

CloudFront はオリジンが返した Cache-Controlmax-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 にする。

理論と実装の両面から学ぶなら関連書籍が参考になる。

この記事は役に立ちましたか?

関連用語

関連する記事