HTTP/1.1の「見えざる罠」:Varyヘッダーとキャッシュ汚染の深淵
Webインフラの現場で、一度は必ずと言っていいほど頭を抱える問題がある。「キャッシュが効いていない(または、効きすぎている)」という現象だ。
ブラウザのデベロッパーツールで`X-Cache: HIT`を確認して満足している君、ちょっと待ってほしい。そのキャッシュ、「誰に対して」HITしているものだろうか?
今日はHTTP/1.1の屋台骨であり、同時にエンジニアを奈落へ突き落とすこともある`Vary`ヘッダーと、それに付随する「キャッシュ汚染」について、現場の視点から紐解いていこう。
—
1. Varyヘッダーとは何か? ― 「条件付きの約束」
HTTP/1.1において、キャッシュサーバー(CDNやブラウザのキャッシュ)は、通常「URL」をキャッシュのキー(識別子)にする。しかし、Web APIやモダンなWebサイトでは、URLが同じでも「リクエストヘッダーの内容によってレスポンスを出し分けたい」場面が多々ある。
ここで登場するのが `Vary` ヘッダーだ。これはオリジンサーバーからキャッシュサーバーに対し、こう伝えている。
> 「このレスポンスをキャッシュする際、URLだけでなく、指定したリクエストヘッダーの値もキーの一部として見なせ」
代表的な利用例
- `Vary: Accept-Encoding`: ブラウザが `gzip` に対応しているか(`Accept-Encoding: gzip`)でレスポンスを出し分ける。
- `Vary: Authorization`: 認証情報によって表示内容を変える。
- `Vary: User-Agent`: PCとスマホでHTMLを出し分ける(昨今では推奨されないが、レガシーな環境では未だに現役だ)。
—
2. なぜ「キャッシュ汚染(Cache Poisoning)」が起きるのか
キャッシュ汚染の恐ろしさは、「間違ったキャッシュが正当なものとして世界中に配布されてしまう」点にある。
例えば、`Vary: User-Agent` を設定しているとしよう。キャッシュサーバーは `User-Agent` ごとにキャッシュを保持する。もし `User-Agent` のバリエーションが無限に増えたらどうなるか?
1. キャッシュサーバーのストレージが圧迫される。
2. キャッシュサーバーが「これ以上細かくキャッシュできない」と判断し、本来分けるべきものを同一視し始める。
3. 結果、スマホ向けのレスポンスがPCユーザーに返されたり、最悪の場合、Aさんの個人情報が入ったレスポンスがBさんに返される(情報漏洩)。
これがキャッシュ汚染のメカニズムだ。特に `Vary: `(ワイルドカード)を安易に指定すると、キャッシュサーバーは「このリクエストは多種多様な条件で変化するから、キャッシュしてはいけない」と判断し、キャッシュ効率がゼロになる。これもまた、パフォーマンス観点では大きな痛手となる。
—
3. 実践:デバッグと検証方法
理屈はわかっても、実務でどう確認すべきか。まずは `curl` でヘッダーを確認するのが鉄則だ。
curlによる確認
-Iでヘッダーのみ取得
-Hでリクエストヘッダー(例: 言語設定)を細工してレスポンスが変わるか検証
curl -I -H “Accept-Language: ja” https://api.example.com/data
curl -I -H “Accept-Language: en” https://api.example.com/data
もし、`Accept-Language` を変えているのに、キャッシュサーバーから返ってくる `X-Cache` が両方とも `HIT` で、かつレスポンスボディが同じであれば、`Vary` の設定が正しく機能していないか、キャッシュサーバーが `Vary` を無視している可能性が高い。
Nginxでの設定例
もしあなたがインフラ層でこの制御を行うなら、以下のように設定するはずだ。
nginx.conf
location /api/ {
# 適切にVaryを指定することで、キャッシュの粒度を制御する
add_header Vary “Accept-Encoding, Accept-Language”;
# 注意: Varyにあまりにユニークな値(例: Cookie)を含めると
# キャッシュ率が激減するので注意が必要
}
—
4. シニアエンジニアからの警告:設計の指針
トラブルを防ぐために、以下の3点を徹底してほしい。
1. Varyは最小限に:
`Vary: User-Agent` は避けろ。昨今の開発では、レスポンスの出し分けはクライアントサイドで行うか、URLそのものを変える(`api.example.com/v1/data?lang=ja` のように)方が、キャッシュ効率もデバッグのしやすさも圧倒的に上だ。
2. CDNの特性を把握せよ:
CloudFrontやFastly、AkamaiなどのCDNは、`Vary` の挙動がそれぞれ微妙に異なる。特に「`Vary` ヘッダーを無視する設定」が有効になっていると、どんなにオリジンで工夫してもキャッシュ汚染は防げない。必ずCDN側の仕様を確認すること。
3. 「キャッシュさせない」という選択肢:
個人情報や機密性の高いAPIレスポンスには、`Cache-Control: private, no-store` を付与し、そもそもキャッシュサーバーに保存させないという勇気を持つこと。
最後に
ネットワークの挙動は、パケットの往来という「物理」と、プロトコル仕様という「論理」の交差点にある。Varyヘッダーは、便利だが諸刃の剣だ。
「なぜか一部のユーザーだけ表示がおかしい」という問い合わせが来たら、まずは `Vary` を疑え。それが、現場で生き残るための、最初の第一歩だ。
コメント