はじめに
コンテンツ配信ネットワーク(CDN)は、現代のWebパフォーマンスの基盤です。世界中に分散されたエッジノードでコンテンツをキャッシュすることで、レイテンシを劇的に削減し、オリジンサーバーの負荷を軽減します。しかし、効果的なキャッシュ戦略はCDNを有効にするだけでは実現できません。パフォーマンス(高いキャッシュヒット率、低レイテンシ)と鮮度(最小限の陳腐化、高速な無効化)の適切なバランスを取るには、意図的な設計と実装が必要です。
本ガイドでは、CDNキャッシュの基礎、Cache-Controlディレクティブ、サロゲートキー、無効化戦略、stale-while-revalidate、マルチレイヤーキャッシュ、そして監視方法までを、実践的なレシピとともに解説します。
CDNキャッシュの基礎
CDNキャッシュは、エッジノード(POP)にレスポンスのコピーを保存し、同じリソースへの後続リクエストをオリジンサーバーに問い合わせずに処理します。キャッシュヒット時は5〜50ミリ秒で応答しますが、キャッシュミス時はオリジンへの到達に100〜500ミリ秒のレイテンシが発生します。
CDNがキャッシュするコンテンツは多岐にわたります:静的アセット(画像、CSS、JavaScript、フォント)、APIレスポンス(JSON、GraphQL)、HTMLページ、ストリーミングコンテンツなどです。キャッシュ階層は通常、エッジノード→リージョナルキャッシュ→オリジンシールド→オリジンサーバーの順になります。
主要CDNプロバイダーの比較は以下の通りです:
| プロバイダー | Cache-Control | サロゲートキー | オリジンシールド | SWR対応 |
|---|---|---|---|---|
| Cloudflare | 完全対応 | Cache-Tag | Argo Tiered Cache | 対応 |
| Fastly | 完全対応 | Surrogate-Key | Fastly Shield | 対応 |
| Akamai | 完全対応 | Cache-Tag | Property Manager | 対応 |
| CloudFront | 完全対応 | — | Origin Shield | 対応 |
Cache-Controlディレクティブの詳細
Cache-Controlヘッダーは、CDNやブラウザにキャッシュ方法を指示する主要な仕組みです。各ディレクティブを理解することで、キャッシュ動作を微調整できます。
max-age=<seconds>— リソースがフレッシュと見なされる期間を指定します。バージョン管理されたアセット(例:app.a1b2c3.js)には、max-age=31536000とimmutableディレクティブを設定します。s-maxage=<seconds>— 共有キャッシュ(CDNやプロキシ)向けにmax-ageを上書きし、CDNとブラウザに異なるTTLを設定できます。publicvsprivate—publicはCDNとブラウザの両方でのキャッシュを許可。privateはブラウザのみに制限します。no-cachevsno-store—no-cacheはキャッシュを許可するが毎回再検証が必要。no-storeはすべてのキャッシュを禁止します。stale-while-revalidate=<seconds>— 古いコンテンツを即座に配信し、バックグラウンドで新しいコンテンツを取得します。immutable— リソースが鮮度期間中に変更されないことをブラウザに伝え、条件付き再検証を排除します。
コンテンツタイプ別の実践的なレシピ:
# バージョン管理された静的アセット — 1年間キャッシュ、再検証スキップ
Cache-Control: public, max-age=31536000, immutable
# APIレスポンス — 1分間フレッシュ、24時間はstale配信
Cache-Control: public, s-maxage=60, stale-while-revalidate=86400
# HTMLページ — 毎回再検証
Cache-Control: public, no-cache
サロゲートキーとターゲット無効化
サロゲートキー(オリジンキャッシュタグ)を使用すると、関連リソースをグループ化し、対象を絞ったキャッシュ無効化が可能です。個別のURLをパージする代わりに、タグ単位でパージできるため、グループ化されたコンテンツに対してはるかに効率的です。
オリジンサーバーがレスポンスにSurrogate-Keyヘッダー(Fastly)またはCache-Tagヘッダー(Cloudflare、Akamai)を含めるだけで実装できます。タグはコンテンツタイプ(articles)、特定エンティティ(article:1234)、コレクション(section:tech)などを表します。
# 記事をタグ付けするレスポンスヘッダー
Cache-Tag: articles, article:1234, section:tech
記事が更新されたら、該当タグのキャッシュをすべてパージします:
# Fastly サロゲートキーパージ
curl -X POST "https://api.fastly.com/service/{service}/purge/article:1234" \
-H "Fastly-Key: $API_KEY"
# Cloudflare タグパージ
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tags":["article:1234"]}'
タグ命名のベストプラクティス:空白を避け、大文字小文字を統一し、プロバイダーの制限内に収めるため1レスポンスあたり256タグ未満に抑えます。
Stale-While-Revalidate
Stale-While-Revalidate(SWR)は、キャッシュミスペナルティを同期待ちから非同期更新に変換するCache-Control拡張です。古いリソースへのリクエストがあった場合、CDNは即座に古いバージョンを配信し、バックグラウンドで新しいバージョンを取得します。
Cache-Control: public, max-age=60, stale-while-revalidate=86400
これにより、リソースは60秒間フレッシュに保たれ、最大24時間は古いコンテンツを配信しながらバックグラウンドで再検証します。ユーザーの待ち時間はゼロになります。SWRはstale-if-errorと組み合わせると効果的です。stale-if-errorはオリジンに到達できない場合のフォールバックとして古いコンテンツを配信します。CDNのダッシュボードでstale-to-fresh比率を監視し、SWRウィンドウを適切に調整しましょう。
マルチレイヤーキャッシュとオリジンシールド
効果的なキャッシュは複数のレイヤーで動作します:
- ブラウザキャッシュ — 最前線。適切な
Cache-Controlヘッダーが同一アセットへの再リクエストを防ぎます。 - CDNエッジキャッシュ — エッジノードが近隣ユーザーにキャッシュコンテンツを配信します。
- CDNオリジンシールド — エッジノードからのリクエストを統合する中間キャッシュで、オリジン負荷を大幅に削減します。
- アプリケーションキャッシュ — RedisやMemcachedによるデータベース結果とレンダリングフラグメントのキャッシュ。
- データベースクエリキャッシュ — 頻繁なクエリ結果のデータベースレベルキャッシュ。
オリジンシールドは特に重要です。複数のエッジノードが同じアセットをキャッシュミスすると、それぞれがオリジンに問い合わせる「暴走する群集問題」が発生します。オリジンシールドはこれらのリクエストを統合し、自身のキャッシュを確認してから一度だけオリジンに問い合わせます。これによりオリジントラフィックを80〜95%削減できます。Cloudflare Argo Tiered Cache、Fastly Shield、CloudFront Origin Shieldのいずれかで設定可能です。
監視とデバッグ
キャッシュ戦略が正しく機能していることを確認するには、以下のメトリクスを監視します:
- キャッシュヒット率 — Cloudflare Analytics、Fastly Observatory、CloudFront Reportsで確認可能。80%以上が良好、95%以上が優秀です。
- CDNレスポンスヘッダー —
CF-Cache-Status(Cloudflare)、X-Cache(Fastly、CloudFront)、Ageでキャッシュの状況を把握できます。 - キャッシュミス分析 — 初回リクエスト、退避、TTL期限切れ、明示的バイパスなど、ミスの原因を特定します。
curlでキャッシュヘッダーをデバッグ:
curl -I https://example.com/assets/app.js -H "Accept-Encoding: gzip"
# CF-Cache-Status: HIT, Age: 12345 などを確認
まとめ
効果的なCDNキャッシュには、適切なTTLの選択、サロゲートキーによるターゲット無効化、stale-while-revalidateによるゼロウェイト更新、そしてオリジンシールドによるマルチレイヤーキャッシュの組み合わせが欠かせません。最も影響力のあるディレクティブはstale-while-revalidateです。これはパフォーマンスと鮮度のトレードオフを「どちらか」から「両方」へと変換します。適切に設計されたサロゲートキーシステムと組み合わせ、オリジンシールドに投資し、キャッシュヒット率を継続的に監視することで、ユーザーはより速い読み込みを、オリジンサーバーは軽減された負荷を享受できます。
