Dev Study
AWSサービスの内部原理 コース

102. キャッシュキーは『同じリクエスト』の定義である — キーを最小にするほどヒット率は上がる

キャッシュ設計は「何を保存するか」より先に「何をもって同じリクエストとみなすか」を決める作業だ、という原理を、CloudFrontのキャッシュキーを図で追っていきます。

① そもそもキャッシュキーとは何か — 「同じ」の判定に使う一意な識別子

ビューワーリクエスト
同じキーのオブジェクトがエッジのキャッシュにあり、まだ有効か?
キャッシュキーキャッシュ内オブジェクトの一意な識別子
ミスならオリジンへリクエスト
キャッシュヒットエッジから即返す
効果: オリジン負荷の低減 / ビューワーのレイテンシ低減
キャッシュミスない / 無効
オリジンから取得ビューワーに返しつつエッジに保存
  • 公式定義: 「The cache key is the unique identifier for an object in the cache.」
  • キャッシュヒットの定義: 「A cache hit occurs when a viewer request generates the same cache key as a prior request, and the object for that cache key is in the edge location's cache and valid.」
  • ヒット率(cache hit ratio)を上げる一つの方法は「include only the minimum necessary values in the cache key(キーに最小限必要な値だけを含める)」こと。

キャッシュとは「過去のリクエストと同じキーが出たら、保存済みの答えを返す」仕組み。だから設計の出発点は保存ではなく、「どのリクエストを同じキーにまとめるか」という同値の定義になる。

② 既定のキーは驚くほど小さい — ドメイン名とURLパスだけ

リクエスト全体GET /content/stories/example-story.html?ref=…
Host / User-Agent / Accept-Language / Cookie / Referer …
残り(クエリ文字列・各ヘッダ・Cookie)は
ドメイン名d111111abcdef8.cloudfront.net
URLパス/content/stories/example-story.html
キーに入らない「同じ」の判定に使われない
  • 公式: 既定のキーに含まれるのは「The domain name of the CloudFront distribution (for example, d111111abcdef8.cloudfront.net)」と「The URL path of the requested object (for example, /content/stories/example-story.html)」の2つ。
  • 公式: 「Other values from the viewer request are not included in the cache key, by default.」
  • 補足(公式Note): OPTIONSリクエストではOPTIONSメソッドがキーに含まれ、OPTIONSへの応答はGET/HEADリクエストへの応答とは別にキャッシュされる。

既定のキーは意図的に小さい。小さいほど多くのリクエストが同じキーに落ち、ヒット率が上がるからだ。DynamoDB編で見た「何をキーにするかがすべてを決める」パーティションキーと同じ問いが、ここでは「何をキーに入れないか」として現れる。

③ 公式の例 — 中身の違う2つのGETが「同じ」になる

リクエスト1?ref=0123abc&split-pages=false
User-Agent: …Firefox/68.0 / Cookie: session_id=01234abcd / Referer: https://news.example.com/
リクエスト2?ref=xyz987&split-pages=true
User-Agent: …Chrome/83… / Cookie: session_id=wxyz9876 / Referer: https://rss.news.example.net/
リクエスト2は
キーは完全に同一(d111111abcdef8.cloudfront.net, /content/stories/example-story.html)
キャッシュヒットオリジンには一切行かない
リクエスト1が保存した答えをそのまま返す
  • 公式: 「This request is for the same object as the previous request, but is different from the previous request. It has a different URL query string, different User-Agent and Referer headers, and a different session_id cookie. However, none of these values are part of the cache key by default, so this second request results in a cache hit.」
  • 公式例で2つのリクエスト間で違うのはクエリ文字列・User-Agent・Referer・session_id Cookieの4点で、Accept-Languageは両方とも en-US,en で同一。

「同じ」とはバイト単位の一致ではなく、「キーが一致すること」。キーを最小にするとは、この同値の網を広く張ること——多くの見かけ上異なるリクエストを、意図的に一つの答えへ束ねることだ。

④ キーのカスタマイズ — 「応答を変える値」だけを足す

