キャッシュの仕組みと設計完全ガイド:HTTP/CDNからRedisまでのアルゴリズム・運用対策
はじめに — 「キャッシュ」とは何か
キャッシュ(cache)は、データや計算結果を一時的に保管しておき、次回以降のアクセスを高速化するための仕組みです。IT分野ではハードウェア(CPUキャッシュ)からOSやストレージ、ウェブ(ブラウザ・CDN)、アプリケーションレイヤ(RedisやMemcached)に至るまで、多層にわたって幅広く使われています。キャッシュの目的は主に「遅いリソースへのアクセス回数を減らす」ことにあり、これによりレイテンシ低下、スループット向上、コスト削減が期待できます。
キャッシュの基本概念
- ヒット(hit)/ミス(miss): 要求されたデータがキャッシュに存在すればヒット、存在しなければミスです。ヒット率(hit ratio)はキャッシュ効果を示す重要指標です。
- 有効期限(TTL / freshness): キャッシュに保存したデータがどのくらい「新鮮」であるかを示す時間や条件。期限切れ後は再取得が必要です。
- 置換(eviction): キャッシュ容量が足りないとき、どのデータを追い出すかを決めるルール(ポリシー)です。
- 整合性(consistency): キャッシュにあるデータが元のデータ源(オリジン)とどの程度一致しているか。厳密性と性能はトレードオフになります。
主なキャッシュの種類
- CPUキャッシュ — L1/L2/L3など、プロセッサ内の高速メモリ。命令やデータの局所性を活かしてメモリアクセスを高速化します。キャッシュコヒーレンシ(MESIなどのプロトコル)やラインサイズなどの概念が重要です。
- ディスク/ブロックキャッシュ(OSページキャッシュ) — OSがディスクI/Oを減らすために使うメモリ。ファイルシステムの読み書きを高速化します。
- ブラウザキャッシュ — ブラウザが画像・HTML・JavaScript・CSSなどを保存。HTTPヘッダー(Cache-Control、Expires、ETag、Last-Modified、Vary)で制御されます。
- CDN(コンテンツ配信ネットワーク)キャッシュ — エッジサーバで静的コンテンツや動的に生成したキャッシュを保持し、配信元サーバへの負荷と遅延を低減します。
- アプリケーションキャッシュ / 分散キャッシュ — Redis、Memcached 等を用いてデータベース負荷を下げる。セッション情報、頻繁に参照されるクエリ結果、計算結果などを格納します。
キャッシュアルゴリズム(置換とポリシー)
代表的な置換ポリシーには以下があります。
- LRU(Least Recently Used) — 最も長く使われていないものを削除。多くの実装で標準的。
- LFU(Least Frequently Used) — 使用頻度が最も低いものを削除。頻繁に参照されるが最近参照がないデータを残す。
- FIFO(First-In First-Out) — 最初に入ったものから削除。実装は簡単だがアクセスパターンに弱い。
- Random — ランダムに削除。衝突や負荷分散の場面で単純かつ高速。
書き込みに関しては主に書き込みポリシーがあります(write-through、write-back、write-around)。write-throughは書き込み毎に永続化を行い整合性が高い一方で遅く、write-backは遅延書き込みで高速だが障害時のデータ損失リスクが高まります。
ウェブ(HTTP)におけるキャッシュの詳細
HTTPキャッシュはブラウザ、プロキシ、CDNなどで広く用いられ、以下のヘッダーで制御されます。
- Cache-Control: max-age、public/private、no-cache、no-store、must-revalidate、s-maxage 等。最も優先度が高い。
- Expires: 旧式の有効期限ヘッダー(Cache-Controlが優先される)。
- ETag: エンティティタグ。条件付きGET(If-None-Match)でリソースの変更を効率的に検出。
- Last-Modified: 最終更新日時。If-Modified-Since と組み合わせて使う。
- Vary: リクエストのどのヘッダー(例: Accept-Encoding, Cookie)でキャッシュを分けるかを指定し、誤配信を防ぐ。
さらに、近年は Cache-Control の extensions(stale-while-revalidate、stale-if-error)により、コンテンツを一時的に古いまま返しつつバックグラウンドで更新する、といった振る舞いを柔軟に指定できます(サーバ・クライアントやCDNが対応している必要があります)。
分散キャッシュで直面する課題
- キャッシュの無効化(invalidation) — キャッシュの更新や削除を確実に伝えることは難しい。部分的な変更が多いシステムでは特に複雑になります。「キャッシュ無効化は難しい」と言われる所以です。
- キャッシュスタンピー(cache stampede) — 大量のクライアントが同時に期限切れのキーを再生成すると、オリジンに負荷が集中します。対策としてロック(mutex)、リクエスト合流(singleflight)、早期再計算、TTLにジッタを入れる、stale-while-revalidate 等が使われます。
- 整合性(consistency)と遅延(latency)のトレードオフ — 強い整合性を保とうとするとレスポンス遅延が増える。多くのシステムは最終的整合性(eventual consistency)で妥協します。
セキュリティ・プライバシー上の注意点
- 認証が必要なコンテンツを誤って共有キャッシュに置かない(Cache-Control: private を使う)。
- Vary ヘッダーの設定ミスにより、ユーザごとの応答がキャッシュされて他ユーザに返されるリスクがある。
- ETag にユーザ識別情報を含めると情報漏えいにつながる可能性があるため避ける。
- HTTPSでも中間コンポーネントのキャッシュやブラウザの挙動を理解して設計する。
設計と運用のベストプラクティス
- まずはメトリクスを可視化する(ヒット率、ミス率、レイテンシ、オリジン負荷)。改善効果を定量化することが重要です。
- TTLはアクセスパターンに合わせて決める。静的リソースは長め、動的データは短めあるいは条件付きキャッシュを使う。
- キャッシュキー設計を慎重に。URL、クエリパラメータ、ヘッダー(Vary)などで意図せぬキャッシュ分割や共有が起きないようにする。
- 失敗時の挙動(stale-if-error / フォールバック)を設計して可用性を確保する。
- 分散キャッシュではオブジェクトの粒度と更新パターンを考慮し、必要ならデータを分割してキャッシュ効率を上げる。
運用でよくある問題と対処法
- キャッシュが効かない — 原因: 不適切なヘッダー、Cookieや認証でキャッシュ分離、誤ったVary設定。ログとリクエスト/レスポンスヘッダーを確認。
- 古いデータが返る — 原因: TTLが長すぎる、無効化が漏れている。対処: 明示的なキーの削除、バージョニング(URLやキーにバージョン番号を付与)を検討。
- オリジンへの突発的負荷(スタンピー) — 対処: ロック、単一生成(singleflight)、バックグラウンド更新、TTLジッタ。
まとめ
キャッシュはシステム性能とユーザ体験を大幅に改善できる強力な手段ですが、「いつ」「どこで」「どのように」キャッシュするかの設計が重要です。整合性・セキュリティ・無効化の困難さといったコストを理解した上で、適切な戦略(レイヤー分け、TTL管理、ヘッダー設計、監視)を採ることが成功の鍵です。最終的に、測定と継続的なチューニングが高品質なキャッシュ運用には不可欠です。
参考文献
- MDN Web Docs — HTTP キャッシュ
- RFC 7234 — HTTP/1.1 Caching
- Redis ドキュメント — Caching
- Memcached 公式サイト
- Wikipedia — Cache (computing)
- AWS — CDNとキャッシュの基本
- Wikipedia — MESI プロトコル(キャッシュコヒーレンシ)


