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 Set | leaderboard、ranking、time series 風 |
| Stream | append-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 store | RDB + AOF |
| ジョブキュー | AOF(fsync every command) |
| Counter / Rate limit | RDB(多少のロスは許容) |
| 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 命名。次章でこれらを扱う。