そのキャッシュ、本当に「正しい相手」に届いていますか?——HTTP/1.1 `Vary`ヘッダーで制御するコンテンツ配信の深淵
ネットワークエンジニアとして現場を歩いていると、こんな悲鳴をよく耳にします。「CDNでキャッシュしているはずなのに、なぜかスマホで見るとPC用の画面が出る」「ログイン後のマイページが表示されてしまった」。
これらはすべて、HTTPのキャッシュ機構に対する「甘え」が引き起こす悲劇です。今日は、Web API設計やインフラ運用において避けては通れない、しかし意外と正しく理解されていない`Vary`ヘッダーについて、現場の知見を交えて解説しましょう。
—
1. なぜ「Vary」が必要なのか?
HTTP/1.1において、キャッシュサーバーやブラウザは「URL」をキーにしてコンテンツを保存します。しかし、現代のWebは同じURLでも「誰が、どのデバイスで、どんな言語で」リクエストしたかによって、返す中身が変わることがあります。
例えば、レスポンスヘッダーに`Vary: User-Agent`と指定したとしましょう。これはキャッシュサーバーに対して、「このURLは『User-Agent』ヘッダーの中身によって中身が変わるから、同じURLでもヘッダーが違えば別物としてキャッシュしてくれ」という命令を出しているのと同じです。
もしこれを怠ると、最初にPCからアクセスした際のキャッシュが保存され、次にスマホでアクセスしたユーザーに「PC版のレイアウト」がそのまま配送されるという事故が起きます。
—
2. 通信フローで見る「Vary」の挙動
Varyの役割をシーケンスで追うと、その重要性がよくわかります。
1. Client → `GET /api/data` (`Accept-Encoding: gzip`) → CDN/Cache
2. CDN/Cache → (キャッシュ未ヒット) → Origin Server
3. Origin Server → `Vary: Accept-Encoding`, `Content-Encoding: gzip` → CDN/Cache
4. CDN/Cache → (gzip版を保存) → Client
ここで、別のクライアントが `Accept-Encoding: br` (Brotli) でリクエストを送った場合、CDNは「Varyに書かれているヘッダーが前回と違うから、キャッシュは使わずにOriginへ聞きに行こう」と判断します。これがVaryの正しい仕事です。
—
3. 実践:Varyヘッダーの適切な設定
Nginxでの設定例
リクエストに応じて動的にキャッシュを振り分ける場合、Nginxの設定では以下のように記述します。
location /api/ {
# 圧縮方式ごとにキャッシュを分けるための指定
add_header Vary “Accept-Encoding, Authorization”;
# 注意: 必要以上にVaryを増やすとキャッシュ効率が劇的に下がるため、
# 本当に必要なヘッダーのみを指定すること。
proxy_pass http://backend_server;
}
Python (Flask) での指定例
アプリケーションレイヤーで制御する場合、以下のようにレスポンスオブジェクトを操作します。
from flask import Flask, make_response
app = Flask(__name__)
@app.route(‘/api/user-profile’)
def profile():
response = make_response(“ユーザーデータ”)
# 認証トークンやリクエストヘッダーに応じて出し分けることを明示
response.headers[‘Vary’] = ‘Authorization, Accept-Language’
return response
—
4. 現場で陥る「キャッシュ汚染」の罠と対策
シニアエンジニアとして、後輩たちに必ず伝えている「Varyの鉄則」が3つあります。
1. 「Vary: 」は絶対に使うな
仕様上、`Vary: ` と書くと「あらゆるヘッダーを考慮せよ」という意味になりますが、これは実質的にキャッシュを無効化するのと同義です。CDN上のキャッシュがすべて無効になるため、オリジンサーバーに負荷が集中します。絶対に避けてください。
2. カーディナリティ(値の多様性)に注意
`Vary: User-Agent` は非常に危険です。ブラウザのバージョンやOSのパッチレベルによってUser-Agentは無限に変化するため、キャッシュサーバー側で「ヒット率」が極端に低下します。
- 対策: `User-Agent` で出し分けたい場合は、バックエンドで判定して `X-Device-Type: mobile` のような独自のヘッダーを付与し、`Vary: X-Device-Type` とするのが定石です。
3. デバッグの秘訣:レスポンスを確認せよ
トラブルが起きたら、まず `curl` でヘッダーを覗き込みましょう。
デバッグ用コマンド
-I: レスポンスヘッダーのみ取得
-H: 特定のヘッダーを付与してリクエスト
curl -I -H “Accept-Encoding: gzip” https://api.example.com/data
ここで `Vary` ヘッダーが意図通りに含まれているか、キャッシュサーバー(CDN)を介した場合に `X-Cache: HIT` となっているかを確認します。
—
最後に:ネットワークは「期待」ではなく「設定」で動く
ネットワークの世界では、「たぶんこう動くだろう」という推測は、数ヶ月後に必ず自分を苦しめる技術的負債に変わります。`Vary` ヘッダーは、クライアントとサーバーの間の「約束事」です。
この約束事を正しく書くことで、キャッシュ効率は最大化され、ユーザーには爆速のレスポンスを提供できます。次にサーバーの設定ファイルを開くときは、ぜひ「このリクエストは、何によって変化するのか?」を自問自答してみてください。それが、インフラエンジニアとしての腕の見せ所です。
コメント