第8章 — 分散キャッシュ: 一貫性との戦い
スケールの壁
FreshMarket が成長し、サーバーが3台に増えた。ところが新しい問題が発生した。
「サーバーAでは新価格が表示されるのに、サーバーBでは旧価格のまま……」
分散環境でのキャッシュ一貫性——これがスケールの壁だ。
Consistent Hashing
複数のキャッシュサーバーにキーを分散する手法。サーバーの追加・削除時に最小限のキーだけが再配置される。
Loading diagram...
| 手法 | サーバー追加時の影響 |
|---|---|
| 単純なモジュロ | 全キーの再配置(壊滅的) |
| Consistent Hashing | 1/N のキーだけ再配置 |
Cache Stampede(キャッシュスタンピード)
人気データのキャッシュが期限切れになった瞬間、大量のリクエストが一斉に DB に殺到する問題。
Loading diagram...
対策1: ロックによる排他制御
最初のリクエストだけが DB にアクセスし、残りはロック解放を待つ。
def fetch_with_lock(key)
value = cache.read(key)
return value if value
lock_key = "lock:#{key}"
if cache.write(lock_key, "1", nx: true, ex: 10)
# ロック取得成功 → DB から取得
value = db_fetch(key)
cache.write(key, value, expires_in: 1.hour)
cache.delete(lock_key)
value
else
# ロック取得失敗 → 少し待ってリトライ
sleep(0.1)
fetch_with_lock(key)
end
end対策2: 確率的早期再計算(Probabilistic Early Recomputation)
キャッシュの期限が近づくと、確率的に早めに再計算を開始する。
Loading diagram...
対策3: stale-while-revalidate
古いキャッシュを返しつつ、裏で更新する。HTTP の SWR と同じ概念をアプリケーション層で実装。
Thundering Herd(サンダリングハード)
Stampede と似ているが、こちらはキャッシュサーバー自体がダウンした場合。全リクエストが DB に殺到する。
対策: サーキットブレーカー
Loading diagram...
キャッシュの一貫性パターン
イベント駆動無効化
データ更新時にイベントを発行し、関連するキャッシュを無効化する。
Loading diagram...
バージョニング
キャッシュキーにバージョンを含め、更新時は新バージョンを参照する。古いキャッシュは自然に TTL で消える。
# バージョンベースのキャッシュ
cache_version = product.updated_at.to_i
cache_key = "product:#{id}:v#{cache_version}"分散キャッシュの設計チェックリスト
WARNING
分散キャッシュの設計で忘れてはいけない5つの問い:
- キャッシュサーバーがダウンしたらどうなる?
- 人気データの TTL が同時に切れたらどうなる?
- データ更新後、全サーバーのキャッシュはいつ一致する?
- キャッシュのメモリが不足したらどうなる?
- ネットワーク分断が起きたらどうなる?
マナミの対策
FreshMarket で実装した分散キャッシュ対策:
| 問題 | 対策 |
|---|---|
| Stampede | ロック + SWR |
| Thundering Herd | サーキットブレーカー |
| 一貫性 | イベント駆動無効化 |
| サーバー追加 | Consistent Hashing |
次の章では、キャッシュに潜むセキュリティリスクを学ぶ。