Skip to content

✍️ 필사 모드: Redis 内部と分散キャッシュ — シングルスレッド、データ構造、Cluster、Sentinel、RDB/AOF、Redlock、Valkey、Dragonfly 完全解説 (2025)

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

"Redis is what happens when a C programmer falls in love with data structures." — Salvatore Sanfilippo (antirez)

Redis ほど「とりあえず使う」と「きちんと理解して使う」の差が大きいシステムは珍しい。ほとんどの開発者は GET/SET しか使わず、しかも文字列キャッシュ用途だけ。しかし Redis は本来、インメモリデータ構造サーバー (In-Memory Data Structure Server) であり、キャッシュは副産物に過ぎない。

2009 年、Salvatore Sanfilippo が自身のウェブ解析ツール LLOOGG のために作った。MySQL ではリアルタイムランキングを維持できず、自作した「Remote Dictionary Server」が Redis の始まりである。2010 年に VMware、2013 年に Pivotal、2015 年に Redis Labs (現 Redis Inc) へと移り、そして 2024 年 3 月、Redis は突如オープンソースライセンスを捨てた。Linux Foundation が Valkey をフォークし、クラウド戦争の新局面が開いた。

本稿は Redis を「本当に」理解したい人のための地図である。


1. なぜ Redis は速いのか — シングルスレッドの逆説

よくある誤解

「シングルスレッドなのに速い? コアを遊ばせているのでは?」

違う。Redis 6.0 以降、ネットワーク I/O はマルチスレッドで処理するが、コマンド実行そのものは依然シングルスレッド。そしてそれこそが速さの理由である。

シングルスレッドの合理性

  1. メモリ速度は CPU 速度に近い — ボトルネックはネットワークと OS 呼び出し
  2. ロックなし — 共有構造のミューテックスオーバーヘッドゼロ
  3. コンテキストスイッチなし — CPU キャッシュ局所性を最大化
  4. 原子性が自然 — すべてのコマンドが atomic
  5. デバッグが容易 — バグ再現が単純

antirez の言葉:「2009 年当時すでに、ロックベースのマルチスレッドが現実には難しいと分かっていた。ロックなしで速いものを作ろう」

100 万 QPS の秘密

実際に Redis は単一インスタンスで 毎秒 100 万コマンド を超える。秘訣は:

  • I/O 多重化epoll (Linux) / kqueue (BSD) イベントループで 1 スレッドから数千ソケットを監視
  • RESP プロトコル — 単純なテキストベース、パース費用最小
  • パイプライニング — 複数コマンドをまとめて送受信
  • ゼロコピーではない — しかし単位が小さく問題なし
  • 大半が O(1)O(1) または O(logN)O(\log N) の構造

イベントループ 1 周

1. epoll_wait() — 準備済みソケット検出
2. リクエストパース (RESP)
3. コマンド実行 (シングルスレッド、メモリ操作のみ)
4. レスポンスバッファ書き込み
5. 次のイベントへ

1 コマンドが長いと? 全体が止まる。だから KEYS *FLUSHALL は本番禁止。代わりに SCANUNLINK を使う。


2. データ構造 — Redis の真の力

OBJECT ENCODING mykey で内部エンコーディングを見ると驚く。同じ「String」でも数値なら int、短ければ embstr、長ければ raw

9 つの中核データ構造

構造用途内部実装特徴
String文字列/数値/バイナリSDS最大 512MB
Listキュー/スタックQuickList双方向 O(1) push/pop
Hashオブジェクトフィールドlistpack/hashtable小ハッシュは ziplist
Setユニーク値集合listpack/intset/hashtable整数のみなら intset
Sorted SetランキングSkip List + hash歴史的に興味深い選択
StreamイベントログRadix TreeKafka 風 consumer group
HyperLogLogカーディナリティ推定固定 12KB、標準偏差 0.81%確率的構造
Bitmapビット配列String ベース10 億ユーザー DAU を 128MB に
Geospatial位置クエリSorted Set + GeohashGEOADD/GEORADIUS

SDS — なぜ C 文字列ではないのか

struct sdshdr {
    int len;     // O(1) strlen
    int free;    // 余剰領域
    char buf[];  // 実データ + '\0'
};
  • strlen O(1) (C 文字列は O(N))
  • バイナリセーフ (途中に \0 可)
  • 再割り当て削減 (2 倍バッファ)

Sorted Set の Skip List — なぜ Red-Black Tree ではないのか

