mybook

第8章 — 分散キャッシュ: 一貫性との戦い

スケールの壁

FreshMarket が成長し、サーバーが3台に増えた。ところが新しい問題が発生した。

「サーバーAでは新価格が表示されるのに、サーバーBでは旧価格のまま……」

分散環境でのキャッシュ一貫性——これがスケールの壁だ。

Consistent Hashing

複数のキャッシュサーバーにキーを分散する手法。サーバーの追加・削除時に最小限のキーだけが再配置される。

Loading diagram...
手法サーバー追加時の影響
単純なモジュロ全キーの再配置(壊滅的)
Consistent Hashing1/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つの問い:

  1. キャッシュサーバーがダウンしたらどうなる?
  2. 人気データの TTL が同時に切れたらどうなる?
  3. データ更新後、全サーバーのキャッシュはいつ一致する?
  4. キャッシュのメモリが不足したらどうなる?
  5. ネットワーク分断が起きたらどうなる?

マナミの対策

FreshMarket で実装した分散キャッシュ対策:

問題対策
Stampedeロック + SWR
Thundering Herdサーキットブレーカー
一貫性イベント駆動無効化
サーバー追加Consistent Hashing

次の章では、キャッシュに潜むセキュリティリスクを学ぶ。