HTTP Varyとキャッシュキー: 再利用の条件、Vary: *、キャッシュの分割

Varyはキャッシュキーにリクエストヘッダーを加えます。RFC 9110とRFC 9111をもとに、ヘッダー値が一致するとみなされる条件、Vary: *で毎回再検証が必要になる理由、コンテンツネゴシエーションがキャッシュを細かく分ける仕組みを整理します。

httpcachevarycontent-negotiation

同じURLなのに、あるリクエストはキャッシュからすぐ返り、別のリクエストは毎回オリジンサーバーまで届く。レスポンスにVaryが付いているなら、原因はたいていVaryが指すリクエストヘッダーの値です。この記事では、HTTPのセマンティクスを定めるRFC 9110(2022年6月)と、HTTPキャッシュを定めるRFC 9111(2022年6月)の該当節をもとに、Varyがキャッシュの再利用をどう変えるかを説明します。特定のCDNやブラウザーの実装は扱いません。

no-cacheやno-storeのように、レスポンスを保存して再利用してよいかを決めるディレクティブはCache-Controlのno-cacheとno-storeの違いで扱いました。Varyが答えるのは別の問いです。保存済みのレスポンスがあるとき、それを今届いたリクエストに使ってよいか。

キャッシュキーの出発点: メソッドとURI

RFC 9111の2節は、キャッシュキーをキャッシュがレスポンスを選ぶために使う情報と定義し、最低限の構成要素としてリクエストメソッドとターゲットURIを挙げています。同じ節は、現在広く使われているキャッシュの多くはGETレスポンスしか保存しないため、実質的にURIだけをキーにしているとも述べています。

困るのは、同じURIがリクエストによって異なる内容を返す場合です。/indexがAccept-Encoding: gzipを送ったクライアントにはgzipで圧縮した本文を返し、それ以外のクライアントには元の本文を返すとしたら、URIだけでは二つのレスポンスを区別できません。RFC 9111の2節によると、このときキャッシュは元のリクエストのヘッダーの一部をキャッシュキーに含めてレスポンスを区別し、どのヘッダーを含めるかはレスポンスのVaryが示します。

Varyが伝えること

RFC 9110の12.5.5節によると、VaryはメソッドとターゲットURI以外に、リクエストのどの部分がこのレスポンスの内容の選択に影響した可能性があるかを示します。値は*か、リクエストヘッダー名のリストです。リストに挙がったヘッダーをRFCはselecting header fieldsと呼びます。

HTTP/1.1 200 OK
Vary: accept-encoding, accept-language

このレスポンスは、オリジンサーバーがリクエストのAccept-EncodingとAccept-Languageの値、またはそれらがないことを見て内容を選んだ可能性がある、という意味です。ヘッダー名は大文字と小文字を区別しないため(RFC 9110 5.1節)、accept-encodingとAccept-Encodingは同じ名前です。

12.5.5節は、ヘッダー名のリストからなるVaryの目的を二つにまとめています。

  1. キャッシュに対して: 後のリクエストがリストのヘッダーについて元のリクエストと同じ値を持たない限り、オリジンサーバーが再利用を検証した場合を除いて、このレスポンスをそのリクエストに使ってはなりません(MUST NOT)。RFCの言い方では、Varyはキャッシュキーを広げます。
  2. ユーザーエージェントに対して: このレスポンスはコンテンツネゴシエーションを経ており、リストのヘッダーに別の値を送れば別の表現が返る可能性があります。

二つのリクエストのヘッダーが「同じ」とされる基準

RFC 9111の4.1節は、保存済みレスポンスにVaryがある場合、そのVaryが指すリクエストヘッダーがすべて元のリクエストと一致しない限り、再検証なしに再利用してはならないと定めています(MUST NOT … unless)。一致の判定は、文字列をそのまま比べるより少し広く、一方のリクエストのヘッダーを次の変換だけでもう一方と同じにできれば一致とみなします。

  • そのヘッダーの構文が許す位置で空白を加える、または取り除く
  • 同じ名前の複数のヘッダー行を一つにまとめる
  • ヘッダーの仕様上、意味が同じだと分かっている方法で両方を正規化する。たとえば順序に意味のない値の並べ替えや、大文字小文字を区別しない値の大文字小文字の統一

