キャッシング戦略

英語名 Caching Strategy
読み方 キャッシング ストラテジー
難易度
所要時間 設計に1〜3日、運用は継続的
提唱者 コンピュータサイエンスの基礎概念
目次

ひとことで言うと
#

よく使うデータをDBより速い場所(メモリ等)に一時保存し、同じデータへの繰り返しアクセスを高速化する技術。適切に使えばレスポンスタイムが数十倍改善し、DBの負荷も劇的に下がる。

押さえておきたい用語
#

押さえておきたい用語
Cache-Aside
アプリがキャッシュを確認し、なければDBから取得してキャッシュに保存する最も一般的なパターン。
TTL(Time To Live)
キャッシュの有効期限。設定した時間が経過すると自動的にデータが破棄される。
キャッシュヒット率
リクエストのうちキャッシュから応答できた割合。90%以上が理想。低いとキャッシュ導入の効果が薄い。
Thundering Herd(雷鳴問題)
キャッシュ期限切れ直後に大量のリクエストが同時にDBに殺到する現象。ロック機構や事前更新で防ぐ。
キャッシュ無効化
データが更新された際に古いキャッシュを削除または更新する処理。キャッシュ設計で最も難しい部分。

キャッシング戦略の全体像
#

対象選定→パターン選択→TTL設計→レイヤー構成の4ステップ
対象選定読み取り頻度が高く書き込み頻度が低いデータを選ぶパターン選択Cache-Aside /Write-Through /Write-Behind から選ぶTTL・無効化設計有効期限と更新時のキャッシュ無効化戦略を決めるレイヤー構成ブラウザ→CDN→アプリ→DBの多層キャッシュを構成速度とデータ鮮度のバランスを設計する
キャッシング戦略の進め方フロー
1
対象選定
高頻度読取データを特定
2
パターン選択
Cache-Asideから始める
3
TTL設計
有効期限と無効化を決定
4
レイヤー構成
多層キャッシュで効果最大化

こんな悩みに効く
#

  • ページの読み込みが遅く、同じクエリが何度もDBに発行されている
  • トラフィックが増えるとDBがボトルネックになりスローダウンする
  • APIのレスポンスタイムを改善したいが、DBのチューニングだけでは限界がある

基本の使い方
#

ステップ1: キャッシュする対象を選定する

すべてをキャッシュするのではなく、効果が高いものを選ぶ

  • 読み取り頻度が高く、書き込み頻度が低いデータが最適
  • 例: 商品マスタ、カテゴリ一覧、ユーザープロフィール、設定値
  • 計算コストが高い集計結果(ランキング、レコメンドなど)も効果的

ポイント: 「このデータが数秒古くても問題ないか?」が判断基準。リアルタイム性が必要なデータはキャッシュに向かない。

ステップ2: キャッシュパターンを選ぶ

用途に応じてキャッシュの読み書きパターンを決める

  • Cache-Aside: アプリがキャッシュを確認→なければDBから取得→キャッシュに保存。最も一般的
  • Write-Through: 書き込み時にDBとキャッシュを同時に更新
  • Write-Behind: 書き込みをキャッシュに行い、非同期でDBに反映
  • Read-Through: キャッシュミス時にキャッシュ自身がDBからデータを取得

ポイント: 迷ったらCache-Asideから始める。シンプルで理解しやすい。

ステップ3: TTL(有効期限)と無効化戦略を決める

キャッシュの鮮度をどう保つかを設計する

  • TTL(Time To Live): 一定時間後に自動的に期限切れにする
  • イベント駆動の無効化: データ更新時にキャッシュを明示的に削除する
  • タグベースの無効化: カテゴリやユーザーIDでグループ化して一括削除

ポイント: TTLは短すぎるとキャッシュの効果が薄く、長すぎると古いデータが表示される。最初は5分で試す。

ステップ4: キャッシュのレイヤーを構成する

複数のレイヤーでキャッシュを構成し、効果を最大化する

  • ブラウザキャッシュ: HTTP Cache-Controlヘッダーで制御
  • CDN: 静的アセットやAPIレスポンスをエッジでキャッシュ
  • アプリケーションキャッシュ: Redis、Memcachedでアプリ層にキャッシュ
  • DBキャッシュ: クエリキャッシュ、マテリアライズドビュー

