仕組みから学ぶ Web
応用読了目安 13#CDN#キャッシュ#エッジ#オリジン

CDN — 利用者の近くに置いて、オリジンを守る

配信を速くする仕組みであると同時に、オリジンへのアクセスを減らす仕組みでもある。キャッシュキーの設計を誤ると、どちらも機能しない。

この記事の進み方

What — リクエストがどこで折り返すか

CDN が入ると、リクエストはオリジンまで届かずに途中で折り返すことがあります。まず、届く場合と届かない場合を追ってください。

Figureキャッシュミスとキャッシュヒット
利用者東京エッジ東京のCDNオリジン米国のサーバーGET /logo.png(1人目)キャッシュを確認 → 無いオリジンへ取りに行く200 + Cache-Control配信 + キャッシュ保存GET /logo.png(2人目)キャッシュから即返す
1/7
GET /logo.png(1人目)DNS が CDN のアドレスを返すため、利用者は最寄りのエッジに接続する。物理的に近いので往復が速い。

2回目のリクエストはオリジンに届かない。速くなるのは距離が縮むからだけでなく、オリジンの処理時間そのものが消えるから。

キャッシュキーが事故の分かれ目

CDN は「このリクエストとこのリクエストは同じものか」をキャッシュキーで判断します。既定では URL だけ。ここに何を含めるかが設計の中心です。

Figureエッジがリクエストを受けてから返すまでの判断
  1. 1① キャッシュキーを組み立てる既定は URL のみ。ここに何を足すかを決める重い
  2. 2② キャッシュを探すキーが一致するものがあるか軽い
  3. 3③ 期限切れなら再検証ETag / Last-Modified でオリジンに問い合わせる
  4. 4④ オリジンへ取りに行く見つからない・無効なら取得する重い
  5. 5⑤ 保存して返すCache-Control に従って保存する軽い

事故の多くは①で起きる。URL が同じで中身が違うレスポンス(ログイン後の画面、言語別ページ、A/B テスト)を、キーに何も足さずにキャッシュすると、最初の1人の結果が全員に配られる。

1/5
① キャッシュキーを組み立てるCookie・Accept-Language・デバイス種別・クエリパラメータのうち、レスポンスの中身を変えるものだけをキーに足す。足しすぎるとヒット率が落ち、足りないと他人の結果が配られる。

判断はすべてエッジで行われ、オリジンは関与しない。オリジンが影響できるのは、前回返したレスポンスヘッダだけ。

オリジン
本来のサーバー。CDN の後ろにいる、実際にコンテンツを生成する側。
エッジ
利用者に近い場所に置かれた CDN のサーバー。世界中に数百〜数千ある。
キャッシュキー
「このリクエストは前のあれと同じか」を判定する材料。既定は URL。
ヒット率
オリジンに行かずに返せた割合。CDN の効果はほぼこの数字で決まる。
パージ
キャッシュを明示的に捨てる操作。デプロイ直後などに使う。
Cache-Control: private
「特定の利用者向けなので、共有キャッシュは保存するな」という指示。CDN 事故の予防線。
確認 — ここまで読めたか

CDN は既定で何をキャッシュキーにしますか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ「近くに置く」だけで速くなるのか

物理的な距離は、そのまま時間になります。光ファイバー中の信号はおよそ秒速20万km。東京とニューヨークの往復は約2万2千km で、理論値でも往復110ms。実際には経路が直線ではなく、ルーターの処理も入るので150〜200ms かかります。

そして、この往復は1回では済みません。

  • TCP の3ウェイハンドシェイク:1.5往復
  • TLS のハンドシェイク:1往復(TLS 1.3)
  • HTTP のリクエストとレスポンス:1往復

データを1バイトも送る前に、3.5往復=約600ms が消える。 サーバーの処理が 10ms でも、利用者の体感は 600ms 以上になります。

CDN は、この往復の相手を「地球の反対側のオリジン」から「同じ都市のエッジ」に置き換えます。往復が 5ms になれば、3.5往復でも 20ms 弱。サーバーを速くするのではなく、距離を消すというアプローチです。

なぜオリジンが Cache-Control で指示する形なのか

CDN が勝手に判断する設計にすると、キャッシュしてはいけないものをキャッシュしてしまいます。かといって CDN 側の設定だけで決めると、アプリの変更とキャッシュ設定が別の場所にあるため、ずれていきます。

HTTP は「コンテンツを生成した側が、そのコンテンツの扱い方を一緒に返す」という形を選びました。レスポンスと指示が同じ場所にあるので、アプリを変更するときに指示も一緒に変えられます。

パージとバージョニング

キャッシュを更新したいとき、方法は2つあります。

パージ は CDN に「これを捨てろ」と命令する方式。確実ですが、世界中のエッジに反映されるまで時間がかかり、その間は古いものが残ります。また、パージの API 呼び出しが失敗したときに気づきにくい。

バージョニングapp.a3f9c2.js のようにファイル名にハッシュを入れる)は、そもそも同じ URL を再利用しないという方式。中身が変われば URL も変わるので、キャッシュを捨てる必要がありません。古いファイルは誰も参照しなくなるだけです。

静的アセットはバージョニング、HTML はパージまたは短い TTL、というのが定石です。HTML はファイル名を変えられない(URL が仕様)ため、こうなります。

確認 — ここまで読めたか

デプロイのたびにパージを実行する運用と、ファイル名にハッシュを入れる運用。壊れ方が違うのはどこですか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

このサイトで、どのパスをどうキャッシュしますか?

与えられた条件
  • レシピ投稿サイト。月間 2000 万 PV、うち 8 割が未ログインの閲覧
  • /recipes/:id — レシピ詳細。内容は誰が見ても同じ。ただしログイン中はヘッダに「◯◯さん」と表示される
  • /api/recipes/:id/likes — いいね数を返す API。数分の遅れは許容できる
  • /mypage — ログイン必須。その人の投稿一覧
  • /assets/app.[hash].js — ビルドごとにハッシュが変わる
  • テレビで紹介されると特定のレシピに数万 PV/分が集中することがある
この軸で考える
  • · そのレスポンスは「誰が見ても同じ」か、「人によって違う」か
  • · ログイン名の表示だけのために、ページ全体をキャッシュ不可にする価値があるか
  • · アクセス集中時に、オリジンに届いてほしくないのはどれか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「CDN を入れたい」と言う同僚に、速くなる以外に何が起きるかを説明してください

与えられた条件
  • 相手は「配信が速くなる仕組み」としてだけ理解している
  • 対象は会員制サイトで、ログイン後の画面もある
  • 相手はまず全パスをキャッシュ対象にしようとしている

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。