キャッシュ制御の深淵:HTTP/1.1の`Cache-Control`をマスターする
ネットワークエンジニアとして現場を渡り歩いていると、「なぜか古いコンテンツが表示される」「意図せずキャッシュが効きすぎて更新が反映されない」といったトラブルに幾度となく遭遇する。その原因のほとんどは、HTTP/1.1の心臓部の一つである`Cache-Control`ヘッダーの解釈ミスにある。
RFC 7234が規定するこのヘッダーは、単なる設定値ではない。クライアント、CDN、そしてブラウザの「生存戦略」を定義する憲法のようなものだ。今回は、現場で泥臭くデバッグを行うエンジニア諸君のために、このキャッシュ制御の真髄を紐解いていこう。
—
1. キャッシュの意思決定フローを理解せよ
まず押さえておきたいのは、キャッシュとは「いかに通信を減らすか」という戦いであると同時に、「いかに最新性を担保するか」というリスク管理であるという点だ。
通信フローにおいて、`Cache-Control`は以下の3つの役割を果たす。
1. TTLの定義: どれくらいの間、そのリソースを信頼して使い続けるか。
2. 検証の強制: 再利用する前に、必ずサーバーへ「更新はあるか?」と聞きに行くべきか。
3. 保存の禁止: そもそもローカルディスクやプロキシのディスクに書き込むべきではないのか。
—
2. 実務で多用する主要ディレクティブの正体
多くのエンジニアが「なんとなく」使っているディレクティブを、現場の視点で切り分ける。
`max-age=`
最も基本的な「寿命」だ。例えば`max-age=3600`とすれば、ブラウザは1時間、サーバーに問い合わせることなく手元のキャッシュを使う。これはネットワーク帯域の節約には最強だが、その1時間の間にサーバー側でデータが更新されても、クライアントはそれを知る術がないという諸刃の剣でもある。
`no-cache`
名前で勘違いしやすいが、「キャッシュするな」ではない。「検証なしで使うな」という意味だ。キャッシュは保存されるが、使う前に必ずサーバーへ「条件付きリクエスト(`If-None-Match`など)」を送り、304 Not Modifiedが返ってくることを確認しなければならない。
`no-store`
これが「キャッシュするな」の真打ちだ。メモリにもディスクにも一切保存してはならない。機密性の高い個人情報や、一度限りの認証トークンを扱うAPIレスポンスには、必ずこれを付与する。
`must-revalidate`
`max-age`が切れたら、絶対にサーバーに確認せよ、という指示だ。ネットワークが切断されている場合など、本来はキャッシュの期限が切れても「許容範囲なら古いものを見せる」というブラウザの甘い挙動を封じ、「古いならエラーにせよ」と強制する。金融系など厳格な整合性が求められる現場で重宝する。
—
3. 実践:サーバーサイドの設定とクライアントでの検証
ここからは、実際に現場でどう書くかを見ていこう。
Nginxでの設定例
リバースプロキシやWebサーバーとしてNginxを運用している場合、`location`ブロックに以下のように記述する。
location /api/v1/user-profile {
# 個人情報はキャッシュさせない(no-store)
# 再検証を強制する(no-cache)
add_header Cache-Control “no-store, no-cache, must-revalidate, proxy-revalidate”;
}
location /static/js/ {
# JSなどの資産は強力にキャッシュさせる
# 1年間キャッシュを許可
expires 1y;
add_header Cache-Control “public, max-age=31536000, immutable”;
}
クライアント(Fetch API)でのデバッグ
クライアント側でリクエストを投げる際、キャッシュをどう扱うか制御したいケースも多い。
// 強制的にネットワークから取得し、キャッシュを更新させる場合
fetch(‘/api/data’, {
method: ‘GET’,
headers: {
// ブラウザのキャッシュを無視して最新を取りに行くヘッダー
‘Cache-Control’: ‘no-cache’,
‘Pragma’: ‘no-cache’ // レガシー対応
}
})
.then(response => response.json())
.then(data => console.log(‘最新データを取得:’, data));
—
4. トラブルシューティングの鉄則
もし「更新が反映されない!」という緊急コールを受けたら、以下の手順で切り分けてほしい。
1. `curl`でヘッダーを確認:
`curl -I -v https://example.com/target-resource` を打ち、`Cache-Control`と`Age`ヘッダーを見ろ。`Age`が大きくなっていれば、途中のCDNやプロキシがキャッシュを握っている証拠だ。
2. ブラウザのDevTools:
Networkタブの「Size」列を確認せよ。`(disk cache)`や`(memory cache)`と出ていれば、ブラウザがサーバーを見に行かずにローカルから読み出している。
3. `Vary`ヘッダーを確認:
`Cache-Control`以前に、`Vary`ヘッダーで`User-Agent`や`Authorization`を指定していないか?意図しない条件分岐でキャッシュが分離されている可能性がある。
—
最後に:ネットワークは「信頼」の上に成り立つ
`Cache-Control`は、サーバーとクライアントの間の「信頼の契約書」だ。サーバー側が「これは1時間信用していい」と言い、クライアントが「分かった、1時間は黙って使う」と約束する。この約束が齟齬をきたした時、エンジニアの胃が痛くなるような障害が生まれる。
仕様書を丸暗記する必要はない。だが、パケットがどこで止まり、どのキャッシュ層が何を守っているのかを想像する力こそが、シニアエンジニアとしての唯一無二の武器になる。
次にAPIを設計する際は、ぜひ「このデータはどれだけの鮮度が必要か?」を自問自答してみてほしい。それが、洗練されたWebアプリケーションへの第一歩だ。
コメント