antirez が自身のブログで理由を述べている:

  1. 実装が単純 — B-Tree/Red-Black の半分以下
  2. Range query に強い — ソート済み連結リスト構造
  3. メモリ局所性がそこそこ良い
  4. デバッグ容易 — 木の回転なし

「Skip List を選んだのは最適だったからではなく、実装が単純だったからだ」— antirez

HyperLogLog — 12KB で数十億推定

  • 「今日の UV は?」→ Set ではメモリ爆発
  • HLL は確率的で標準偏差 0.81%
  • 固定 12KB — 1 億でも 100 億でも 12KB
PFADD visitors user:1 user:2 user:3
PFCOUNT visitors
PFMERGE today yesterday

Bitmap — SETBIT の威力

SETBIT user:active:20260415 12345 1
BITCOUNT user:active:20260415
BITOP AND weekly user:active:*

10 億ユーザー → 10 億ビット → 128MB。「過去 30 日連続ログインユーザー」のクエリが高速。

Stream — Kafka-lite (2018)

Redis 5.0 で追加。Consumer Group と offset 管理で Kafka-lite 用途。ただし永続性は Redis の persistence 依存なので、Kafka 代替というより軽量メッセージバス。


3. 永続化 — RDB vs AOF のトレードオフ

RDB (Redis Database) — スナップショット

  • 定期的に全データセットをバイナリファイルへダンプ
  • BGSAVE — fork() 後に子プロセスがダンプ、親は応答継続
  • Copy-on-Write により親メモリは通常 2 倍にならない
  • 欠点:fork 以降のデータは失われる可能性
  • 利点:再起動が速い、ファイルサイズ小

AOF (Append-Only File) — ログ

  • 全書き込みコマンドをファイル末尾に追記
  • fsync ポリシー:
    • always — 毎コマンド fsync (遅い、損失なし)
    • everysec — 毎秒 (デフォルト、最大 1 秒損失)
    • no — OS 任せ (速い、損失大)
  • AOF Rewrite — 定期圧縮
  • 欠点:再起動が遅い、ファイル大
  • 利点:損失ほぼなし

ハイブリッド — Redis 4.0+

aof-use-rdb-preamble yes — AOF 先頭に RDB、末尾に増分 AOF。高速再起動と低損失の両立。現在の事実上の標準。

意思決定マトリクス

シナリオRDBAOFハイブリッド
キャッシュOK (永続化不要)XX
セッション保存XOK (everysec)OK
再起動時間重視OKXOK
主ストレージXOK (always)OK (最良)
ディスク I/O 敏感OKX注意

本当に永続ストアとして使えるか

推奨しない。antirez も繰り返し警告した。Redis の目標は「高速キャッシュと最小損失」であり、「完全な耐久性」ではない。重要データは Postgres/MySQL へ、Redis はキャッシュに。


4. Redis Cluster — Hash Slot の芸術

なぜ Cluster か

  • 単一 Redis はメモリ/スループット上限 (通常数十 GB)
  • マルチシャード必要 → Redis Cluster (3.0、2015)

16384 Hash Slot

  • キーを CRC16 でハッシュ後、16384 (2^14) でモジュロ
  • 各スロットをマスターノードへ割り当て
  • 3 マスターなら各 ~5461 スロット

なぜ 16384 か

antirez が GitHub issue で直接答えた:

  1. スロットビットマップを gossip で送信 — 16384 ビット = 2KB
  2. 65536 だと 8KB — gossip では大きすぎる
  3. 1000 ノード未満なら 16384 で分配品質十分

MOVED & ASK — リダイレクト

  • MOVED <slot> host:port — 「このスロットは恒久的にそこ」
  • ASK <slot> host:port — 「移行中、一度だけそこに聞け」

スマートクライアント (Lettuce、redis-py-cluster) はスロットマップをキャッシュし、MOVED で更新する。

Hash Tag — 同一スロット保証

SET {user:1}:profile "..."
SET {user:1}:sessions "..."

{} 内の部分のみハッシュ対象。MULTI/EXEC、SUNION、複数キーを扱う Lua で必須。

Gossip と障害検出

  • ノード間で PING/PONG
  • cluster-node-timeout 超 → PFAIL (主観障害)
  • 過半数が PFAIL 報告 → FAIL (客観障害)
  • レプリカが自動昇格

制約

  • マルチキーコマンド (SINTER) は同一スロット必須
  • クロススロットトランザクション不可
  • バックアップが複雑 (ノード別)

5. Sentinel — HA のもう一つの道

Cluster がシャーディングと HA を兼ねる一方、Sentinel は HA のみ を提供する。

