Redisキャッシュスタンピード対策ガイド:高負荷時にDBを守る設計と実装
Redis を使っていても、ピークトラフィック時に DB が突然落ちることがあります。原因の多くはキャッシュスタンピードです。人気キーの TTL が同時に切れると、大量リクエストが一斉に DB へ流れ、接続プールが飽和します。
「Redis を入れたのに遅い」「ピーク時だけ 500 が増える」という現象は、このパターンで説明できることが非常に多いです。
本記事では、キャッシュスタンピードを実運用で防ぐために、設計原則・実装パターン・監視方法を順に解説します。
1. キャッシュスタンピードとは何か
典型シナリオ:
- 商品ランキング API が
ranking:dailyを Redis に 300 秒で保存 - 300 秒後、人気時間帯にキー期限切れ
- 同時に 1000 リクエストが miss
- 1000 回 DB 集計が走ってレイテンシ急増
このとき Redis 自体は正常でも、背後の DB が壊れます。つまり、問題はキャッシュ障害ではなく「再生成の同時実行制御」です。
2. 防御の基本は三層構え
スタンピード対策は単一施策では不十分です。次の三層を組み合わせると安定します。
- 同時再生成の抑制(singleflight / 分散ロック)
- 期限切れの分散(TTL ジッター)
- 期限切れ後の挙動制御(stale-while-revalidate)
3. パターン1: singleflight で同時再生成を止める
同一キーの miss が同時発生しても、1 リクエストだけ再生成し、他は待つ設計です。
TypeScript 例:
|
|
単一プロセスではこれで十分ですが、複数インスタンス構成では分散ロックも必要です。
4. パターン2: 分散ロック(SET NX EX)
複数 Pod で同時 miss が起きる場合、Redis ロックで再生成担当を 1 つに制限します。
|
|
注意点は、ロック TTL が短すぎると再生成中に失効し、二重計算になることです。処理時間の p99 を見て余裕を持って設定してください。
5. パターン3: TTL ジッターで期限切れを分散
同系統キーが同時に切れると負荷波形が尖ります。TTL にランダム幅を持たせるだけでかなり改善します。
|
|
300 秒固定より、300〜390 秒の分布にすると、同時失効が目に見えて減ります。
6. パターン4: stale-while-revalidate
高可用性が重要な API では、期限切れ直後でも古い値を短時間返しつつ、裏で更新する手法が有効です。
データ構造例:
|
|
now < softExpireAt: 新鮮データsoftExpireAt <= now < hardExpireAt: 古い値を返しつつ非同期更新hardExpireAt <= now: 同期再生成
この方式はピーク帯の体感性能を維持しやすく、DB 保護にも強いです。
7. 失敗時フォールバックを設計する
キャッシュ再生成が失敗したときの動作を明確にしておくと、障害時の揺れが減ります。
推奨順序:
- stale データがあれば返す
- stale もなければ軽量な代替レスポンス
- 最後に明示エラー(再試行可能)
「毎回 DB にフォールバック」は危険です。障害時に DB をさらに追い込むため、回路遮断(circuit breaker)とセットで設計すべきです。
8. 監視で見るべき指標
対策の効果はメトリクスで評価します。
- cache hit ratio
- key 単位の miss burst(短時間 miss 件数)
- 再生成処理の同時実行数
- DB クエリ QPS と接続待ち時間
Prometheus を使う場合、以下のようなカウンタを実装すると分析しやすいです。
cache_requests_total{key_group, result="hit|miss|stale"}cache_rebuild_total{key_group, result="ok|error"}cache_lock_contention_total{key_group}
9. 導入手順(既存システム向け)
Step 1: 熱いキーを特定
アクセス上位 20 キーを抽出し、まずそこだけ対策します。
Step 2: singleflight + ジッター導入
実装コストが低く、効果が高い組み合わせです。
Step 3: 分散ロック導入
複数インスタンス環境で必須。ロック競合率も計測する。
Step 4: stale-while-revalidate 追加
高トラフィック API へ段階適用。UX と DB 安定性が両立しやすい。
10. よくある失敗例
失敗1: TTL を長くしてごまかす
データ鮮度要件を満たせず、別の問題が出ます。期限延長は応急処置に留める。
失敗2: 分散ロックだけで安心する
ロックが取れない側の待機戦略がないと、スパイクは残ります。待機・再取得・stale 応答まで設計が必要です。
失敗3: miss 率しか見ない
miss が少なくても、特定キーへの burst が強ければ障害は起きます。キーグループ単位で観測してください。
11. 実運用チェックリスト
- 熱いキーを定義し、キーグループで計測している
- singleflight もしくは同等の同時実行制御がある
- TTL にジッターを導入している
- 分散ロックの TTL が処理時間 p99 を上回る
- stale-while-revalidate の返却条件が明確
- 障害時フォールバック(回路遮断含む)がある
まとめ
Redis キャッシュスタンピード対策は、キャッシュを置くことではなく「miss 後の世界を設計する」ことです。
- 同時再生成を止める
- 失効タイミングを分散する
- 期限切れ直後の応答を制御する
この3点を実装すれば、ピーク時の DB 崩壊リスクは大幅に下げられます。まずはアクセス上位キーから段階導入し、メトリクスで効果を確認してください。仕組みとして回り始めると、トラフィック増加に対する耐性が目に見えて改善します。
12. キャッシュキー設計の実務ポイント
スタンピード対策では、アルゴリズム以前にキー設計が重要です。user:123:timeline のような粒度が粗すぎるキーは、人気ユーザーにアクセスが集中したとき一気にホットスポットになります。可能なら page や segment を分割し、巨大レスポンスを小さく分けると miss 時の再生成コストを抑えられます。
また、キーの命名規約をチームで統一しておくと観測しやすくなります。service:domain:resource:variant 形式で揃えれば、メトリクス集計時に key_group を自動分類しやすく、どの領域で burst が起きているかを短時間で判断できます。運用性を上げるキー設計は、性能改善と同じくらい価値があります。