【実務・中級編】HTTP/1.1のVaryヘッダーによるキャッシュの制御 – HTTPプロトコル・通信規格実践ガイド

そのキャッシュ、本当に「正しい相手」に届いていますか?——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` ヘッダーは、クライアントとサーバーの間の「約束事」です。

この約束事を正しく書くことで、キャッシュ効率は最大化され、ユーザーには爆速のレスポンスを提供できます。次にサーバーの設定ファイルを開くときは、ぜひ「このリクエストは、何によって変化するのか?」を自問自答してみてください。それが、インフラエンジニアとしての腕の見せ所です。

コメント

タイトルとURLをコピーしました