目次を表示する

DB 設計の軸 2026 ─ ドメイン駆動と特性駆動の二つの流派を行き来する 19 章

Cache の本質 ─ 揮発性・データ構造・Tier

Cache の本質 ─ 揮発性・データ構造・Tier

Cache」と聞くと「DB の前段」の役割を思い浮かべる人が多い。だが Redis のようなモダンな in-memory store は、**Cache を超えてプリミティブな “データ構造サーバー”**として使われる。これを理解しないと、Cache 設計は単純な “key-value で値を置く” レベルから出られない。

Redis の哲学 ─「データ構造サーバー」

Redis 公式 GitHub の説明:

Redis is the preferred, fastest, and most feature-rich cache, data structure server, document and vector query engine.

作者 Salvatore Sanfilippo(antirez)が一貫して掲げてきたのが「data structure server」というアイデンティティ。KV ストアではなく、サーバー越しに使えるデータ構造のライブラリ ── これが Redis を一般的な “Cache” 製品と分ける。

提供するデータ構造(抜粋)

構造用途
Stringカウンタ、bitmap、bitfield
Listキュー、stack、最近使ったもの
Hashオブジェクト風、フィールド単位の更新
Set集合、unique 制約、tag
Sorted Setleaderboard、ranking、time series 風
Streamappend-only log、consumer group
HyperLogLog概算 cardinality
Bitmap大量 bool フラグ、user activity
Geo緯度経度、近傍検索
Vector類似検索、AI/ML(近年)

カウンタを実装する」「rate limiter を作る」「leaderboard を持つ」── これらは全部プリミティブデータ構造の合成で書ける。これが Redis の強み。

