by MintJams

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

HTTPの次世代ヘッダー「Cache-Status」を徹底解説。多段キャッシュ環境で「なぜキャッシュが効かない?」を秒速で特定するテクニックを、NGINX設定例とログ活用法を交えて紹介します。

この記事では、Web開発者やインフラエンジニア向けに、キャッシュの挙動を可視化する新しいHTTPヘッダー Cache-Status について解説します。複数のキャッシュレイヤーをまたいだ問題を秒速で突き止めるための実践的なアプローチを紹介します。


この記事でわかること

  • Cache-Status ヘッダーが解決する「多段キャッシュ」の問題
  • Cache-Status の主要なパラメータと、正確な読み解き方
  • CDNの独自ヘッダーとの違いと、実際のプロダクション環境での使い分け
  • NGINXでの実装例と、ログ・モニタリングでの活用法

Cache-Status で「キャッシュが効かない」を秒速特定する

Webサイトのパフォーマンスチューニングにおいて、キャッシュは欠かせません。しかし、ブラウザ、CDN、リバースプロキシなど、複数のキャッシュ層が存在する現代のシステムでは、「なぜかキャッシュが効かない」という問題が発生すると、原因特定が非常に困難になりがちです。


なぜキャッシュが効かないのか、すぐにわからないのか?

従来のHTTPヘッダーでは、キャッシュの情報を各レイヤーが独自に伝達していました。たとえば、CDNは X-Cache、オリジンサーバーは ETagLast-Modified など、断片的な情報しか得られません。

特に困るのが、「キャッシュのチェーン」です。

  • ブラウザCDNオリジンサーバー

この流れの中で、オリジンサーバーが Cache-Control: no-cache を設定していた場合、これは「キャッシュはするが、利用前にオリジンへの再検証が必須」を意味します。このため、Cache-Status ではCDN側のメンバーがfwd=stale; fwd-status=304 のように見えることがあり、単なる「キャッシュヒット/ミス」だけでは判断できない複雑な状況が発生します。

この問題を解決するために登場したのが、RFC 9211 で標準化された Cache-Status ヘッダーです。


Cache-Status ヘッダーとは?

Cache-Status は、キャッシュチェーン全体での各キャッシュの振る舞いを、hitfwd(転送理由)といった統一されたフォーマットで、機械可読な形で一望できるようにする目的で策定されました。これにより、個別のベンダーに依存する X-CacheCF-Cache-Status といった独自ヘッダーの問題を解消しようとしています。

Cache-Status の基本的な構文

リスト構造になっており、最初の要素がオリジンに近いキャッシュ、最後の要素がユーザーに最も近いキャッシュとして追加されます。

Cache-Status: OriginCache; hit; ttl=1100, "CDN Company"; fwd=uri-miss; collapsed

この例では、以下のように読み解くことができます。

  1. OriginCache: オリジンサーバー手前のキャッシュ(リバースプロキシなど)で、ヒット (hit)。有効期限(ttl)は残り1100秒です。
  2. "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=?0fwdがある時のみ有効。 collapsed → リクエストコラプシングが成功
key / detail キャッシュキーや実装依存の情報。本番環境では露出に注意 key="a0c5c..." → キャッシュキー情報を追加

補足:同一メンバー内で hitfwd は同時に現れません(どちらか一方)。また 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 の真価は、ログ基盤と連携して初めて発揮されます。

  1. アクセスログへの投入: NGINXの設定例のように、$sent_http_cache_status 変数を使って、実際にクライアントに返された Cache-Status ヘッダーの値をログに含めます。
  2. ログ集計: ログ集計ツール(Elasticsearch, Datadogなど)で、Cache-Status をパースし、各パラメータを構造化データとして保存します。
  3. ダッシュボード化: fwd=uri-missttl が負の値になっているリクエストの割合を可視化します。

これにより、「特定のエンドポイントで急に uri-miss が増えた」といった異常をグラフで即座に検知できるようになります。

たとえば、次のようなグラフを作成すると効果的です。

  • キャッシュヒット率の推移: hit の割合を時系列で追うことで、キャッシュが正常に機能しているか一目でわかります。
  • 転送理由の割合: uri-missfwd=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サービスのパフォーマンスチューニングの一助となれば幸いです。ご覧いただきありがとうございました。