- はじめに: なぜいま Redis の高可用性が重要なのか
- 公式ドキュメントと一次ソース
- Sentinel モードと Cluster モードの比較
- Sentinel の構成と運用
- Cluster モードの構成と運用
- 障害シミュレーションと復旧スクリプト
- 運用上の注意点と失敗事例
- パフォーマンスチューニングのチェックリスト
- プロダクション運用チェックリスト
- モニタリングと通知の構成
- レプリケーション詳解: PSYNC と部分同期
- 永続化戦略: RDB と AOF の比較
- まとめ
- 参考資料
はじめに: なぜいま Redis の高可用性が重要なのか
Redis はインメモリデータストアとして、キャッシュ、セッション管理、リアルタイムのリーダーボード、メッセージキューなど、現代のアプリケーションの中核インフラとして定着した。2026 年現在、マイクロサービスアーキテクチャが一般化するにつれて Redis への依存度はさらに高まり、単一 Redis インスタンスの障害がシステム全体のダウンタイムに直結する事例が頻発している。
とくに次のような状況では、Redis の高可用性構成が欠かせない:
- セッションストア: Redis が障害を起こすと、すべてのユーザーが同時にログアウトする
- 分散ロック: Redis がダウンすると同時実行制御が失敗し、データ整合性が損なわれる
- リアルタイムキャッシュ: キャッシュミスの急増でデータベースが過負荷になる (Cache Stampede)
- イベントストリーミング: Redis Streams ベースのパイプラインが停止する
この記事では、Redis の 2 つの高可用性戦略である Sentinel モードと Cluster モードを深く比較し、実際のプロダクション環境での構成方法、障害検知と自動フェイルオーバーの仕組み、そして実践的なトラブルシューティング事例までを扱う。
公式ドキュメントと一次ソース
この記事で参照する Redis の公式ドキュメントと一次ソースは次のとおり:
- Redis Sentinel Documentation - https://redis.io/docs/management/sentinel/
- Redis Cluster Specification - https://redis.io/docs/reference/cluster-spec/
- Redis Cluster Tutorial - https://redis.io/docs/management/scaling/
- Redis Replication - https://redis.io/docs/management/replication/
- Redis Persistence (RDB/AOF) - https://redis.io/docs/management/persistence/
- redis-py Cluster Client - https://redis-py.readthedocs.io/en/stable/clustering.html
Sentinel モードと Cluster モードの比較
アーキテクチャ比較表
| 項目 | Sentinel | Cluster |
|---|---|---|
| 目的 | 高可用性 (HA) | 高可用性 + 水平スケーリング |
| 最小ノード数 | Sentinel 3 + Master 1 + Replica 1 | Master 3 + Replica 3 (計 6) |
| データ分散 | 不可 (単一マスター) | ハッシュスロットによる自動分散 (16384 スロット) |
| 書き込みスケール | 不可 | マルチマスター対応 |
| 読み取りスケール | レプリカの READONLY | レプリカの READONLY |
| 障害検知 | Sentinel のクォーラム投票 | クラスターバスの Gossip プロトコル |
| フェイルオーバー | Sentinel のリーダーが実行 | レプリカ自身が昇格 (過半数の投票) |
| クライアントの複雑さ | Sentinel-aware が必要 | MOVED/ASK リダイレクションの処理が必要 |
| マルチキーコマンド | 無制限 | 同じハッシュスロット内でのみ可能 |
| 最大データサイズ | 単一ノードのメモリ上限 | ノード数に比例して拡張 |
| 運用の複雑さ | 中程度 | 高い |
| 向いているシナリオ | 単一データセットの HA | 大規模データ + 高スループット |
どちらをいつ選ぶか
# 意思決定フロー
#
# Q1: 全データが単一ノードのメモリ (例: 64GB) に収まるか?
# YES -> Q2 へ進む
# NO -> Cluster モードが必須
#
# Q2: 書き込みスループットは単一マスターで足りるか?
# YES -> Sentinel モードを推奨
# NO -> Cluster モードが必要
#
# Q3: マルチキートランザクション (MULTI/EXEC) は頻繁か?
# YES -> Sentinel を優先 (Cluster ではハッシュタグ設計が必要)
# NO -> どちらのモードでも可
#
# Q4: 運用チームの規模とインフラ経験は?
# 小規模 -> Sentinel (運用がシンプル)
# 専任 -> Cluster (複雑だが強力)
Sentinel の構成と運用
Sentinel の設定ファイル
Sentinel は最低 3 台の独立したプロセスとしてデプロイする必要がある。各 Sentinel はマスターを監視し、障害発生時にはクォーラム (quorum) にもとづく合意によってフェイルオーバーを実行する。
# sentinel.conf - 基本構成
port 26379
daemonize yes
logfile "/var/log/redis/sentinel.log"
dir "/var/lib/redis/sentinel"
# マスター監視の設定
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 10.0.1.10 6379 2
# マスターが無応答と判定されるまでの時間 (ミリ秒)
sentinel down-after-milliseconds mymaster 5000
# フェイルオーバーのタイムアウト (ミリ秒)
# この時間内にフェイルオーバーが完了しなければ再試行される
sentinel failover-timeout mymaster 60000
# 新しいマスターへ同時に同期するレプリカ数
# 小さいほど安全 (同期中のレプリカは読み取りに使えない)
sentinel parallel-syncs mymaster 1
# 認証が必要な場合
sentinel auth-pass mymaster StrongP@ssw0rd!
# Sentinel 自体の認証 (Redis 6.2+)
requirepass SentinelP@ss!
# 通知スクリプト (障害発生時に実行)
sentinel notification-script mymaster /opt/redis/notify.sh
# フェイルオーバー完了後に実行するスクリプト
sentinel client-reconfig-script mymaster /opt/redis/reconfig.sh
マスター Redis の設定
# redis.conf - Master の設定
bind 0.0.0.0
port 6379
requirepass StrongP@ssw0rd!
masterauth StrongP@ssw0rd!
# レプリケーション関連の設定
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes
repl-diskless-sync-delay 5
# 永続化の設定
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# メモリの設定
maxmemory 4gb
maxmemory-policy allkeys-lru
# ネットワークのチューニング
tcp-backlog 511
tcp-keepalive 300
timeout 0
フェイルオーバーの動作原理
Sentinel の障害検知とフェイルオーバーは次の段階で進む:
# === 段階 1: SDOWN (Subjective Down) ===
# 個々の Sentinel が down-after-milliseconds のあいだマスターの無応答を検知する
# この時点ではその Sentinel だけの主観的な判断
# Sentinel のログを確認
# +sdown master mymaster 10.0.1.10 6379
# === 段階 2: ODOWN (Objective Down) ===
# quorum 以上の Sentinel が SDOWN に同意すると ODOWN に切り替わる
# これが客観的な障害判定
# +odown master mymaster 10.0.1.10 6379 #quorum 2/2
# === 段階 3: Sentinel リーダーの選出 ===
# Raft ベースのアルゴリズムでフェイルオーバーを実行するリーダーを選出する
# majority (過半数) の投票が必要
# Sentinel 3 台 -> 最低 2 票が必要
# === 段階 4: レプリカの選択 ===
# リーダー Sentinel が最適なレプリカを選ぶ
# 選択基準 (優先順位順):
# 1. replica-priority の値が最も小さいノード (0 は除外)
# 2. レプリケーションオフセットが最も大きいノード (データが最も新しい)
# 3. run ID が辞書順で最も小さいノード
# === 段階 5: フェイルオーバーの実行 ===
# 選ばれたレプリカに REPLICAOF NO ONE コマンドを送る
# 残りのレプリカを新しいマスターに接続する
# Sentinel の設定ファイルが自動更新される
# フェイルオーバー状態の監視
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
redis-cli -p 26379 SENTINEL replicas mymaster
redis-cli -p 26379 SENTINEL sentinels mymaster
Python から Sentinel へ接続する
import redis
from redis.sentinel import Sentinel
# Sentinel インスタンスの一覧で接続
sentinel = Sentinel(
[
("10.0.1.20", 26379),
("10.0.1.21", 26379),
("10.0.1.22", 26379),
],
socket_timeout=0.5,
sentinel_kwargs={"password": "SentinelP@ss!"},
)
# マスターアドレスの確認
master_host, master_port = sentinel.discover_master("mymaster")
print(f"Current master: {master_host}:{master_port}")
# マスター接続 (書き込み用)
master = sentinel.master_for(
"mymaster",
socket_timeout=0.5,
password="StrongP@ssw0rd!",
db=0,
)
# レプリカ接続 (読み取り用)
replica = sentinel.slave_for(
"mymaster",
socket_timeout=0.5,
password="StrongP@ssw0rd!",
db=0,
)
# 書き込みはマスターへ
master.set("session:user:1001", "active")
# 読み取りはレプリカへ (読み取り負荷の分散)
session = replica.get("session:user:1001")
print(f"Session status: {session}")
# フェイルオーバー発生時は自動的に新しいマスターへ再接続される
# redis-py の SentinelConnectionPool が自動処理する
try:
master.set("key", "value")
except redis.exceptions.ConnectionError:
# Sentinel が新しいマスターを知らせる -> 自動再接続
print("Failover detected, reconnecting...")
master.set("key", "value") # 再試行時は新しいマスターに接続される
Cluster モードの構成と運用
Cluster ノードの設定
# redis-cluster.conf - 各ノード共通の設定
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
# クラスターバスのポート (既定: port + 10000)
# cluster-port 17000
# 認証
requirepass ClusterP@ss!
masterauth ClusterP@ss!
# レプリケーションの設定
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes
# 永続化
appendonly yes
appendfsync everysec
save 900 1
save 300 10
# メモリ
maxmemory 8gb
maxmemory-policy allkeys-lru
# ネットワーク
bind 0.0.0.0
tcp-backlog 511
tcp-keepalive 300
# データの一部でも使用不可のときクラスター全体をダウンさせるか
# yes: 一部のスロットでもカバーされなければ全体を拒否
# no: カバーされているスロットのキーだけ提供する (推奨)
cluster-require-full-coverage no
# レプリカがないマスターへ、他マスターの余剰レプリカを自動的に移す
cluster-allow-replica-migration yes
Cluster の作成と管理コマンド
# 6 ノードでクラスターを作成 (3 Master + 3 Replica)
redis-cli -a ClusterP@ss! --cluster create \
10.0.1.10:7000 10.0.1.11:7001 10.0.1.12:7002 \
10.0.1.10:7003 10.0.1.11:7004 10.0.1.12:7005 \
--cluster-replicas 1
# クラスター情報の確認
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER INFO
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_size:3
# cluster_known_nodes:6
# ノード一覧の確認
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER NODES
# <node-id> 10.0.1.10:7000@17000 myself,master - 0 0 1 connected 0-5460
# <node-id> 10.0.1.11:7001@17001 master - 0 1234567890 2 connected 5461-10922
# <node-id> 10.0.1.12:7002@17002 master - 0 1234567890 3 connected 10923-16383
# ...
# スロット分配の確認
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER SLOTS
# クラスター状態の点検
redis-cli -a ClusterP@ss! --cluster check 10.0.1.10:7000
# === ノードの追加 ===
# 新しいマスターノードを追加
redis-cli -a ClusterP@ss! --cluster add-node \
10.0.1.13:7006 10.0.1.10:7000
# 新しいレプリカノードを追加 (特定マスターのレプリカとして)
redis-cli -a ClusterP@ss! --cluster add-node \
10.0.1.13:7007 10.0.1.10:7000 \
--cluster-slave --cluster-master-id <master-node-id>
# === リシャーディング ===
# 既存マスターから新しいマスターへスロットを移動
redis-cli -a ClusterP@ss! --cluster reshard 10.0.1.10:7000 \
--cluster-from <source-node-id> \
--cluster-to <target-node-id> \
--cluster-slots 4096 \
--cluster-yes
# 自動リバランス (スロットの均等分配)
redis-cli -a ClusterP@ss! --cluster rebalance 10.0.1.10:7000 \
--cluster-threshold 2 \
--cluster-use-empty-masters
# === ノードの削除 ===
# まずスロットを他ノードへ移動してから削除する
redis-cli -a ClusterP@ss! --cluster del-node \
10.0.1.10:7000 <node-id-to-remove>
ハッシュスロットとハッシュタグ
Redis Cluster は CRC16 ハッシュ関数を使い、キーを 16384 個のスロットのいずれかにマッピングする。マルチキーコマンド (MGET、MSET、パイプライン) を使うには、関連するキーが同じスロットにある必要がある。
import redis
from redis.cluster import RedisCluster
# Cluster クライアントの接続
rc = RedisCluster(
startup_nodes=[
{"host": "10.0.1.10", "port": 7000},
{"host": "10.0.1.11", "port": 7001},
{"host": "10.0.1.12", "port": 7002},
],
password="ClusterP@ss!",
decode_responses=True,
# MOVED/ASK リダイレクションを自動処理
skip_full_coverage_check=True,
)
# 基本的な使い方
rc.set("user:1001:name", "Kim")
rc.set("user:1001:email", "kim@example.com")
# ハッシュタグを使って同じスロットに配置する
# 波かっこ内の文字列でスロットが決まる
rc.set("{user:1001}:name", "Kim")
rc.set("{user:1001}:email", "kim@example.com")
rc.set("{user:1001}:session", "abc123")
# これで MGET により一度に取得できる (同じスロット)
values = rc.mget(
"{user:1001}:name",
"{user:1001}:email",
"{user:1001}:session",
)
print(values) # ['Kim', 'kim@example.com', 'abc123']
# パイプラインも同じスロット内でなら使える
pipe = rc.pipeline()
pipe.hset("{order:5001}:info", "status", "pending")
pipe.hset("{order:5001}:info", "amount", "15000")
pipe.expire("{order:5001}:info", 3600)
pipe.execute()
# スロットの確認
slot = rc.cluster_keyslot("{user:1001}:name")
print(f"Slot: {slot}")
# クラスター情報の取得
info = rc.cluster_info()
print(f"Cluster state: {info['cluster_state']}")
print(f"Known nodes: {info['cluster_known_nodes']}")
Cluster のフェイルオーバー機構
Redis Cluster のフェイルオーバーは、Sentinel なしでクラスターノード自身によって実行される。
# === Cluster の障害検知フロー ===
# 1. Gossip プロトコル
# すべてのノードはクラスターバス (port+10000) を通じて
# 毎秒ランダムなノードへ PING を送り、PONG 応答を確認する
# 2. PFAIL (Probable Fail)
# cluster-node-timeout のあいだ PONG を受信できなければ
# そのノードを PFAIL として記録する (主観的な判断)
# 3. FAIL (確定した障害)
# 過半数のマスターが PFAIL に同意すると FAIL に切り替わる
# クラスター全体に FAIL メッセージをブロードキャストする
# 4. レプリカの昇格
# 障害マスターのレプリカが選挙を開始する
# 他のマスターが投票する (過半数が必要)
# 当選したレプリカが新しいマスターへ昇格する
# === 手動フェイルオーバー ===
# レプリカノードで実行する (計画的なメンテナンス時)
redis-cli -a ClusterP@ss! -h 10.0.1.10 -p 7003 CLUSTER FAILOVER
# TAKEOVER: 他マスターの同意なしに強制昇格させる (緊急時のみ)
redis-cli -a ClusterP@ss! -h 10.0.1.10 -p 7003 CLUSTER FAILOVER TAKEOVER
# フェイルオーバー後のクラスター状態を確認
redis-cli -a ClusterP@ss! --cluster check 10.0.1.10:7000
障害シミュレーションと復旧スクリプト
プロダクションへデプロイする前に、必ず障害シナリオをシミュレーションして復旧手順を検証しておく必要がある。
#!/bin/bash
# failover-simulation.sh - Redis 障害シミュレーションと検証スクリプト
REDIS_CLI="redis-cli -a ClusterP@ss!"
MASTER_HOST="10.0.1.10"
MASTER_PORT=7000
echo "=== Step 1: 現在のクラスター状態を確認 ==="
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT CLUSTER INFO | head -5
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT CLUSTER NODES
echo ""
echo "=== Step 2: テストデータの投入 ==="
for i in $(seq 1 100); do
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT \
SET "test:failover:$i" "value_$i" EX 300 > /dev/null 2>&1
done
echo "100 件のテストキーの投入が完了"
echo ""
echo "=== Step 3: マスタープロセスを強制終了 (障害シミュレーション) ==="
# 注意: プロダクションでは絶対に実行しないこと
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT DEBUG SLEEP 30 &
# あるいは実際のプロセスを終了させる
# ssh $MASTER_HOST "redis-cli -p $MASTER_PORT -a ClusterP@ss! SHUTDOWN NOSAVE"
echo "マスターノードの障害シミュレーションを開始"
echo "cluster-node-timeout (5 秒) を待機中..."
sleep 10
echo ""
echo "=== Step 4: フェイルオーバー後のクラスター状態を確認 ==="
# 別のノードを通じてクラスター状態を確認する
$REDIS_CLI -c -h 10.0.1.11 -p 7001 CLUSTER INFO | head -5
$REDIS_CLI -c -h 10.0.1.11 -p 7001 CLUSTER NODES
echo ""
echo "=== Step 5: データ整合性の検証 ==="
FOUND=0
MISSING=0
for i in $(seq 1 100); do
RESULT=$($REDIS_CLI -c -h 10.0.1.11 -p 7001 \
GET "test:failover:$i" 2>/dev/null)
if [ -n "$RESULT" ]; then
FOUND=$((FOUND + 1))
else
MISSING=$((MISSING + 1))
fi
done
echo "検証結果: 見つかったキー=$FOUND, 失われたキー=$MISSING"
echo ""
echo "=== Step 6: 障害ノードの復旧 ==="
# 障害ノードをレプリカとして再合流させる
echo "障害ノードを再起動すると自動的にレプリカとして合流します。"
echo "手動確認: redis-cli --cluster check 10.0.1.11:7001"
運用上の注意点と失敗事例
事例 1: スプリットブレイン (Split Brain)
スプリットブレインとは、ネットワーク分断によって 2 つ以上のノードが同時にマスターの役割を担う状況だ。これはデータの不整合と消失を招く。
発生シナリオ: Sentinel 3 台のうち 2 台がマスターとのネットワークを失って新しいマスターを選出したが、既存のマスターは依然としてクライアントの書き込みを受け付けている状況
予防設定:
# redis.conf - スプリットブレインの防止
# レプリカが最低 N 台つながっているときだけ書き込みを許可する
min-replicas-to-write 1
# レプリカの最大遅延時間 (秒)
# この時間以上 ACK がなければ接続が切れたものとみなす
min-replicas-max-lag 10
# 上の設定の意味:
# マスターがネットワークから切り離されると、
# 接続レプリカが 0 台になるため書き込みを拒否する
# -> 既存マスターに書かれたデータが新しいマスターと衝突するのを防ぐ
実際の障害事例: A 社のプロダクション環境で、ネットワーク機器の交換中に一時的な分断が発生した。min-replicas-to-write が未設定だったため両方のマスターが書き込みを受け付け、フェイルオーバー完了後に既存マスターのデータ (約 30 秒分) が失われた。その後、上記の設定を適用して再発を防いだ。
事例 2: メモリ超過 (OOM)
# 問題の状況: maxmemory が未設定で Redis がシステムメモリを使い切った
# Linux の OOM Killer が Redis プロセスを強制終了させた
# 確認コマンド
redis-cli INFO memory
# used_memory_human:12.45G
# used_memory_peak_human:14.23G
# maxmemory_human:0B <- 上限なし! 危険!
# mem_fragmentation_ratio:1.35
# 予防設定
# redis.conf
maxmemory 8gb
maxmemory-policy allkeys-lru
# メモリ使用量の監視スクリプト
# crontab -e
# */5 * * * * /opt/redis/check_memory.sh
# check_memory.sh の例
# USED=$(redis-cli INFO memory | grep used_memory_bytes | cut -d: -f2 | tr -d '\r')
# MAX=8589934592 # 8GB
# RATIO=$(echo "scale=2; $USED * 100 / $MAX" | bc)
# if (( $(echo "$RATIO > 80" | bc -l) )); then
# echo "WARNING: Redis memory usage at ${RATIO}%" | \
# mail -s "Redis Memory Alert" ops@company.com
# fi
事例 3: Cluster リシャーディング中のタイムアウト
リシャーディング中に大量のキー移動が起きると、クライアントのリクエストは ASK リダイレクションを通じて処理されるため、このとき一時的な遅延が発生することがある。
# リシャーディング中のクラスター状態を確認
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER NODES
# IMPORTING/MIGRATING 状態のスロットが見えればリシャーディング進行中
# リシャーディング速度の調整 (大規模環境で推奨)
redis-cli -a ClusterP@ss! --cluster reshard 10.0.1.10:7000 \
--cluster-from <source-id> \
--cluster-to <target-id> \
--cluster-slots 500 \
--cluster-timeout 10000 \
--cluster-pipeline 100 \
--cluster-yes
# リシャーディングが異常終了した場合の復旧
redis-cli -a ClusterP@ss! --cluster fix 10.0.1.10:7000
事例 4: Sentinel 設定ファイルの権限問題
Sentinel はフェイルオーバー時に設定ファイルを自動更新する。設定ファイルに書き込み権限がないと、フェイルオーバー自体は成功するものの、Sentinel の再起動時に古いマスター情報を参照して誤ったノードへ接続する問題が起きる。
# sentinel.conf の権限を確認
ls -la /etc/redis/sentinel.conf
# -rw-r--r-- 1 redis redis 1234 Mar 14 10:00 sentinel.conf
# Redis ユーザーに書き込み権限を付与
chown redis:redis /etc/redis/sentinel.conf
chmod 640 /etc/redis/sentinel.conf
# Sentinel が設定ファイルを書き換えていることを確認
# フェイルオーバー後に sentinel.conf のマスター IP が変わったかをチェック
grep "sentinel monitor" /etc/redis/sentinel.conf
パフォーマンスチューニングのチェックリスト
ネットワークと OS レベル
# /etc/sysctl.conf - カーネルパラメータのチューニング
# TCP バックログサイズの拡大
net.core.somaxconn = 65535
# メモリのオーバーコミットを許可 (Redis の fork 時に必要)
vm.overcommit_memory = 1
# THP (Transparent Huge Pages) の無効化
# Redis は THP によってメモリ使用量が急増し、latency が増加することがある
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
# ファイルディスクリプタ上限の引き上げ
# /etc/security/limits.conf
# redis soft nofile 65536
# redis hard nofile 65536
Redis レベル
# 1. スローログの設定 (10ms 以上かかったコマンドを記録)
slowlog-log-slower-than 10000
slowlog-max-len 128
# スローログの確認
redis-cli SLOWLOG GET 10
redis-cli SLOWLOG LEN
redis-cli SLOWLOG RESET
# 2. latency モニタリングの有効化
latency-monitor-threshold 100
# latency イベントの確認
redis-cli LATENCY LATEST
redis-cli LATENCY HISTORY event-name
# 3. クライアント出力バッファの制限
# レプリカが遅いときにマスターのメモリが膨張するのを防ぐ
client-output-buffer-limit replica 256mb 64mb 60
# 4. コネクションプールの設定 (クライアント側)
# maxclients の既定値は 10000
maxclients 10000
プロダクション運用チェックリスト
デプロイ前に必ず以下の項目を点検する:
構成段階
- Sentinel と Cluster モードのうち適切なほうを選んだか
- 最小ノード数を満たしているか (Sentinel 3+、Cluster 6+)
- すべてのノードで認証 (requirepass、masterauth) が設定されているか
maxmemoryと適切な追い出しポリシーが設定されているか- 永続化戦略 (RDB/AOF) が構成されているか
min-replicas-to-writeでスプリットブレインを防いでいるか
モニタリング
- 各ノードのメモリ使用量を監視しているか
- レプリケーション遅延 (replication lag) を追跡しているか
- スローログを定期的に確認しているか
- Sentinel/Cluster のイベントに対する通知が設定されているか
- クラスター状態 (cluster_state) をヘルスチェックに含めているか
障害への備え
- フェイルオーバーのシミュレーションを行い、復旧時間を計測したか
- バックアップと復元の手順が文書化されているか
- ネットワーク分断のシナリオをテストしたか
- すべてのノードで設定ファイルの書き込み権限が正しいか
- クライアントライブラリがフェイルオーバー時の自動再接続に対応しているか
OS レベル
vm.overcommit_memory = 1が設定されているか- Transparent Huge Pages が無効化されているか
- ファイルディスクリプタの上限は十分か
net.core.somaxconnが Redis のtcp-backlogより大きいか- swap の使用を最小化するよう
vm.swappinessを調整したか
モニタリングと通知の構成
#!/usr/bin/env python3
"""Redis クラスターのヘルスチェックおよび通知スクリプト"""
import redis
from redis.cluster import RedisCluster
import smtplib
from email.mime.text import MIMEText
import json
import time
def check_cluster_health(hosts, password):
"""クラスター全体の状態を点検する。"""
alerts = []
try:
rc = RedisCluster(
startup_nodes=[{"host": h, "port": p} for h, p in hosts],
password=password,
decode_responses=True,
socket_timeout=3,
)
# 1. クラスター状態の確認
info = rc.cluster_info()
if info.get("cluster_state") != "ok":
alerts.append(
f"CRITICAL: cluster_state = {info.get('cluster_state')}"
)
# 2. スロットカバレッジの確認
slots_ok = int(info.get("cluster_slots_ok", 0))
if slots_ok < 16384:
alerts.append(
f"CRITICAL: スロットカバレッジ不足 - {slots_ok}/16384"
)
# 3. 各ノードのメモリ使用量を確認
for node in rc.cluster_nodes():
node_info = rc.info(target_nodes=node)
used = node_info.get("used_memory", 0)
maxmem = node_info.get("maxmemory", 0)
if maxmem > 0:
usage_pct = (used / maxmem) * 100
if usage_pct > 85:
alerts.append(
f"WARNING: ノードのメモリを {usage_pct:.1f}% 使用中"
)
# 4. レプリケーション遅延の確認
for node in rc.cluster_nodes():
repl_info = rc.info(section="replication", target_nodes=node)
lag = repl_info.get("master_repl_offset", 0)
if lag > 1000000: # 1MB 以上の遅延
alerts.append(
f"WARNING: レプリケーション遅延を検知 - offset lag: {lag}"
)
except redis.exceptions.ConnectionError as e:
alerts.append(f"CRITICAL: クラスターへの接続に失敗 - {str(e)}")
except Exception as e:
alerts.append(f"ERROR: ヘルスチェックに失敗 - {str(e)}")
return alerts
def send_alert(alerts, smtp_config):
"""通知メールの送信"""
if not alerts:
return
body = "Redis クラスターの通知:\n\n"
body += "\n".join(f" - {a}" for a in alerts)
body += f"\n\n点検時刻: {time.strftime('%Y-%m-%d %H:%M:%S')}"
msg = MIMEText(body)
msg["Subject"] = f"[Redis Alert] {len(alerts)} 件のイシューを検知"
msg["From"] = smtp_config["from"]
msg["To"] = smtp_config["to"]
with smtplib.SMTP(smtp_config["host"], smtp_config["port"]) as server:
server.send_message(msg)
if __name__ == "__main__":
CLUSTER_HOSTS = [
("10.0.1.10", 7000),
("10.0.1.11", 7001),
("10.0.1.12", 7002),
]
alerts = check_cluster_health(CLUSTER_HOSTS, "ClusterP@ss!")
if alerts:
print("Issues detected:")
for a in alerts:
print(f" {a}")
# send_alert(alerts, SMTP_CONFIG)
else:
print("Cluster health: OK")
レプリケーション詳解: PSYNC と部分同期
Redis のレプリケーションはフル同期 (Full Sync) と部分同期 (Partial Sync、PSYNC) に分かれる。レプリカが一時的に接続を失って再接続するとき、PSYNC で差分だけを送れば、全データを送り直すコストを減らせる。
# レプリケーションバックログの設定 (マスター)
# 部分同期のためのメモリバッファ
repl-backlog-size 128mb
# バックログの保持時間 (すべてのレプリカが切断されたあと)
repl-backlog-ttl 3600
# ディスクレスレプリケーション (ネットワークがディスクより速いときに有利)
repl-diskless-sync yes
repl-diskless-sync-delay 5
repl-diskless-sync-period 0
# レプリケーション状態の確認
redis-cli INFO replication
# role:master
# connected_slaves:2
# slave0:ip=10.0.1.11,port=6379,state=online,offset=1234567,lag=0
# slave1:ip=10.0.1.12,port=6379,state=online,offset=1234567,lag=1
# master_replid:abc123...
# master_repl_offset:1234567
# repl_backlog_active:1
# repl_backlog_size:134217728
PSYNC が失敗してフル同期が発生すると、マスターで RDB スナップショットの生成が始まり、その過程で CPU とメモリを大きく消費する。repl-backlog-size を十分に大きく設定することが要点だ。毎秒の書き込み量 (MB/s) と想定される切断時間 (秒) を掛けた値以上に設定することを推奨する。
永続化戦略: RDB と AOF の比較
| 項目 | RDB (スナップショット) | AOF (Append Only File) |
|---|---|---|
| 方式 | 定期的な全体スナップショット | すべての書き込みコマンドを記録 |
| データの安全性 | 最後のスナップショット以降は消失しうる | fsync ポリシー次第で消失は最小 |
| ファイルサイズ | 小さい (圧縮) | 大きい (コマンドログ) |
| 復旧速度 | 速い | 遅い (コマンドの再実行) |
| 性能への影響 | fork 時に一時的な遅延 | fsync ごとに I/O が発生 |
| 推奨の組み合わせ | AOF と併用 | RDB と併用 |
# 推奨: RDB + AOF の併用
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# AOF rewrite のトリガー条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 混合モード (Redis 4.0+、推奨)
# AOF rewrite 時に RDB フォーマット + 以降の AOF ログを混在させる
aof-use-rdb-preamble yes
まとめ
Redis の高可用性構成は、単に Sentinel や Cluster を有効にする以上の深い理解を求める。クォーラムと過半数の違い、ハッシュスロットの分配原理、スプリットブレインの防止戦略、レプリケーションバックログのサイズ算定、永続化戦略の選択まで、すべての要素が有機的につながっている。
プロダクションへデプロイする前に必ず障害シミュレーションを実施し、監視体制を構築し、上で示したチェックリストをすべて点検すること。とくに min-replicas-to-write と maxmemory の設定は、最も頻繁な障害原因を事前に断つ中核の設定なので、必ず適用してほしい。
参考資料
- Redis Sentinel Documentation - Sentinel のアーキテクチャと構成ガイド
- Redis Cluster Specification - Cluster プロトコルの仕様
- Redis Cluster Tutorial - Cluster の構成と運用チュートリアル
- Redis Replication - レプリケーションの仕組みと PSYNC
- Redis Persistence - RDB と AOF の永続化戦略
- redis-py Documentation - Python Redis クライアントの公式ドキュメント
- Redis Administration - 運用管理ガイド
- Redis Latency Problems Troubleshooting - 遅延問題の診断ガイド