ポイント: ユーザーに近い場所でキャッシュするほど効果が高い。

具体例
#

例1:ニュースサイトのトップページをキャッシュでレスポンスタイム800msから20msに改善する

問題: トップページのリクエストごとに「人気記事ランキング(10件)」「カテゴリ一覧」「最新記事(20件)」を3つのクエリでDBから取得。レスポンスタイム800ms。1日あたり50万PV。

キャッシュ設計:

  • 人気記事ランキング: Redis にキャッシュ。TTL 10分。10分に1回だけ集計クエリが走る
  • カテゴリ一覧: Redis にキャッシュ。TTL 1時間。カテゴリ変更時にイベント駆動で無効化
  • 最新記事: Redis にキャッシュ。TTL 1分。新記事公開時にキャッシュ無効化
  • ページ全体のHTML: CDN にキャッシュ。TTL 30秒。ログインユーザーはCDNを通さない

結果: レスポンスタイムがCDNヒット時20ms、Redisヒット時50msに改善。DB負荷が1/100以下になり、DBインスタンスのスケールダウンで月額5万円のコスト削減も実現した。

例2:ECサイトが商品検索APIにキャッシュを導入しブラックフライデーの10倍トラフィックを捌く

状況: 通常時のAPIリクエスト: 毎秒500。ブラックフライデーは毎秒5,000に急増。DBの処理能力は毎秒800クエリが上限。

キャッシュ戦略:

  • 商品検索結果: Redis にCache-Aside。TTL 30秒。人気キーワード上位100件は事前ウォーミング
  • 商品詳細: Redis にCache-Aside。TTL 5分。価格変更時にイベント駆動で無効化
  • 在庫数: キャッシュしない(リアルタイム性が必須)
  • Thundering Herd対策: Redisのロック機構で同一キーへの並列DB問い合わせを1つに制限

結果: キャッシュヒット率94%を達成。DB負荷は毎秒5,000リクエスト中300のみDBに到達。ブラックフライデー当日のレスポンスタイムp99が380msで安定し、サービスダウンゼロだった。

例3:SaaSのダッシュボードが重い集計クエリをキャッシュで10秒から0.5秒に高速化する

問題: 管理ダッシュボードに「過去30日の売上推移」「顧客数推移」「解約率」の3つのグラフ。各クエリに3〜4秒かかり、ページ表示に10秒以上。ユーザー200名が毎朝9:00〜9:30にアクセスし、DBのCPU使用率が90%に。

キャッシュ設計:

  • 集計結果をRedisにキャッシュ。TTL 15分
  • 毎時0分にバッチジョブで集計を実行し、キャッシュを事前更新(Read-Throughに近い運用)
  • ダッシュボードに「データ更新時刻」を表示し、ユーザーに鮮度を明示

結果: ダッシュボード表示が10秒→0.5秒に改善。朝のDBのCPU使用率ピークが90%→25%に低下。ユーザーから「ダッシュボードが速くなった」の声が15件寄せられた。

やりがちな失敗パターン
#

  1. キャッシュの無効化を設計しない — 古いデータが表示され続けてユーザーからクレームが来る。「データが更新されたらキャッシュをどう消すか」を必ず設計する
  2. キャッシュに依存しすぎる — Redisが落ちるとシステム全体が停止する。キャッシュがなくても動く(ただし遅い)設計にする。サーキットブレーカーも検討
  3. Thundering Herd(雷鳴問題)を考慮しない — キャッシュの期限切れ直後に大量のリクエストがDBに殺到する。ロック機構やキャッシュの事前更新で対策する
  4. キャッシュキーの設計が不適切 — ユーザーIDやロケールを含めずに全ユーザー共通のキーにすると、他人のデータが表示される重大バグに。キーにコンテキスト情報を必ず含める

まとめ
#

キャッシングは「正しく使えば劇的に効果がある」が 「間違えるとデータ不整合の温床になる」 両刃の剣。キャッシュ対象の選定、パターンの選択、無効化戦略の3つを意識して設計することが重要。まずはCache-Asideパターン + TTLの組み合わせで、最もアクセスが多いエンドポイントから導入してみよう。