構成

  • マスター 1 + レプリカ N + Sentinel M (通常 3 以上、奇数)
  • Sentinel 群がマスター状態を監視
  • 障害時は Raft 風合意で新リーダー選出
  • クライアントはまず Sentinel に現在のマスターを問い合わせ

Cluster vs Sentinel

側面SentinelCluster
シャーディングXOK
HAOKOK
設定複雑度
クライアント対応広いスマートクライアント必要
運用規模小〜中中〜大
マルチキー完全Hash Tag 必要

選択基準:データが 1 ノードメモリに収まる → Sentinel、収まらない → Cluster。


6. キャッシュパターン — ホットポテト

Cache-Aside (Lazy Loading)

def get_user(id):
    user = redis.get(f"user:{id}")
    if user is None:
        user = db.query(id)
        redis.set(f"user:{id}", user, ex=3600)
    return user
  • 最も一般的
  • 利点:実装容易、ミス時のみ DB 負荷
  • 欠点:初回リクエストが遅いStale データ 可能性

Write-Through

def update_user(id, data):
    db.update(id, data)
    redis.set(f"user:{id}", data, ex=3600)
  • 書き込み毎に DB + キャッシュ更新
  • 一貫性良好
  • 欠点:書き込み遅延増加、読まれないデータもキャッシュ

Write-Behind (Write-Back)

def update_user(id, data):
    redis.set(f"user:{id}", data)
    queue.push({"id": id, "data": data})
  • 書き込み遅延最小
  • 欠点:データ損失リスク、実装複雑

Refresh-Ahead

  • TTL 間近のキャッシュを非同期で先行更新
  • Thundering Herd 防止

実戦の組合せ

多くの本番は Cache-Aside + TTL + (条件付き) Write-Through。TTL 戦略が肝:

  • 短 TTL (1〜5 分) — 鮮度重視
  • 長 TTL (1 時間以上) — 変更少ないデータ
  • Jitterex=3600 + random(-300, 300) — 同時失効回避

7. Thundering Herd とキャッシュスタンピード

Redis 障害で最も多く、最も見えにくい。

シナリオ

  1. 人気キーが失効
  2. 同時に 数千リクエストがキャッシュミス
  3. 全て DB 直撃 → DB 過負荷 → 全体障害

対策 1 — Mutex Lock

def get_with_lock(key):
    value = redis.get(key)
    if value is not None:
        return value
    lock = redis.set(f"lock:{key}", "1", nx=True, ex=10)
    if not lock:
        time.sleep(0.05)
        return redis.get(key)
    try:
        value = db.query(...)
        redis.set(key, value, ex=3600)
        return value
    finally:
        redis.delete(f"lock:{key}")

対策 2 — 確率的先行期限切れ (XFetch)

失効前に 確率的に更新。論文 "Optimal Probabilistic Cache Stampede Prevention"

def fetch(key, beta=1.0):
    value, ttl, delta = redis.get_with_meta(key)
    now = time.time()
    if value is None or now - delta * beta * math.log(random.random()) >= ttl:
        value = db.query(...)
        redis.set(key, value, ex=3600)
    return value

対策 3 — 二重 TTL (Soft & Hard)

  • Soft TTL 超過でバックグラウンド更新、クライアントは旧値
  • Hard TTL 超過で同期更新

8. 分散ロック — Redlock 論争

SET key value NX PX 10000 で簡単に実装できる。

単純ロック (1 インスタンス)

SET lock:resource unique_id NX PX 30000
EVAL "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock:resource unique_id

Redlock — 5 インスタンス分散ロック

antirez 提案のアルゴリズム:

  1. 5 独立インスタンスへ同時にロック試行
  2. 過半 (3) 成功かつ総時間 < TTL なら取得
  3. 失敗時は全解放

Martin Kleppmann の批判

DDIA 著者が有名なブログで反論:

  • Fencing token なし — GC 停止で失効ロックで作業継続
  • 時計同期仮定が弱い — NTP ジャンプ、VM 停止で TTL 保証不可
  • 正確性 (correctness) が必要なら Redlock を使うな — ZooKeeper、etcd を使え

antirez の反論

  • Redlock は 性能目的 — 稀な重複実行が許容できる場合のみ
  • 正確性が致命的なら DB トランザクションを使え
  • Fencing token は実装可能 (単調増加カウンター)

現実的結論

目的推奨ツール
重複防止 (性能)Redis SET NX PX
リーダー選出 (正確性)ZooKeeper/etcd
トランザクションDB そのもの
決済/金銭関連絶対に Redis ロック禁止

9. 2024 年ライセンス騒動と Valkey フォーク

