Redis 高可用クラスタ構成 実践ガイド
Redis アーキテクチャの進化
スタンドアロンモードの限界
スタンドアロン Redis はシンプルで使いやすいですが、本番環境では多くの課題に直面します:
- 単一障害点:サーバーダウン時はサービス完全停止
- メモリのボトルネック:単一サーバーのメモリ上限がデータ容量を制約
- パフォーマンスの限界:シングルスレッドモデルでは QPS に天井が存在
スタンドアロン → センチネル → クラスタ
Redis アーキテクチャは3つの段階を経て進化しました:
| 段階 | アーキテクチャ | 高可用 | 水平スケーリング | ユースケース |
|---|---|---|---|---|
| 1 | Standalone | ❌ | ❌ | 開発/テスト |
| 2 | Sentinel | ✅ | ❌ | 中小規模本番 |
| 3 | Cluster | ✅ | ✅ | 大規模本番 |
Redis Sentinel センチネルモード
センチネルアーキテクチャの原理
Redis Sentinel は Redis 公式の高可用ソリューションです。1つ以上の Sentinel インスタンスで構成される Sentinel システムは、任意の数のマスターとそのレプリカを監視できます:
# sentinel.conf — センチネル設定例
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel auth-pass mymaster your_strong_password
フェイルオーバーの仕組み
Sentinel フェイルオーバーの完全な流れ:
- 主観的ダウン(SDOWN):単一の Sentinel がマスターを利用不可と判断
- 客観的ダウン(ODOWN):定足数以上の Sentinel がマスターのダウンに同意
- リーダー Sentinel 選出:Raft アルゴリズムでフェイルオーバー実行 Sentinel を選出
- 新マスター選出:優先度 → レプリケーションオフセット → Run ID で順序付け
- フェイルオーバー実行:レプリカをマスターに昇格、他のレプリカを新マスターに再ポイント
# センチネルクラスタの起動(3インスタンス)
redis-sentinel /etc/redis/sentinel-26379.conf
redis-sentinel /etc/redis/sentinel-26380.conf
redis-sentinel /etc/redis/sentinel-26381.conf
# マスター状態の確認
redis-cli -p 26379 sentinel master mymaster
# レプリカ一覧の確認
redis-cli -p 26379 sentinel slaves mymaster
センチネルデプロイのベストプラクティス
- 最低 3つの Sentinel ノードをデプロイして多数派を実現
- Sentinel ノードは異なる物理サーバーに配置
down-after-millisecondsは小さすぎず、ネットワーク揺らぎによる誤検知を防止- クライアントは Sentinel 対応を実装し、新マスターのアドレスを自動取得
Redis Cluster クラスタモード
ハッシュスロットの原理
Redis Cluster はデータを 16384 個のハッシュスロット に分割し、各マスターノードが一部のスロットを担当します:
slot = CRC16(key) % 16384
クラスタノード割り当て例:
| ノード | スロット範囲 | スロット数 |
|---|---|---|
| Node A | 0 ~ 5460 | 5461 |
| Node B | 5461 ~ 10922 | 5462 |
| Node C | 10923 ~ 16383 | 5462 |
クラスタ設定とデプロイ
# redis.conf — クラスタノード設定
port 6379
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
cluster-announce-ip 192.168.1.101
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
requirepass your_strong_password
masterauth your_strong_password
ステップバイステップのクラスタ構築
# ステップ 1:6つの Redis インスタンスを起動(3マスター + 3レプリカ)
for port in 6379 6380 6381 6382 6383 6384; do
redis-server /etc/redis/redis-${port}.conf
done
# ステップ 2:クラスタを作成
redis-cli --cluster create \
192.168.1.101:6379 192.168.1.102:6380 192.168.1.103:6381 \
192.168.1.101:6382 192.168.1.102:6383 192.168.1.103:6384 \
--cluster-replicas 1 -a your_strong_password
# ステップ 3:クラスタ状態を確認
redis-cli -c -p 6379 cluster info
redis-cli -c -p 6379 cluster nodes
# ステップ 4:スロット割り当てを確認
redis-cli -c -p 6379 cluster slots
データ移行とリシャーディング
オンラインリシャーディング
Redis Cluster はダウンタイムなしのオンラインリシャーディングをサポートします:
# 1000スロットを Node A から Node C へ移行
redis-cli --cluster reshard 192.168.1.101:6379 \
--cluster-from <node-a-id> \
--cluster-to <node-c-id> \
--cluster-slots 1000 \
-a your_strong_password
ハッシュタグでデータ分布を制御
関連する Key を同じノードに配置する必要がある場合、ハッシュタグを使用します:
# 中括弧内の内容がスロット割り当てを決定
SET user:{1000}:profile "profile_data"
SET user:{1000}:orders "orders_data"
# 両方の Key は同じスロットに割り当てられる
バッチ移行の注意事項
- 移行中、ターゲットノードはインポート状態(importing)になる
- ソースノードは移行状態(migrating)になる
- 移行中の Key にアクセスするとクライアントは ASK リダイレクト を受信
- 大規模なリシャーディングはオフピーク時間に実行することを推奨
主要データ構造の最適化
String vs Hash でのオブジェクト保存
ユーザーオブジェクトの保存では、Hash 構造が通常メモリ効率に優れます:
# 方式 1:String + JSON(シンプルだがメモリオーバーヘッド大)
SET user:1000 '{"name":"田中","age":30,"city":"東京"}'
# 方式 2:Hash(メモリ節約、部分読み書き対応)
HSET user:1000 name "田中" age 30 city "東京"
HGET user:1000 name
# => "田中"
メモリ比較(100万ユーザーオブジェクト、各5フィールド):
| 保存方式 | メモリ使用量 | 部分更新 | フィールド別TTL |
|---|---|---|---|
| String + JSON | ~320MB | ❌ 全量書き換え | ✅ キー全体 |
| Hash | ~160MB | ✅ 単フィールド更新 | ❌ 非対応 |
ziplist で小規模コレクションを最適化
# Redis 7.0+ は listpack を ziplist の代わりに使用
hash-max-listpack-entries 512
hash-max-listpack-value 64
zset-max-listpack-entries 128
zset-max-listpack-value 64
キャッシュ戦略とパターン
Cache-Aside パターン
最も一般的なキャッシュパターンで、読み込みと書き込みを分離して処理します:
# Cache-Aside パターン
def get_user(user_id):
# 1. キャッシュを先に確認
data = redis.get(f"user:{user_id}")
if data:
return json.loads(data)
# 2. キャッシュミス — DBにクエリ
data = db.query("SELECT * FROM users WHERE id = %s", user_id)
if data:
# 3. キャッシュに書き込み、TTLを設定
redis.setex(f"user:{user_id}", 3600, json.dumps(data))
return data
def update_user(user_id, data):
# 1. DBを更新
db.update("UPDATE users SET ... WHERE id = %s", user_id)
# 2. キャッシュを無効化(更新ではなく削除)
redis.delete(f"user:{user_id}")
Write-Through パターン
すべての書き込みがキャッシュ層を経由し、キャッシュ層が同期的にDBに書き込みます:
# Write-Through パターン
def write_through(key, value):
# キャッシュ層が同期的なDB書き込みを担当
redis.set(key, value)
db.sync_write(key, value)
Write-Behind(遅延書き込み)パターン
書き込みはキャッシュのみ更新し、バックグラウンドで非同期にDBへフラッシュします:
# Write-Behind パターン(非同期書き戻し)
def write_behind(key, value):
redis.set(key, value)
# ダーティデータとしてマーク、非同期フラッシュ待ち
dirty_key_queue.append(key)
async def flush_to_db():
while True:
keys = batch_get_dirty_keys(100)
for key in keys:
value = redis.get(key)
db.async_write(key, value)
await asyncio.sleep(1)
キャッシュの3大問題と解決策
キャッシュペネトレーション
存在しないデータへのクエリがキャッシュをバイパスしてDBに直撃します:
# 解決策 1:ブルームフィルター
def get_with_bloom(key):
if not bloom_filter.might_contain(key):
return None # 確実に存在しない
return cache_aside_get(key)
# 解決策 2:Null値のキャッシュ
def get_with_null_cache(key):
data = redis.get(key)
if data == "NULL":
return None # Nullキャッシュヒット
if data:
return data
data = db.query(key)
if not data:
redis.setex(key, 60, "NULL") # 短いTTLでNull値をキャッシュ
return data
キャッシュブレイクダウン
ホットキーの期限切れ瞬間に大量リクエストがDBに穿透します:
# 解決策:ミューテックスロック + 論理有効期限
def get_with_mutex(key):
data = redis.get(key)
if data:
return data
# ミューテックスロックを取得
lock_key = f"lock:{key}"
if redis.set(lock_key, 1, nx=True, ex=5):
try:
data = db.query(key)
redis.setex(key, 3600, data)
return data
finally:
redis.delete(lock_key)
else:
time.sleep(0.1)
return get_with_mutex(key) # リトライ
キャッシュアバランシェ
大量のキーが同時に期限切れとなり、DB負荷が急増します:
# 解決策:TTLにランダムジッターを追加
import random
def set_with_jitter(key, value, base_ttl=3600):
jitter = random.randint(0, 300) # 0~5分のランダムオフセット
redis.setex(key, base_ttl + jitter, value)
メモリ最適化テクニック
主要設定項目
# メモリ最適化関連設定
maxmemory 8gb
maxmemory-policy allkeys-lru
# lazy-free 非同期削除を有効化
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
# 共有整数オブジェクトプール(0-9999 はデフォルトで共有)
# 範囲外の整数は共有されない
追い出しポリシーの選択
| ポリシー | 説明 | ユースケース |
|---|---|---|
| noeviction | 追い出しなし、書き込みエラー | データ損失不可 |
| allkeys-lru | 全キー LRU | 汎用キャッシュ |
| volatile-lru | TTL付きキー LRU | 混合使用場面 |
| allkeys-lfu | 全キー LFU | ホットデータが明確 |
| volatile-ttl | 最短TTLを追い出し | ビジネス優先度が明確 |
永続化戦略
RDB vs AOF vs ハイブリッド永続化
# RDB スナップショット設定
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
# AOF 追記設定
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# Redis 4.0+ ハイブリッド永続化
aof-use-rdb-preamble yes
| 特徴 | RDB | AOF | ハイブリッド |
|---|---|---|---|
| ファイルサイズ | 小 | 大 | 中 |
| 復旧速度 | 高速 | 低速 | 比較的速い |
| データ安全性 | データ損失の可能性 | 最大1秒の損失 | 最大1秒の損失 |
| パフォーマンス影響 | fork時 | 書き込み時 | バランス |
監視と運用
Redis Insight での監視
# Redis Insight のインストール
docker run -d --name redis-insight \
-p 8001:8001 \
redis/redisinsight:latest
# CLIで主要メトリクスを取得
redis-cli info memory | grep used_memory_human
redis-cli info stats | grep instantaneous_ops_per_sec
redis-cli info replication | grep connected_slaves
主要監視メトリクス
- メモリ使用率:
used_memory / maxmemory> 80% は要注意 - ヒット率:
keyspace_hits / (keyspace_hits + keyspace_misses) - 接続数:
connected_clientsがmaxclientsに接近したらアラート - スロークエリ:
SLOWLOG GET 10で最近のスロークエリを取得 - レプリケーション遅延:
master_repl_offset - slave_repl_offset
一般的なエラーのトラブルシューティング
CLUSTERDOWN エラー
# エラーメッセージ
# (error) CLUSTERDOWN The cluster is not available
# トラブルシューティング手順
redis-cli -p 6379 cluster info
# cluster_state:fail は未カバースロットが存在
# 修復:全ノードの状態を確認
redis-cli --cluster fix 192.168.1.101:6379 -a your_strong_password
MOVED と ASK リダイレクト
# MOVED:スロットが新しいノードに恒久的に移行された
# (error) MOVED 3999 192.168.1.103:6381
# ASK:スロットが移行中(一時的なリダイレクト)
# (error) ASK 3999 192.168.1.103:6381
# 解決策:クライアントはスマートリダイレクトを実装する必要がある
redis-cli -c -p 6379 # -c でクラスタモードの自動リダイレクトを有効化
一般的な接続エラー
# NOAUTH Authentication required
redis-cli -a your_strong_password -p 6379
# CLUSTERDOWN Hash slot not served
redis-cli --cluster check 192.168.1.101:6379
# BUSY Redis is busy running a script
CONFIG SET lua-time-limit 5000 # Luaスクリプトのタイムアウトを調整
本番環境チェックリスト
デプロイ前チェック
- 最低3マスター + 3レプリカ、異なる物理サーバー/アベイラビリティゾーンに配置
-
appendonly yesとaof-use-rdb-preamble yesを有効化 - 適切な
maxmemoryと追い出しポリシーを設定 -
requirepassとmasterauthを設定 - システムの
vm.overcommit_memory=1を設定 - THP を無効化:
echo never > /sys/kernel/mm/transparent_hugepage/enabled - ファイルディスクリプタ制限を設定:
ulimit -n 65535 - クライアントでコネクションプールとリトライ機構を実装
- 監視・アラート設定が完了
運用規範
-
KEYS *などのブロッキングコマンドを禁止 - キーに適切なTTLを設定、永続キャッシュを避ける
- 大きな値(>10KB)は圧縮または分割を検討
- バッチ操作には Pipeline を使用
- クラスタモードではハッシュタグの使用に注意
よくある質問 FAQ
Q: Sentinel と Cluster はどちらを選ぶべき? A: データ量が単一サーバーメモリに収まり、高可用性のみ必要なら Sentinel。水平スケーリングが必要なら Cluster。両方を混用しないこと。
Q: Cluster モードで MGET などの複数キー操作は可能?
A: すべてのキーが同じハッシュスロットに属する場合のみ可能。ハッシュタグ {prefix} を使って関連キーを同じスロットに配置。
Q: クラスタの最大ノード数は? A: 公式推奨は最大1000マスターノード。実運用では数十マスターノード以内に抑えることを推奨。
Q: RDB と AOF はどちらを使うべき?
A: 本番ではハイブリッド永続化(aof-use-rdb-preamble yes)を推奨。復旧速度とデータ安全性のバランスが最適。
Q: クラスタの必要メモリを見積もるには? A: 総メモリ = ノードあたりのデータ量 × マスター数 × 1.5(50%のオーバーヘッドバッファ)。ノードあたりのデータ量は利用可能メモリの70%以下に抑えること。
Redis ツールとオンラインエンコード/デコードの詳細は、ToolsKu JSON フォーマッター、ハッシュ計算、Base64 コーデック をご覧ください。
ブラウザローカルツールを無料で試す →