Skip to content
Published on

Redis Cluster 構築と運用の実践ガイド — シャーディング、レプリケーション、フェイルオーバー

Authors
  • Name
    Twitter
Redis Cluster Setup

はじめに

単一の 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 運用の要点:

  1. 最低6ノード: 3 Master + 3 Replica で高可用性を確保
  2. Hash Tag の活用: 関連キーを同じスロットに配置
  3. 自動フェイルオーバー: cluster-node-timeout の設定に基づく自動復旧
  4. リシャーディング: ノードの追加/削除時にスロットを再分配
  5. モニタリング: 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)