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パスだけ
- 公式: 既定のキーに含まれるのは「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が「同じ」になる
- 公式: 「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 で同一。
「同じ」とはバイト単位の一致ではなく、「キーが一致すること」。キーを最小にするとは、この同値の網を広く張ること——多くの見かけ上異なるリクエストを、意図的に一つの答えへ束ねることだ。
④ キーのカスタマイズ — 「応答を変える値」だけを足す
- 公式: 「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)
- 公式: 「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パスという正規形に載せ替える。
⑥ 「何を見るか」と「何で区別するか」は別の設定 — オリジンリクエストポリシー
- 公式: 「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.」
「オリジンに何を見せるか(転送)」と「キャッシュが何で区別するか(キー)」を混同すると、必要な値を送るためだけにキーが太りヒット率が落ちる。分離すればどちらも最適化できる。
⑦ 締め — 設計は「同じとは何か」を決めることから始まる
- DynamoDB編のパーティションキーが「何をキーにするか」を問うたのと同じ問いが、CDNでは「何をキーに入れ、何を入れないか」として戻ってくる。
ヒット率のチューニングとは、ストレージの話ではない。キャッシュキーという「同値関係の定義」を業務上の真実に一致させる作業だ。だから設計は保存より先に、「同じとは何か」を決めることから始まる。