- Published on
HTTPキャッシュ徹底解説
- Authors
- Name
- あやき
- @ayaki2325
はじめに
対象読者は、HTTPヘッダーを用いたキャッシュ制御のやり方を知らないバックエンドエンジニアです。
この記事のゴールは、適切なキャッシュ戦略1を自分で考えられるようになることです。
Table of Contents
HTTPキャッシュとは
HTTPキャッシュとは、HTTPヘッダーを用いてキャッシュ2を制御する仕組みです。
HTTPキャッシュの仕組み
以下画像の通り、キャッシュサーバーがキャッシュしているパターンと、クライアントがキャッシュしているパターンがあります3。

- キャッシュサーバーがキャッシュしているパターン
- キャッシュサーバーにキャッシュがある場合
- クライアントがキャッシュサーバーにリクエスト
- キャッシュサーバーがクライアントにレスポンス
- キャッシュサーバーにキャッシュがない場合
- クライアントがキャッシュサーバーにリクエスト
- キャッシュサーバーがオリジンサーバーにリクエスト
- オリジンサーバーがキャッシュサーバーにレスポンス
- キャッシュサーバーがレスポンスの内容をキャッシュとして保存
- キャッシュサーバーがクライアントにレスポンス
- キャッシュサーバーにキャッシュがある場合
- クライアントがキャッシュしているパターン
- クライアントにキャッシュがある場合
- クライアントがどこにもリクエストを送らずキャッシュを使って画面表示などを行う
- クライアントにキャッシュがない場合
- クライアントがオリジンサーバーにリクエスト
- オリジンサーバーがクライアントにレスポンス
- クライアントがレスポンスの内容をキャッシュとして保存
- クライアントが画面表示などを行う
- クライアントにキャッシュがある場合
さらに、キャッシュがあったとしても有効期限が切れている場合は、オリジンサーバーに有効性を確認するといった動作が入ることもあります。
これがHTTPキャッシュの仕組みです。
次に、キャッシュを使うと何が嬉しいのか解説します。
HTTPキャッシュのメリット
大まかに以下のようなメリットがあります。
- パフォーマンス向上: たとえば重い処理をオリジンサーバーが行っている場合、オリジンサーバーを経由しないのでパフォーマンスが向上する4。
- インフラコスト削減: たとえばオリジンサーバーの裏にDBサーバーがいる場合、これらのサーバーリソースを使わなくて済むのでインフラコストが削減できる。
- スケーラビリティ向上: キャッシュを読んで返す処理は比較的軽量なので、裏技5を使わずとも大量アクセスに強いシステムになる。
これがHTTPキャッシュのメリットです。
次に、HTTPキャッシュを実現する具体的な方法を解説します。
HTTPキャッシュを実現する方法
ここまで説明してきたようなキャッシュ制御は、HTTPヘッダーを使って実現します。
クライアントとサーバーがHTTPヘッダーを介して、キャッシュについての希望動作やキャッシュの利用可否などをやり取りして、具体的な動作が決定されます。
ここまででHTTPキャッシュの概要については以上です。
次に、具体的にHTTPキャッシュに関連するHTTPヘッダーを網羅的に解説します。
HTTPキャッシュに関するHTTPヘッダー
そこそこ数があるので、以下カテゴリに分けて解説を進めます。
- キャッシュ制御: キャッシュの挙動を直接指示するためのヘッダー
- キャッシュ検証: キャッシュの有効期限が切れた後、そのリソースがサーバー上で更新されているかを検証するためのヘッダー
- その他: キャッシュの挙動に影響を与えたり、キャッシュに関する情報を提供したりするヘッダー
HTTPヘッダー: キャッシュ制御
キャッシュの挙動を直接指示するためのヘッダーです。
主にサーバーがレスポンスで指定します。
Cache-Control
ディレクティブと呼ばれるコマンドを指定することによってキャッシングの動作を指定します。
リクエストとレスポンス両方で指定できます。
ディレクティブにはパラメーターがあるものとないものがあり、複数のディレクティブを指定する場合には、カンマ,
で区切ります。
Cache-Control: private, max-age=0, no-cache
リクエストで指定できるディレクティブは以下です。
ディレクティブ | パラメーター | 説明 |
---|---|---|
no-cache | なし | オリジンサーバーへの強制的な再検証 |
no-store | なし | キャッシュはリクエスト、レスポンスの一部分を保存してはならない |
max-age=[秒] | 必須 | 指定した値よりキャッシュされたリソースが新しい場合には、それを受け入れることができる |
max-stale(=[秒]) | 省略可能 | 期限切れのレスポンスを受け入れる。値を指定した場合、有効期限が切れてから指定時間内であれば受け入れられることを伝える。 |
min-fresh=[秒] | 必須 | 指定した時間は新鮮なレスポンスを望む。たとえば60秒と指定されている場合、60秒以内で有効期限が切れるリソースをレスポンスとして返すことはできない。 |
no-transform | なし | プロキシはメディアタイプを変換してはならない |
only-if-cached | なし | 目的のリソースがキャッシュサーバーのローカルキャッシュにある場合のみレスポンスを返すよう要求 |
cache-extension | - | 新しいディレクティブのためのトークン |
レスポンスで指定できるディレクティブは以下です。
ディレクティブ | パラメーター | 説明 |
---|---|---|
public | なし | 他のユーザーにも返すことができるキャッシュを行ってもよいと明示する |
private | なし | 特定のユーザーに対してのみのレスポンス |
no-cache(=[ヘッダーフィールド名]) | 省略可能 | 有効性の再確認なしではキャッシュは保存してはならない。ヘッダーフィールド名が指定された場合、そのフィールドだけキャッシュできない。 |
no-store | なし | キャッシュはリクエスト、レスポンスの一部分を保存してはならない |
no-transform | なし | プロキシはメディアタイプを変換してはならない |
must-revalidate | なし | キャッシュ可能であるが、オリジンサーバーにリソースの再確認を要求する |
proxy-revalidate | なし | 中間キャッシュサーバーに対し、キャッシュしたレスポンスの有効性の再確認を要求する |
max-age=[秒] | 必須 | キャッシュサーバーが有効性の再確認を行わずにリソースをキャッシュに保持しておける最大時間 |
s-maxage=[秒] | 必須 | max-ageと同様。違いは、共有キャッシュサーバーにだけ適用されるという点。 |
cache-extension | - | 新しいディレクティブのためのトークン |
Expires
レスポンスに指定されます。
リソースの有効期限の日時を伝えます。Cache-Control: max-age=[秒]
が指定されている場合には、このディレクティブは無視されます。
Pragma
リクエストに指定されます。
HTTP/1.0時代の古いヘッダーです。Pragma: no-cache
という形で使われ、Cache-Control: no-cache
と同義です。
下位互換性のために残されています。
HTTPヘッダー: キャッシュ検証
キャッシュの有効期限が切れた後、そのリソースがサーバー上で更新されているかを検証するためのヘッダーです。
ETag
レスポンスに指定されます。
リソースを一義的に特定するための文字列を伝えます。
リソースが更新されるとETag値も更新する必要があります。
後述のIf-None-Match
ヘッダーと組み合わせて使います。
If-None-Match
リクエストに指定されます。
クライアントが前回取得したETagの値をサーバーに送り、「このETagと一致しない場合のみ、リソースを送ってください」と要求します。
一致した場合はキャッシュが有効であることがわかります。
Last-Modified
レスポンスに指定されます。
リソースの最終更新日時を示します。
後述のIf-Modified-Since
ヘッダーと組み合わせて使います。
If-Modified-Since
リクエストに指定されます。
クライアントが前回取得したLast-Modifiedの日時をサーバーに送り、「この日時以降に更新されている場合のみ、リソースを送ってください」と要求します。
指定日時以降に更新されていない場合は、キャッシュが有効であることがわかります。
HTTPヘッダー: その他
キャッシュの挙動に影響を与えたり、キャッシュに関する情報を提供したりするヘッダーです。
Vary
レスポンスに指定されます。
キャッシュをコントロールするために使います。
オリジンサーバーがプロキシサーバーに対して、ローカルキャッシュの使い方の指示を伝えます。
オリジンサーバーからVaryで指定されたレスポンスを受け取ったプロキシサーバーは、以後はキャッシュしたときのリクエストと同様のVaryに指定されているヘッダーフィールドを持つリクエストに対してのみキャッシュを返すことができます。
以下例で考えます。
Vary: Accept-Language
この場合、同じ言語(Accept-Language)のリクエストにのみキャッシュからレスポンスします。
すでにキャッシュがあるURIであってもAccept-Languageが異なるリクエストに対してはキャッシュを返しません。
Age
レスポンスに指定されます。
どれぐらい前にオリジンサーバーでレスポンスが生成されたかを伝えます。
レスポンスをしたのがキャッシュサーバーの場合、キャッシュしたレスポンスが再実証されてから検証した時間になります。
Clear-Site-Data
レスポンスに指定されます。Clear-Site-Data: cache
とすることで、そのサイトのHTTPキャッシュを強制的にクリアさせることができます。
ここまででHTTPキャッシュに関するHTTPヘッダーについては以上です。
次に、これらの情報を元にどうキャッシュ戦略を立てていけばいいか解説します。
キャッシュ戦略を立てるための観点
キャッシュ戦略を立てるのに役立つ、5つの観点を紹介します。
それぞれについて自問自答することで、自然と最適な戦略が立てられるはずです。
- 可視性: 全員に共通の内容か、個人向けか
- 鮮度: どれくらいの頻度で更新されるか
- 検証方法: 期限切れの確認をどう効率化するか
- 厳密度: 古い情報のレスポンスを許容するか
- 文脈: 特定の文脈における挙動をどうすべきか
可視性: 全員に共通の内容か、個人向けか
レスポンスが全ユーザーで全く同じ内容かどうかを判断します。
- 全ユーザーで全く同じ内容:
Cache-Control: public
を指定。たとえば、一般公開されている記事一覧/api/articles
など。 - 特定のユーザーだけの内容:
Cache-Control: private
を指定。たとえば、ユーザープロフィール/api/me
など。
鮮度: どれくらいの頻度で更新されるか
データの寿命を定義して max-age の値を決定します。
- 変更頻度がときどき〜ほとんど不変: 適切な
Cache-Control: max-age=[値]
を設定する。 - リアルタイム性が高い:
Cache-Control: no-cache
を指定して常に再検証されるようにする。 - そもそもキャッシュすべきではない:
Cache-Control: no-store
を指定して一切の保存を禁止する。たとえば、秘匿性の高いトークンなど。
検証方法: 期限切れの確認をどう効率化するか
キャッシュの有効期限が切れたとして、キャッシュが確かに古いかどうか確認する方法を決定します。
この場合、通常はオリジンサーバーにリソースを再度取得することになります。
仮にキャッシュとオリジンサーバーのリソースが同一であったとしても、です。
この挙動で問題ないならいいのですが、もしもっと通信を効率化したい場合は以下の実現を検討します。
- ETagとIf-None-Matchを用いて検証: キャッシュが古くない場合、新しいリソースを丸ごとダウンロードしなくて済むので通信量が減る。基本的には下記よりこちらを推奨。
- Last-ModifiedとIf-Modified-Sinceを用いて検証: 同上のことができるが、タイミングによっては更新を検知できないことがある。
厳密度: 古い情報のレスポンスを許容するか
キャッシュが古くなったとして、古い情報をレスポンスしていいかどうかを判断します。
たとえば、オリジンサーバーにアクセスできない場合、キャッシュの内容をレスポンスしていいか、それともきちんとエラーを返してほしいかを決定するということです、
- エラーにするくらいなら古い情報がレスポンスされてもいい: 特に追加で指定するHTTPヘッダーはない
- 少しでも古い情報は絶対にNG:
Cache-Control: must-revalidate
を指定してオリジンサーバーへの再検証を必須にする。検証できない場合は504 Gateway Timeout
を返す。たとえば、銀行の口座残高など。
文脈: 特定の文脈における挙動をどうすべきか
CDNの利用やレスポンスの圧縮など、特定の文脈に応じてキャッシュの挙動を微調整します。
- CDNのキャッシュ期間を指定したい:
Cache-Control: s-maxage=[秒]
を指定します。 - リクエスト内容によってキャッシュを使い分けたい: Varyヘッダーを指定します。例えば
Vary: Accept-Encoding
と指定すると、キャッシュサーバーは同じURLでも「gzipで圧縮されたレスポンス」と「非圧縮のレスポンス」を別物として管理してくれます。
まとめ
- HTTPキャッシュは、リソースのコピーを一時的に保存する仕組みであり、パフォーマンス向上、インフラコスト削減、スケーラビリティ向上に貢献します。
- キャッシュの制御は、クライアントとサーバーがやり取りするHTTPヘッダーによって実現されます。
- 最適なキャッシュ戦略を立てるには、以下の5つの観点からデータやリソースの性質を分析することが有効です。
- 可視性: 全員に共通の内容か、個人向けか(
public
,private
) - 鮮度: どれくらいの頻度で更新されるか(
max-age
,no-cache
) - 検証方法: 期限切れの確認をどう効率化するか(
ETag
) - 厳密度: 古い情報のレスポンスを許容するか(
must-revalidate
) - 文脈: 特定の文脈における挙動をどうすべきか(
s-maxage
,Vary
)
- 可視性: 全員に共通の内容か、個人向けか(