- Authors
- Name

はじめに
単一の Redis インスタンスにはメモリとスループットに限界があります。Redis Cluster はデータを複数のノードに自動分散(シャーディング)し、ノード障害時に自動フェイルオーバーを提供するネイティブクラスタリングソリューションです。
この記事では、Redis Cluster のアーキテクチャを理解し、実際の構築から運用までをステップバイステップで見ていきます。
Redis Cluster アーキテクチャ
ハッシュスロット(Hash Slots)
Redis Cluster は 16,384個のハッシュスロット を使ってデータを分散します:
# キーのハッシュスロット計算
# HASH_SLOT = CRC16(key) % 16384
# 例: 3つのマスターノード
# Node A: スロット 0 ~ 5460
# Node B: スロット 5461 ~ 10922
# Node C: スロット 10923 ~ 16383
クラスタートポロジー
# 最小推奨構成: 3 Master + 3 Replica = 6 ノード
#
# Master A (スロット 0-5460) ←→ Replica A'
# Master B (スロット 5461-10922) ←→ Replica B'
# Master C (スロット 10923-16383) ←→ Replica C'
#
# 各 Master がダウンすると該当 Replica が自動昇格
6ノード Redis Cluster の構築
Docker Compose で構築
# docker-compose.yml
version: '3.8'
services:
redis-node-1:
image: redis:7.4
container_name: redis-node-1
ports:
- '7001:7001'
- '17001:17001'
volumes:
- ./redis-node-1:/data
command: >
redis-server
--port 7001
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.11
redis-node-2:
image: redis:7.4
container_name: redis-node-2
ports:
- '7002:7002'
- '17002:17002'
volumes:
- ./redis-node-2:/data
command: >
redis-server
--port 7002
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.12
redis-node-3:
image: redis:7.4
container_name: redis-node-3
ports:
- '7003:7003'
- '17003:17003'
volumes:
- ./redis-node-3:/data
command: >
redis-server
--port 7003
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.13
redis-node-4:
image: redis:7.4
container_name: redis-node-4
ports:
- '7004:7004'
- '17004:17004'
volumes:
- ./redis-node-4:/data
command: >
redis-server
--port 7004
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.14
redis-node-5:
image: redis:7.4
container_name: redis-node-5
ports:
- '7005:7005'
- '17005:17005'
volumes:
- ./redis-node-5:/data
command: >
redis-server
--port 7005
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.15
redis-node-6:
image: redis:7.4
container_name: redis-node-6
ports:
- '7006:7006'
- '17006:17006'
volumes:
- ./redis-node-6:/data
command: >
redis-server
--port 7006
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--protected-mode no
--bind 0.0.0.0
networks:
redis-cluster:
ipv4_address: 172.20.0.16
networks:
redis-cluster:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
# コンテナを起動
docker compose up -d
# クラスター作成(3 master + 3 replica)
docker exec -it redis-node-1 redis-cli --cluster create \
172.20.0.11:7001 172.20.0.12:7002 172.20.0.13:7003 \
172.20.0.14:7004 172.20.0.15:7005 172.20.0.16:7006 \
--cluster-replicas 1 --cluster-yes
# クラスター状態確認
docker exec -it redis-node-1 redis-cli -p 7001 cluster info
docker exec -it redis-node-1 redis-cli -p 7001 cluster nodes
ベアメタル / VM での構築
# Redis インストール(Ubuntu)
sudo apt update && sudo apt install -y redis-server
# ノード別設定ファイルの作成
cat > /etc/redis/redis-7001.conf << 'EOF'
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
appendfilename "appendonly-7001.aof"
dbfilename dump-7001.rdb
dir /var/lib/redis/7001
logfile /var/log/redis/redis-7001.log
pidfile /var/run/redis/redis-7001.pid
protected-mode no
bind 0.0.0.0
# メモリ設定
maxmemory 4gb
maxmemory-policy allkeys-lru
# パフォーマンスチューニング
tcp-backlog 511
timeout 0
tcp-keepalive 300
EOF
# ディレクトリ作成
sudo mkdir -p /var/lib/redis/7001
sudo chown redis:redis /var/lib/redis/7001
# サービス起動
sudo redis-server /etc/redis/redis-7001.conf --daemonize yes
# 6ノードすべて起動後、クラスターを作成
redis-cli --cluster create \
192.168.1.1:7001 192.168.1.2:7002 192.168.1.3:7003 \
192.168.1.4:7004 192.168.1.5:7005 192.168.1.6:7006 \
--cluster-replicas 1
クラスター運用
データの読み書き
# クラスターモードで接続(-c フラグ)
redis-cli -c -h 172.20.0.11 -p 7001
# MOVED リダイレクションが自動処理される
172.20.0.11:7001> SET user:1000 "Kim Youngju"
-> Redirected to slot [3817] located at 172.20.0.11:7001
OK
172.20.0.11:7001> SET user:2000 "Park Minho"
-> Redirected to slot [8234] located at 172.20.0.12:7002
OK
Hash Tag で同じスロットに格納
# {user:1000} の部分のみハッシュ計算に使用される
SET {user:1000}.profile "Kim Youngju"
SET {user:1000}.email "youngju@example.com"
SET {user:1000}.settings "{\"theme\":\"dark\"}"
# 同じスロットに格納されるので MGET が可能
MGET {user:1000}.profile {user:1000}.email
Python クライアント
from redis.cluster import RedisCluster
# クラスター接続
rc = RedisCluster(
startup_nodes=[
{"host": "172.20.0.11", "port": 7001},
{"host": "172.20.0.12", "port": 7002},
{"host": "172.20.0.13", "port": 7003},
],
decode_responses=True,
skip_full_coverage_check=True
)
# 基本操作
rc.set("user:1000", "Kim Youngju")
print(rc.get("user:1000"))
# パイプライン(同じスロットのキーのみ)
pipe = rc.pipeline()
pipe.set("{user:1000}.name", "Kim Youngju")
pipe.set("{user:1000}.age", "30")
pipe.get("{user:1000}.name")
results = pipe.execute()
print(results)
# クラスター情報
print(rc.cluster_info())
ノードの追加 / 削除
# 新しいマスターノードを追加
redis-cli --cluster add-node 172.20.0.17:7007 172.20.0.11:7001
# スロットのリバランス
redis-cli --cluster rebalance 172.20.0.11:7001
# 新しいノードにレプリカを追加
redis-cli --cluster add-node 172.20.0.18:7008 172.20.0.11:7001 \
--cluster-slave --cluster-master-id <master-node-id>
# ノード削除(まずスロットを他のノードに移動)
redis-cli --cluster reshard 172.20.0.11:7001 \
--cluster-from <removing-node-id> \
--cluster-to <target-node-id> \
--cluster-slots 5461 \
--cluster-yes
redis-cli --cluster del-node 172.20.0.11:7001 <removing-node-id>
自動フェイルオーバー
フェイルオーバーの動作過程
# 1. Master A のダウンを検知(cluster-node-timeout 秒後)
# 2. Replica A' が他の Master に投票を要求
# 3. 過半数の Master が承認すれば Replica A' が Master に昇格
# 4. 新しい Master A' が既存のスロットを担当
# フェイルオーバーテスト
docker stop redis-node-1
# 状態確認(Replica が Master に昇格済み)
docker exec -it redis-node-2 redis-cli -p 7002 cluster nodes
手動フェイルオーバー
# Replica で実行(graceful failover)
redis-cli -h 172.20.0.14 -p 7004 CLUSTER FAILOVER
# 強制フェイルオーバー(Master がダウンしている場合)
redis-cli -h 172.20.0.14 -p 7004 CLUSTER FAILOVER FORCE
モニタリング
主要メトリクス
# クラスター状態確認
redis-cli -p 7001 cluster info
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_known_nodes:6
# ノード別メモリ使用量
redis-cli -p 7001 info memory
# used_memory_human:1.5G
# maxmemory_human:4.0G
# スロット分配の確認
redis-cli --cluster check 172.20.0.11:7001
Prometheus + Grafana によるモニタリング
# docker-compose.monitoring.yml
services:
redis-exporter:
image: oliver006/redis_exporter:latest
environment:
- REDIS_ADDR=redis://172.20.0.11:7001
- REDIS_CLUSTER=true
ports:
- '9121:9121'
# prometheus.yml
scrape_configs:
- job_name: 'redis-cluster'
static_configs:
- targets: ['redis-exporter:9121']
# 主要 Grafana ダッシュボードクエリ
# 1秒あたりのコマンド数
rate(redis_commands_processed_total[5m])
# メモリ使用率
redis_memory_used_bytes / redis_memory_max_bytes * 100
# キー数
redis_db_keys
# 接続中のクライアント数
redis_connected_clients
# レプリケーション遅延
redis_replication_offset
トラブルシューティング
CROSSSLOT エラー
# エラー: CROSSSLOT Keys in request don't hash to the same slot
# 原因: MGET、MSET などで異なるスロットのキーを使用
# 解決: Hash Tag を使用
MGET {order:1}.items {order:1}.total # OK(同じスロット)
MGET order:1 order:2 # ERROR(異なるスロットの可能性)
クラスター状態の復旧
# クラスター状態が fail の場合
redis-cli --cluster fix 172.20.0.11:7001
# スロットが欠落している場合
redis-cli --cluster fix 172.20.0.11:7001 --cluster-fix-with-unreachable-masters
まとめ
Redis Cluster 運用の要点:
- 最低6ノード: 3 Master + 3 Replica で高可用性を確保
- Hash Tag の活用: 関連キーを同じスロットに配置
- 自動フェイルオーバー: cluster-node-timeout の設定に基づく自動復旧
- リシャーディング: ノードの追加/削除時にスロットを再分配
- モニタリング: Prometheus + redis_exporter で常時監視
クイズ(7問)
Q1. Redis Cluster のハッシュスロット数は? 16,384個
Q2. キーのハッシュスロットを計算する式は? CRC16(key) % 16384
Q3. Hash Tag の役割は? 中括弧内の文字列のみをハッシュ計算に使用し、関連キーを同じスロットに配置する
Q4. 自動フェイルオーバー時に Replica が Master に昇格するために必要なものは? 過半数の Master の投票(承認)が必要
Q5. CROSSSLOT エラーの原因と解決法は? 異なるスロットのキーを1つのコマンドで使用した際に発生。Hash Tag で同じスロットに配置して解決
Q6. cluster-node-timeout の役割は? ノード障害を検知する時間。この時間内に応答がなければ障害と判断
Q7. ノード削除時にまず行うべき作業は? 該当ノードのスロットを他のノードにリシャーディング(reshard)