もう一つ規則があります。正規化のあとでもあるヘッダーがリクエストにない場合、そのリクエストは同じヘッダーがない別のリクエストとしか一致しません。

Vary: Accept-Language付きのレスポンスが、Accept-Language: ko-KR,ko;q=0.9を送ったリクエストから保存されたとします。次の表は上の規則をそのまま当てはめた結果で、特定のキャッシュ製品で観測したものではありません。

後のリクエストのAccept-Language 再検証なしの再利用 理由
ko-KR,ko;q=0.9 可 値が同じ
ko-KR, ko;q=0.9 可 リスト区切りの後の空白だけが異なる
(ヘッダーなし) 不可 ないヘッダーはないヘッダーとしか一致しない
ko 不可 サーバーが同じ韓国語ページを返すとしても値が異なる
en-US,en;q=0.9 不可 値が異なる

空白の違いはRFCが明示している変換なので、一致とみなせます。一方、koとko;q=1のように、品質値の既定値(RFC 9110 12.4.2節でqがなければ1)を考えると意味が同じになる場合は、三つ目の変換、つまり意味が同じだと分かっている正規化に当たる可能性があります。RFCはこの正規化を許しているだけなので、実際に一致として扱うかどうかはキャッシュの実装次第です。

一致するレスポンスが複数あれば、キャッシュは一つを選ぶ必要があります。4.1節によると、Accept系の品質値のように選好の順位を付ける方法があるヘッダーならその方法を使ってよく(MAY)、そうした方法がないか選好が同じなら、Dateヘッダーで最も新しいレスポンスを選びます。一致するものがなければキャッシュはそのリクエストに応答できず、通常はオリジンサーバーへ転送します。その際、すでに保存しているレスポンスを示す条件付きヘッダーを付けることがあります。

コンテンツネゴシエーションがキャッシュを分ける仕組み

表のkoの行が要点です。一致はリクエストヘッダーの値で判定され、サーバーが実際に返した内容では判定されません。そのため、サーバーが多くのAccept-Languageの値に対してまったく同じ韓国語ページを返していても、クライアントが送る値の組み合わせが多様であれば、同じ内容が別々のキャッシュエントリーとして保存されたり、一致するエントリーが見つからずオリジンへ向かうリクエストが増えたりします。これは4.1節の規則からそのまま導かれる結果です。

RFC 9110もこのコストを認めています。12.1節は、サーバー主導のネゴシエーション(proactive negotiation)の欠点の一つとして、共有キャッシュでのレスポンスの再利用性を下げることを挙げています。同じ節は、サーバーがクライアントのネットワークアドレスやUser-Agentの一部といった暗黙の特性からも表現を選びうると説明しています。Varyに名前を載せるとそのヘッダー値全体が比較対象になるので、値の種類が多いヘッダーほどキャッシュは細かく分かれます。この文は規則から導いた推論であり、分割の程度を測った数値はこの記事の出典にはありません。

だからといって、Varyを外せば済むとは限りません。12.5.5節は、オリジンサーバーが選択ヘッダーに合わせて内容を調整し、そのレスポンスを選択的に再利用させたい場合、キャッシュ可能なレスポンスにVaryを生成すべき(SHOULD)だとしています。同時に、内容の選択による違いがVaryによるキャッシュ性能への影響ほど重要ではないと判断する場合、特にキャッシュディレクティブですでに再利用が制限されている場合は、Varyを省くこともありうると書いています。どちらにしても判断するのはオリジンサーバーです。

Vary: * の意味

*を含むVaryは、リクエストヘッダー以外の要素、場合によってはメッセージの外にあるクライアントのネットワークアドレスなども表現の選択に関わった可能性があることを示します(RFC 9110 12.5.5節)。その結果、受信者はこのレスポンスが後のリクエストに合うかどうかを、オリジンサーバーへリクエストを転送しない限り判断できません。RFC 9110の12.4.3節はこれを、変化の幅が無制限だと表現しています。

