そのキャッシュ、本当に「正しい相手」に届いていますか?——Varyヘッダーで制御するコンテンツネゴシエーションの深淵
ネットワークの現場に長くいると、「なぜか特定のユーザーだけ古い情報が見える」「ログインしていないはずのユーザーに、誰かの個人情報が混じったキャッシュが返された」といった、胃が痛くなるようなトラブルに一度は遭遇するはずです。
その犯人の多くは、CDNやブラウザが「何をもって同一リソースとみなすか」というキャッシュキーの設計ミスにあります。今日は、Web APIのアーキテクチャにおける「隠れた主役」、Varyヘッダーによるキャッシュ制御の極意を、現場の視点から紐解いていきましょう。
—
1. Varyヘッダーとは何か?:RFC 7231が定める「キャッシュの境界線」
HTTPにおいて、キャッシュサーバーは通常「URL」をキーにしてコンテンツを保存します。しかし、現代のAPIは同じURLでも、リクエストヘッダーによって中身を劇的に変えるのが当たり前です。
ここで登場するのが Vary ヘッダーです。これはオリジンサーバー(APIサーバー)からキャッシュサーバーに対し、「このレスポンスをキャッシュする際は、URLだけでなく、このヘッダーの値もキャッシュキーに含めろ」と命令するための指示書です。
もし Vary: Accept-Encoding と指定されていれば、キャッシュサーバーは「gzip圧縮版」と「非圧縮版」を別々に保存し、リクエストヘッダーの Accept-Encoding に応じて出し分けるようになります。これを怠ると、gzip対応していないクライアントに圧縮されたバイナリが届き、画面が文字化けの海と化すわけです。
—
2. 実務で遭遇する「キャッシュの罠」
特に危険なのが Vary: Authorization です。
これを指定すると、認証情報が異なればキャッシュが別々になりますが、あまりに多くのバリエーション(数万人のユーザーIDなど)がキャッシュキーに含まれると、CDNのキャッシュヒット率が劇的に低下します。
よくある失敗パターン:
Vary: *を指定してしまう:これは「すべてのリクエストヘッダーをキャッシュキーにする」という意味になり、実質的にキャッシュを無効化(ミス)させます。パフォーマンスを稼ぐためにキャッシュを入れたはずが、これでは本末転倒です。Varyを指定し忘れる:異なるレスポンスが混ざり合い、致命的な情報漏洩や不整合を引き起こします。
—
3. 実践:Varyを意識したAPI設計と検証
では、どのように実装し、確認すべきか。まずは curl を使って、サーバーが正しく Vary を返しているか確認する癖をつけましょう。
# -I オプションでヘッダーのみを取得
# サーバーがどのような「キャッシュの制約」を設けているかを確認
curl -I https://api.example.com/v1/profile
レスポンスヘッダーに以下の行があるかチェックしてください。
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept-Encoding, Authorization
Cache-Control: public, max-age=3600
Python (Flask) でのヘッダー設定例
Webサーバー側で Vary を明示的に制御するコード例です。
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/api/data')
def get_data():
resp = make_response({"status": "ok", "data": "secret_info"})
# コンテンツネゴシエーションを正しく行うためにVaryを設定
# キャッシュサーバーに「圧縮方式」と「認証状態」を意識させる
resp.headers['Vary'] = 'Accept-Encoding, Authorization'
# CDNがキャッシュ可能であることを明示
resp.headers['Cache-Control'] = 'public, max-age=3600'
return resp
—
4. インフラエンジニアへの提言:CDN側の設定を過信するな
多くのエンジニアが陥るのが、「CDN側でキャッシュ設定をいじればいいや」という考え方です。しかし、キャッシュの正当性は、あくまで 「オリジンサーバーがそのリソースの特性を正しく理解し、正当な Vary を宣言していること」 に依存します。
トラブルシューティングの際、以下のステップを必ず踏んでください。
1. レスポンスヘッダーの確認: curl -v で、期待するヘッダーがレスポンスに含まれているか。
2. キャッシュキーのシミュレーション: 異なるヘッダー(例: Accept-Encoding: gzip と Accept-Encoding: identity)を送った際に、キャッシュサーバーがちゃんと別々のレスポンスを返しているか(X-Cache: MISS / HIT の挙動を確認)。
3. Proxyの挙動: Nginxを使っている場合、proxy_hide_header や proxy_set_header でヘッダーが書き換えられていないか確認してください。
終わりに:美しいAPIは「見えないルール」でできている
Vary ヘッダーは、一見すると地味な仕様です。しかし、これこそが「APIが世界中のあらゆる環境で正しく動く」ための通信の礼儀作法と言えます。
キャッシュ戦略を設計する際は、「このレスポンスは誰にとって同じものか?」を自問自答してみてください。その答えが、そのまま Vary の設定値になります。この小さな一歩が、将来の巨大な障害を未然に防ぐ、強固なインフラ構築への近道となります。
さあ、皆さんも一度、自社のAPIのヘッダーを curl してみてください。そこには、まだ見ぬ最適化のヒントが隠されているはずです。
コメント