2024 年 3 月 20 日、Redis Inc が衝撃発表:

  • Redis 7.4 から BSD → SSPL/RSALv2 デュアルライセンス
  • AWS ElastiCache 等クラウド事業者への打撃が目的
  • OSS コミュニティは激怒

Valkey — 48 時間での反撃

  • 3 月 28 日、Linux Foundation が Valkey プロジェクト開始
  • AWS、Google Cloud、Oracle、Ericsson が創設スポンサー
  • Redis 7.2.4 からフォーク、BSD 3-Clause 維持
  • 主要メンテナーの大半が Valkey へ移籍

antirez の復帰

2024 年 11 月、antirez が Redis Inc に復帰。2025 年はベクトル検索 (RedisVL) と AI 統合に注力。

2025 年現状

製品ライセンス主導貢献者
RedisSSPL/RSALv2Redis Incantirez 復帰
ValkeyBSD 3-ClauseLinux FoundationAWS/Google/Oracle
KeyDBBSD 3-ClauseSnapマルチスレッドフォーク
DragonflyBSL/Apache 2.0Dragonfly Labsゼロから再実装

選択ガイド:

  • パブリッククラウド managed → 気にしなくてよい (ElastiCache は Valkey へ移行中)
  • 自前運用 + OSS 主義 → Valkey
  • マルチスレッド極限性能 → Dragonfly
  • Redis 7.4+ の新機能必要 → Redis

10. Dragonfly — "Redis を 25 倍速く"

2022 年登場、C++20 でゼロから再実装。秘訣:

  • マルチスレッド Shared-nothing — 各スレッドが自シャード担当、ロック不要
  • io_uring — Linux 最新の非同期 I/O (epoll より速い)
  • Dash ハッシュテーブル — 学術論文ベース、キャッシュフレンドリー
  • RDB 保存速度 30 倍 — メモリスナップショットアルゴリズム刷新

性能 (2025 ベンチマーク)

  • 単一 AWS c7g.16xlarge:650 万 QPS (Redis は約 20 万 QPS)
  • メモリ効率も 30% 改善

制約

  • スクリプティング (Lua) 制限
  • Cluster プロトコル 100% 互換ではない
  • 一部 edge case コマンド未実装
  • 運用ツール生態系が小さい

KeyDB

  • Snap (Snapchat) 製マルチスレッドフォーク
  • Redis とほぼ 100% 互換
  • 2024 年以降開発停滞 — 勢いは Dragonfly へ

11. メモリ管理 — OOM を防ぐ 8 つの方法

Redis の OOM は即障害。

maxmemory + Eviction Policy

maxmemory 4gb
maxmemory-policy allkeys-lru

ポリシー:

  • noeviction — 新規書き込み拒否 (デフォルト、危険)
  • allkeys-lru — 全キーから LRU
  • volatile-lru — TTL 持ちキーから LRU
  • allkeys-lfu — LFU (4.0+、推奨)
  • volatile-ttl — TTL 間近優先
  • allkeys-random / volatile-random

キャッシュ用途なら allkeys-lfu を推奨。LRU はスキャン攻撃に弱い。

プロファイリング

MEMORY USAGE user:1
MEMORY STATS
MEMORY DOCTOR

Big Key 問題

単一キーが 1MB 超で危険:

  • KEYS 走査コスト
  • ネットワーク送信遅延
  • Cluster リシャーディング時の全体移動
  • 対策:ハッシュ/リストに分割

Hot Key 問題

単一キーに集中 → 1 コア全開

  • 対策:クライアント側キャッシュ、シャード分散、レプリカで読み分散

TTL 戦略

  • 必ず TTL 設定 — 無限キーはメモリリーク
  • Jitter 追加 — 同時失効回避
  • Hot は長 TTL、Cold は短 TTL

12. Lua スクリプティングと Functions

Redis は Lua 5.1 を内蔵。複数コマンドを atomic 実行。

local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
    redis.call('DECRBY', KEYS[1], ARGV[1])
    return 1
else
    return 0
end

Functions (Redis 7.0+)

Lua スクリプトをサーバーにライブラリ保存 (関数単位管理)。

注意

  • Lua 実行中は 他全コマンドブロック — 長スクリプト禁止
  • EVAL 頻用時は SCRIPT LOAD + EVALSHA

13. Client-Side Caching (Tracking) — Redis 6.0

サーバー側変更をクライアントへ プッシュ通知。クライアントはローカルキャッシュを保持し、必要時に無効化。