キャッシュ側の規則はもっと短く、RFC 9111の4.1節は、Varyに*を含む保存済みレスポンスは常に一致しないと定めています。4.1節の最初の規則と合わせると、このようなレスポンスは再検証なしにはどのリクエストにも再利用できません。保存そのものを禁じる文ではないので、オリジンサーバーが再利用を検証する道は残ります。

細かい点もいくつかあります。

  • リストに*が一つでもあれば、上の意味が適用されます。Vary: Accept-Encoding, *も*として扱われます。RFC 9110の付録B.3は、ほかの値と一緒に使われた*の意味がこの改訂で明確になったと記録しています。
  • プロキシはVaryの値に*を生成してはなりません(MUST NOT)。この制限はオリジンサーバーではなくプロキシにかかっています。

Varyに入れなくてよいもの

RFC 9110の12.5.5節は二つの例を挙げています。

  • Authorization: このヘッダーを含むリクエストへのレスポンスを別のユーザーに再利用することは、ヘッダーの定義(11.6.2節)ですでに禁じられているため、Varyに入れる必要はありません。RFC 9111の3.5節も、共有キャッシュがAuthorizationを含むリクエストへのレスポンスを再利用するには、must-revalidate、public、s-maxageのようなレスポンスディレクティブが必要だと定めています。
  • ネットワーク上の地域: 内容が地域によって選ばれていても、ユーザーが地域を移動してもキャッシュ済みのレスポンスをそのまま使わせたいなら、Varyで示す必要はありません。

逆に、デフォルトのレスポンスでVaryを付け忘れる誤りにもRFCは直接触れています。クライアントが選好を何も送らなかったときに返すデフォルトのレスポンスにVaryがないと、より適したレスポンスが保存されていても、キャッシュがそのデフォルトのレスポンスを選んでしまうことがあります。RFC 9111の4.1節は、同じURIの保存済みレスポンスの一部にVaryがない場合、キャッシュは有効なVaryを持つレスポンスのうち最も新しいものを選ぶべき(SHOULD)だとしています。

再検証と304でのVary

再検証の経路でもVaryが使われます。

  • キャッシュが自ら検証リクエストを作るときは、保存済みレスポンスからメソッド、ターゲットURI、そしてVaryが指すリクエストヘッダーをコピーしてリクエストを合成します(RFC 9111 4.3.1節)。
  • 304(Not Modified)を送るサーバーは、同じリクエストに200を返していたなら含めたはずのContent-Location、Date、ETag、Vary、そしてCache-Control、Expiresを生成しなければなりません(MUST、RFC 9110 15.4.5節)。
  • 304を受け取ったキャッシュは、まずそのリクエストに対して選ばれ得た保存済みレスポンスを絞り込みます。304に強いバリデーター(strong validator)が含まれていれば、その集合の中で同じバリデーターを持つレスポンスのヘッダーを更新します(RFC 9111 4.3.4節)。

ここでETagの設計が効いてきます。オリジンサーバーが強いETagを使うなら、表現データが異なる表現同士で同じETagを共有してはいけません。RFC 9110の8.8.3.3節は、/indexの元の形とgzipでコーディングした形に別々のタグを付ける例でこれを示しています。元の本文のレスポンスはETag: "123-a"、gzipのレスポンスはETag: "123-b"で、どちらもVary: Accept-Encodingを付けています。同じ節は、コンテンツコーディングは表現データの性質なので、エンコードされた表現の強いエンティティタグはエンコードされていない表現のものと異なる必要があると説明しています。

HTTP/1.1 200 OK
ETag: "123-b"
Vary: Accept-Encoding
Content-Encoding: gzip

レスポンスを設計するときの確認事項

  • 内容を選ぶときに実際に読むリクエストヘッダーだけをVaryに入れているか。読まないヘッダーを入れても再利用の機会が減るだけです。
  • ネゴシエーションをするURIなら、デフォルトのレスポンスにも同じVaryを付けているか。
  • Vary: *は本当に必要か。付けた時点で、すべての再利用がオリジンサーバーの検証を通ることになります。
  • 304のレスポンスにも200と同じVaryとETagが出ているか。
  • 強いETagを使うなら、エンコードされた内容や言語ごとの内容が異なる表現に、それぞれ別の値を付けているか。

出典

確認日: 2026-10-10

ブログへ