判断の分岐点この値は、オリジンの応答を変えるか?
応答を変えない値を誤ってキーに入れると
キーに追加キャッシュポリシーで指定
例: 言語で内容が変わるならAccept-Languageを含める
キーに入れない応答を変えない値
重複キャッシュ同じ中身のコピーが増える
ヒット率が下がる / オリジンへのリクエストが増える
  • 公式: 「You can modify the values (URL query strings, HTTP headers, and cookies) in the cache key by using a cache policy.」
  • 公式: 「When a value in the viewer request determines the response that your origin returns, you should include that value in the cache key. But if you include a value in the cache key that doesn't affect the response that your origin returns, you might end up caching duplicate objects.」原理: 応答を決める入力だけをキーに含める。
  • 補足1: キーはキャッシュポリシーのほか、ビューワーリクエスト時のLambda@Edge関数またはCloudFront Functionでも変更できる。
  • 補足2: ヘッダをキーに含めると、CloudFrontはそのヘッダをキャッシュミス時のオリジンリクエストにも含める(⑥の分離の理解に必要)。

これは関数の純粋性の裏返しだ——「同じ入力なら同じ出力」を成り立たせたいなら、出力を変える入力だけをキーに数えればいい。Lambda編・Step Functions編で見た決定性の議論が、ここでは「キーに何を含めるか」として再登場する。

⑤ 公式の失敗例 — 表記ゆれが別キーを生む(Accept-Language / User-Agent)

意味は全て「英語」en-US,en / en,en-US / en-US, en / en-US
ビューワーが送るAccept-Languageの表記ゆれ4例
対策: この値をキーに入れず、言語をURLパスで分ける
4つの別キー同じ英語ページが何度もキャッシュされる
ヒット率が下がる / オリジンリクエストが増える
URLパスに載せ替え/en-US/content/stories/example-story.html
正規化された1つのキーに収束する
  • 公式: 「viewers might send any of the following values … en-US,en / en,en-US / en-US, en / en-US … the variation can cause CloudFront to cache the same object multiple times. This can reduce cache hits and increase the number of origin requests.」対策も公式どおり: Accept-Languageヘッダをキーに含めず、言語別に異なるURL(例: /en-US/content/stories/example-story.html)を使うようウェブサイト/アプリを構成する。
  • User-Agentは「can have thousands of unique variations(数千の一意な変種があり得る)」ため、原則キーに入れない。入れると変種ごとに別コピーが生まれる。
  • session_idのようなユーザー固有・セッション固有で数千〜数百万リクエストにわたり一意なCookieも同様に不適。
  • こうした高変動値への公式の選択肢は「キーから除外する」または「オブジェクトを非キャッシュ化(non-cacheable)にする」。

「別キーになる表記ゆれ」は、認証暗号編のSigV4正規化(署名の前にリクエストを正規形に整える)と同じ問題への別解だ。あちらは正規化してから比較し、こちらは揺れる値をそもそもキーから外し、URLパスという正規形に載せ替える。

⑥ 「何を見るか」と「何で区別するか」は別の設定 — オリジンリクエストポリシー

分析用ヘッダなど応答は変えないが、オリジンが受け取りたい値
分析(analytics)・テレメトリ用途
両立の結果
キャッシュポリシー何で区別するか = キャッシュキーに入れる
応答を変える値だけをここに
オリジンリクエストポリシー何をオリジンに転送するか = キーには入れない
応答は変えないがオリジンが見たい値をここに
キーは最小のままヒット率を保ちつつ、オリジンは必要な値を受け取れる
  • 公式: 「If your origin website or application needs to receive certain values from viewer requests for analytics, telemetry, or other uses, but these values don't change the object that the origin returns, use an origin request policy to include these values in origin requests but not include them in the cache key.」

「オリジンに何を見せるか(転送)」と「キャッシュが何で区別するか(キー)」を混同すると、必要な値を送るためだけにキーが太りヒット率が落ちる。分離すればどちらも最適化できる。

⑦ 締め — 設計は「同じとは何か」を決めることから始まる

「同じ」の定義キャッシュキー = 同値関係の定義
業務上の真実「どのリクエストは本当に同じ答えを返すべきか」に一致させる
その結果
同値の網が広がる多くのリクエストが1つの答えに束ねられる
ヒット率が上がるオリジン負荷の低減 / レイテンシの低減
  • DynamoDB編のパーティションキーが「何をキーにするか」を問うたのと同じ問いが、CDNでは「何をキーに入れ、何を入れないか」として戻ってくる。

ヒット率のチューニングとは、ストレージの話ではない。キャッシュキーという「同値関係の定義」を業務上の真実に一致させる作業だ。だから設計は保存より先に、「同じとは何か」を決めることから始まる。

公式ドキュメントで詳しく ↗