CLIENT TRACKING ON REDIRECT 1234 BCAST PREFIX user:
  • ラウンドトリップ削減 → 超低レイテンシ
  • Lettuce、redis-py など主要ドライバが対応
  • クライアント側メモリ管理必要

14. 監視指標 — 必見の 10 個

指標説明警告閾値
used_memory / maxmemoryメモリ使用率80%
evicted_keys追い出しキー数増加傾向
connected_clients同時接続1 万以上注意
instantaneous_ops_per_secQPSベースライン対比
latency_percentiles_usec_*p99 遅延1ms 超
rejected_connections接続拒否0 維持
keyspace_hits / missesヒット率90% 以上
aof_current_sizeAOF サイズディスク余裕
rdb_last_save_time最終 RDB古いと警告
master_link_status (レプリカ)複製接続up

遅いコマンド追跡

CONFIG SET slowlog-log-slower-than 10000
SLOWLOG GET 10

MONITOR 禁止

MONITOR は全コマンドをストリーミング → 性能 50%+ 低下。本番厳禁。代わりに SLOWLOGLATENCY


15. キャッシュ戦略アンチパターン TOP 10

  1. TTL なしキー大量挿入 — メモリリーク
  2. KEYS *FLUSHALL — シングルスレッド停止
  3. 巨大 Hash/List 蓄積 — 1 コマンドが秒単位
  4. キャッシュのみ保存 — Redis 損失で復旧不能
  5. 全キー同一 TTL — 同時失効 → スタンピード
  6. 分散ロックで金銭管理 — Redlock 論争参照
  7. Pub/Sub を永続キューに — メッセージ消滅、Stream を使え
  8. 短 TTL の Write-Through — 毎回 DB + Redis、無意味
  9. レプリカへ書き込み — 複製非同期、損失
  10. Cluster でクロススロットトランザクション試行 — 静かに失敗

16. Redis を賢く使うチェックリスト

  • 用途は キャッシュか、ストレージか を明確に
  • maxmemoryeviction policy を明示設定
  • 全キーに TTL — Jitter 込み
  • AOF + everysec で 1 秒以内損失許容、ハイブリッドも検討
  • MONITOR / KEYS * / FLUSHALL 禁止ポリシー
  • Big Key / Hot Key アラート (1MB / 10K QPS)
  • ヒット率 90%+ 目標、ミスは原因分析
  • Thundering Herd 対策 — Mutex か XFetch
  • 分散ロックは 性能目的のみ — 正確性が必要なら別ツール
  • Cluster vs Sentinel 判断基準明確 (データサイズ)
  • Client-Side Caching 検討 — 読み中心ワークロード
  • レプリカ昇格シナリオ訓練 (Failover 演習)

おわりに — シングルスレッドの優雅さ

Redis の成功は逆説だ。並列が王の時代に、シングルスレッドで毎秒 100 万 QPS を出す システム。その背後には antirez の哲学がある。

「複雑なものを単純にするのではなく、最初から単純に設計するのが本当のエンジニアリングだ」— antirez

多くの「レガシー」技術がそうであるように、Redis は見た目よりずっと深い。データ構造、I/O モデル、永続化、分散、トレードオフ — すべての決定に理由がある。2024 年のライセンス論争は OSS と商用化の古い緊張が Redis で噴出した事件であり、Valkey/Dragonfly の台頭は「Redis そのものよりそのインターフェースが重要になった」時代を示している。


次回予告 — PostgreSQL 内部とクエリ最適化

Redis が「データ構造の優雅さ」なら、PostgreSQL は「リレーショナル DB の完成形」。次回:

  • MVCC、VACUUM、WAL と複製
  • クエリプランナ内部
  • B-Tree/Hash/GiST/GIN/BRIN/HNSW (pgvector)
  • Partitioning、pgBouncer
  • JSONB vs Document DB
  • PostgreSQL 18 (2025) の新機能 (AIO、DirectIO、UUIDv7)

データベースを「ブラックボックスでなく透明なエンジン」として見る旅。


「Redis はもともと私自身のためのツールでした。1 万人が使い始めても、100 万人が使い始めても、私は『自分が使いやすい』ものを作ろうとしました。それが Redis の秘密です」— Salvatore Sanfilippo (2025 年 Redis 復帰インタビュー)

현재 단락 (1/312)

Redis ほど「とりあえず使う」と「きちんと理解して使う」の差が大きいシステムは珍しい。ほとんどの開発者は GET/SET しか使わず、しかも文字列キャッシュ用途だけ。しかし Redis は本来、*...

작성 글자: 0원문 글자: 11,045작성 단락: 0/312