// rate limiter の例
async function checkLimit(userId: string): Promise<boolean> {
  const key = `rate:${userId}:${Math.floor(Date.now() / 60000)}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, 60);
  return count <= 100;
}

3 行で sliding window rate limiter ができる。これは antirez の設計の勝利。

単一スレッド ─ 速さの根拠

Redis は基本的に 単一スレッドで全コマンドを実行する。これは多くの人を驚かせる ── 「マルチコア CPU を使い切れないのでは?」と。

antirez の答え:

In-memory で sub-microsecond の操作をする場合、ロック競合のコストは sequential 実行のコストを超える。

つまり、メモリ上で μs 単位の操作なら、ロックを取る方が遅い。マルチスレッド化すると同期コストでオーバーヘッドが増える。それなら 1 スレッドで全部直列に処理する方が速い

graph TB
  subgraph "クライアント"
    C1[Client 1]
    C2[Client 2]
    C3[Client 3]
  end

  subgraph "Redis サーバー"
    Q[Command Queue]
    E[Single Thread Executor]
    M[(In-Memory Data)]
  end

  C1 --> Q
  C2 --> Q
  C3 --> Q
  Q --> E
  E --> M

  style E fill:#e1f5ff

副次的なメリット:

  • コマンドが atomic で予測可能(ロックフリー)
  • MULTI/EXEC で複数コマンドの atomic 実行が単純に書ける
  • race condition がない(単一スレッドだから)

ただし長時間かかるコマンドKEYS * や巨大な SORT)は他のクライアントを全員待たせる。これは設計編で扱う。

永続化の選択 ─ RDB / AOF / 両方 / なし

Redis は in-memory だが、永続化の選択肢は複数ある。

RDB(Snapshot)

定期的にメモリ全体をディスクに書き出す。

  • pros: 復元が速い、ファイルサイズが小さい
  • cons: 最後の snapshot 以降のデータは失われる(最大数分)

AOF(Append-Only File)

全書き込みコマンドをファイルに append する。

  • pros: データ損失が小さい(最大 1 秒)
  • cons: ファイルが大きくなる、復元が遅い

両方

RDB + AOF。リカバリは AOF を優先。多くの本番環境はこれ。

なし

純粋なキャッシュとして使う場合、永続化なしで運用できる。再起動するとデータは消えるが、それで構わない。この選択ができるのが Redis の強みでもある。

graph LR
  W[書き込み] --> M[Memory]
  M --> RDB[RDB: 定期 snapshot]
  M --> AOF[AOF: 全コマンド append]

  RDB -.復元時優先順位 2.-> R[再起動時の復元]
  AOF -.復元時優先順位 1.-> R

  style M fill:#e1f5ff

判断軸

用途永続化
Cache(DB 前段)なし、または RDB のみ
Session storeRDB + AOF
ジョブキューAOF(fsync every command)
Counter / Rate limitRDB(多少のロスは許容)
Source of truth として使うAOF + replication

Redis を Source of truth にする選択も実在する。Twitter は一時期、ツイートタイムラインを Redis に保存していた。これは「Cache」の枠を超えた使い方だが、Redis の特性を理解すれば成立する。

Cache の Tier 設計

実用上、Cache は単一層ではなく 多段(Tier) で組まれることが多い。

graph TB
  C[Client] --> L1[L1: アプリ内 in-process cache<br/>ns - μs]
  L1 -.miss.-> L2[L2: Redis<br/>μs - ms]
  L2 -.miss.-> L3[L3: PostgreSQL<br/>ms]
  L3 -.miss.-> L4[L4: S3 / Glacier<br/>s - min]

  style L1 fill:#e1ffe1
  style L2 fill:#fff4e1
  style L3 fill:#ffe1e1
  style L4 fill:#ffd1d1

各層のレイテンシオーダーが 1 桁ずつ違う。hot data ほど上の層に。第 3 章のレイテンシバジェット軸と寿命軸が、ここで物理的に Tier として現れる。

Redis Cluster ─ Sharding の流儀

単一ノードの Redis では、メモリと CPU の限界に当たる。それを超えるための Redis Cluster

  • 16384 個のスロットにキーを割り当て
  • 各ノードが一定範囲のスロットを持つ
  • クライアントがスロットを計算してノードに直接アクセス
// キー -> スロット -> ノード
const slot = CRC16(key) % 16384;
const node = clusterMap[slot];

Cluster には重要な制約複数キーの atomic 操作は同一スロット内のキーでしかできない。これに対処するのが hash tag

// 同一スロットに揃える
redis.set('user:{123}:profile', ...);
redis.set('user:{123}:settings', ...);
// → {123} の部分でスロット計算するため、両キーが同一スロット
redis.mget(['user:{123}:profile', 'user:{123}:settings']); // OK

設計編(次章)で hash tag の使いどころを扱う。

Cache ≠ DB の代替ではない

最後に重要な認識:Cache は永続的な真実の場所ではない(前項の例外を除き)。

  • Source of truth は別の DB(Postgres / DynamoDB)
  • Cache は 加速のための投影
  • データ損失は許容、整合性は eventually

これは「Inside data の写し」と捉えることもできる。Pat Helland の語彙に乗せると、OLTP(Inside)の hot 部分を、外に投影した中間層が Cache。

この章の要点

  • Redis は KV ストアではなく データ構造サーバー。プリミティブなデータ構造を組み合わせて使う
  • 単一スレッド設計が in-memory ns - μs オペレーションでは最速の選択
  • 永続化は RDB / AOF / 両方 / なしの 4 択。用途で選ぶ
  • Cache は通常 Tier で構成される。各層 1 桁ずつレイテンシが違う
  • Redis Cluster は 16384 スロット sharding。複数キー操作は hash tag で同一スロットに揃える
  • Cache は Source of truth ではない(原則)。Inside data の hot 部分の投影

次章への問いかけ

データ構造サーバーとしての Redis の世界観は分かった。次は「どう使うか」。

Cache 設計には固有の意思決定がある ── Cache-aside か Write-through か / TTL の決め方 / Stampede 対策 / Key 命名。次章でこれらを扱う。