Cache-Statusで「キャッシュが効かない」を秒速特定!次世代のHTTPヘッダー活用術

この記事では、Web開発者やインフラエンジニア向けに、キャッシュの挙動を可視化する新しいHTTPヘッダー Cache-Status
について解説します。複数のキャッシュレイヤーをまたいだ問題を秒速で突き止めるための実践的なアプローチを紹介します。
この記事でわかること
Cache-Status
ヘッダーが解決する「多段キャッシュ」の問題Cache-Status
の主要なパラメータと、正確な読み解き方- CDNの独自ヘッダーとの違いと、実際のプロダクション環境での使い分け
- NGINXでの実装例と、ログ・モニタリングでの活用法
Cache-Status
で「キャッシュが効かない」を秒速特定する
Webサイトのパフォーマンスチューニングにおいて、キャッシュは欠かせません。しかし、ブラウザ、CDN、リバースプロキシなど、複数のキャッシュ層が存在する現代のシステムでは、「なぜかキャッシュが効かない」という問題が発生すると、原因特定が非常に困難になりがちです。
なぜキャッシュが効かないのか、すぐにわからないのか?
従来のHTTPヘッダーでは、キャッシュの情報を各レイヤーが独自に伝達していました。たとえば、CDNは X-Cache
、オリジンサーバーは ETag
や Last-Modified
など、断片的な情報しか得られません。
特に困るのが、「キャッシュのチェーン」です。
- ブラウザ → CDN → オリジンサーバー
この流れの中で、オリジンサーバーが Cache-Control: no-cache
を設定していた場合、これは「キャッシュはするが、利用前にオリジンへの再検証が必須」を意味します。このため、Cache-Status
ではCDN側のメンバーがfwd=stale; fwd-status=304
のように見えることがあり、単なる「キャッシュヒット/ミス」だけでは判断できない複雑な状況が発生します。
この問題を解決するために登場したのが、RFC 9211 で標準化された Cache-Status
ヘッダーです。
Cache-Status
ヘッダーとは?
Cache-Status
は、キャッシュチェーン全体での各キャッシュの振る舞いを、hit
、fwd
(転送理由)といった統一されたフォーマットで、機械可読な形で一望できるようにする目的で策定されました。これにより、個別のベンダーに依存する X-Cache
や CF-Cache-Status
といった独自ヘッダーの問題を解消しようとしています。
Cache-Status
の基本的な構文
リスト構造になっており、最初の要素がオリジンに近いキャッシュ、最後の要素がユーザーに最も近いキャッシュとして追加されます。
Cache-Status: OriginCache; hit; ttl=1100, "CDN Company"; fwd=uri-miss; collapsed
この例では、以下のように読み解くことができます。
OriginCache
: オリジンサーバー手前のキャッシュ(リバースプロキシなど)で、ヒット (hit
)。有効期限(ttl
)は残り1100秒です。"CDN Company"
: CDNのキャッシュでは、miss
ではなく転送 (fwd=uri-miss
)が起きています。これは、リクエストされたURIに一致するキャッシュが存在しなかったためです。さらに、collapsed
は、同じリソースへの同時アクセスを一つのリクエストにまとめた(リクエストコラプシング)ことを示唆しています。
このように、たった一行のヘッダーで、キャッシュチェーン全体の挙動を把握できるのです。
主要なパラメータと読み解き方
Cache-Status
を理解する上で、まず押さえておきたい主要なパラメータをいくつか紹介します。
パラメータ | 意味 | 読み解き例 |
---|---|---|
hit |
キャッシュから応答。ttl が負数の場合はstale からの配信。 |
hit; ttl=376 → 残り鮮度376秒でヒット<br>hit; ttl=-120 → 鮮度切れだがヒット(staleヒット) |
fwd |
オリジンへ転送した理由。 | fwd=uri-miss → キー不一致で転送<br>fwd=stale → 鮮度切れのため再検証のために転送 |
fwd-status |
転送先のオリジンからのステータスコード。 | fwd=stale; fwd-status=304 → 鮮度切れで再検証し、304を返した |
ttl |
「残り鮮度(秒)」。負数はstale (鮮度切れ)。 |
ttl=300 → 鮮度が残り300秒<br>ttl=-50 → 鮮度切れから50秒経過 |
stored |
転送後にキャッシュに保存したか。fwd がある時のみ有効。 |
fwd=miss; stored=?1 → ミスだったが、保存した |
collapsed |
リクエストコラプシングの成否。<br>失敗時は collapsed=?0 。fwd がある時のみ有効。 |
collapsed → リクエストコラプシングが成功 |
key / detail |
キャッシュキーや実装依存の情報。本番環境では露出に注意。 | key="a0c5c..." → キャッシュキー情報を追加 |
補足:同一メンバー内で hit
と fwd
は同時に現れません(どちらか一方)。また ttl<0
は stale 配信を示します。
これらのパラメータを組み合わせることで、「fwd=uri-miss
が多発しているから、キャッシュキーの設定に問題があるかもしれない」「fwd=stale; fwd-status=304
の割合が高いから、Cache-Control
を見直そう」といった具体的な対策に繋げられます。
NGINXでの実装例:コピペで試せる!
現状、多くのCDNは Cache-Status
を自動で付与しません。そのため、自前でこのヘッダーを付与するのが現実的なアプローチです。ここでは、NGINXをリバースプロキシとして使用する場合の実装例を紹介します。
NGINXでは、$upstream_cache_status
変数を使ってキャッシュの状態を取得し、map
ディレクティブでCache-Status
の標準的な値に変換するのが最も効果的です。
# キャッシュ設定(例)
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:200m inactive=60m;
map $upstream_cache_status $cs_basic {
HIT "hit";
MISS "fwd=miss";
BYPASS "fwd=bypass";
EXPIRED "fwd=stale"; # 期限切れでオリジンに転送
REVALIDATED "fwd=stale; fwd-status=304"; # 条件付きリクエストで再検証
STALE "hit"; # ステールをそのまま配信(proxy_cache_use_stale)
UPDATING "hit"; # 更新中のステール配信
default "fwd=miss";
}
map $upstream_cache_status $cs_detail {
STALE "; detail=STALE";
UPDATING "; detail=UPDATING";
default "";
}
server {
proxy_cache my_cache;
# Cache-Status を常に付与(4xx/5xx でも)
add_header Cache-Status "ReverseProxyCache; $cs_basic$cs_detail" always;
# (任意)アクセスログに書く
log_format cachelog escape=json
'{ "time":"$time_iso8601","status":$status,'
'"u_cache":"$upstream_cache_status","cache_status":"$sent_http_cache_status",'
'"rt":$request_time,"uri":"$request_uri" }';
access_log /var/log/nginx/access_cache.log cachelog;
location / { proxy_pass http://backend; }
}
この例では、map
ブロックで $upstream_cache_status
の値を Cache-Status
の標準的なパラメータに変換し、add_header
でヘッダーとして付与しています。また、always
をつけることで、キャッシュが適用されないエラーレスポンス(4xx/5xx)でもヘッダーが付与され、トラブルシューティングに役立ちます。
可観測化の実践:ログ・モニタリングでの活用
Cache-Status
の真価は、ログ基盤と連携して初めて発揮されます。
- アクセスログへの投入: NGINXの設定例のように、
$sent_http_cache_status
変数を使って、実際にクライアントに返されたCache-Status
ヘッダーの値をログに含めます。 - ログ集計: ログ集計ツール(Elasticsearch, Datadogなど)で、
Cache-Status
をパースし、各パラメータを構造化データとして保存します。 - ダッシュボード化:
fwd=uri-miss
やttl
が負の値になっているリクエストの割合を可視化します。
これにより、「特定のエンドポイントで急に uri-miss
が増えた」といった異常をグラフで即座に検知できるようになります。
たとえば、次のようなグラフを作成すると効果的です。
- キャッシュヒット率の推移:
hit
の割合を時系列で追うことで、キャッシュが正常に機能しているか一目でわかります。 - 転送理由の割合:
uri-miss
、fwd=stale
などの割合を円グラフなどで表示し、主なキャッシュミスの原因を特定します。 - 再検証率:
fwd-status=304
の割合を見ることで、条件付きリクエストによる効率的な再検証がどれくらい行われているかを把握できます。
よくある落とし穴:注意すべき点
- CDNの独自ヘッダーとの併用: Cloudflareの
CF-Cache-Status
など、既存の独自ヘッダーを併用し、段階的にCache-Status
に移行するのが現実的です。ダッシュボード側で両者を突き合わせることで、Cache-Status
が未対応な区間の補助ができます。 - 情報の出し過ぎに注意:
key
などの詳細なキャッシュ情報は、RFC 9211の“Security Considerations”にもある通り、攻撃者にキャッシュポイズニングのヒントを与える可能性があります。本番環境では、公開する情報を限定し、内部ツールやデバッグ環境のみで詳細情報を表示するようにしましょう。
導入時のチェックリストと動作確認
導入時のチェックリスト
- まずは 下流すべてで
Cache-Status
を温存(プロキシやアプリで勝手に上書きしない)。 - NGINX は
$upstream_cache_status → Cache-Status
マッピングを入れる。always
で 4xx/5xx も可視化。 - ログに
$sent_http_cache_status
を入れてダッシュボード化。 - ベンダー固有ヘッダーとの二刀流で暫定運用(例:
CF-Cache-Status
)。 - 本番は
key
/detail
の露出を最小化。デバッグ環境のみ詳細。
動作確認ワンライナー
Cache-Status
ヘッダーが付与されているか、curl
コマンドで簡単に確認できます。
curl -I https://example.com | grep -i '^cache-status:'
二段以上のキャッシュを通過していれば、カンマ区切りで複数のメンバーが並ぶのが正常です。
まとめ
Cache-Status
ヘッダーは、多層的なキャッシュシステムにおいて、各キャッシュレイヤーの振る舞いを統一されたフォーマットで可視化する強力なツールです。
hit
/fwd
などのパラメータで、キャッシュが効かない原因を瞬時に特定できます。- NGINXの設定例のように、自前でヘッダーを付与することで、既存のインフラでも導入可能です。
- ログ基盤と組み合わせることで、キャッシュの異常を早期に検知し、安定したサービス運用に繋がります。
この記事が、Webサービスのパフォーマンスチューニングの一助となれば幸いです。ご覧いただきありがとうございました。