【実務・中級編】 Varyヘッダーによるキャッシュキーの制御とコンテンツネゴシエーション – Web APIアーキテクチャ・データ連携実践ガイド

そのキャッシュ、本当に「正しい相手」に届いていますか?——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 してみてください。そこには、まだ見ぬ最適化のヒントが隠されているはずです。

コメント

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