LabHub

ブログ

Redis Cluster高可用性とフェイルオーバーガイド

한국어English日本語

はじめに: なぜいま Redis の高可用性が重要なのか

Redis はインメモリデータストアとして、キャッシュ、セッション管理、リアルタイムのリーダーボード、メッセージキューなど、現代のアプリケーションの中核インフラとして定着した。2026 年現在、マイクロサービスアーキテクチャが一般化するにつれて Redis への依存度はさらに高まり、単一 Redis インスタンスの障害がシステム全体のダウンタイムに直結する事例が頻発している。

とくに次のような状況では、Redis の高可用性構成が欠かせない:

この記事では、Redis の 2 つの高可用性戦略である Sentinel モードと Cluster モードを深く比較し、実際のプロダクション環境での構成方法、障害検知と自動フェイルオーバーの仕組み、そして実践的なトラブルシューティング事例までを扱う。

公式ドキュメントと一次ソース

この記事で参照する Redis の公式ドキュメントと一次ソースは次のとおり:

  1. Redis Sentinel Documentation - https://redis.io/docs/management/sentinel/
  2. Redis Cluster Specification - https://redis.io/docs/reference/cluster-spec/
  3. Redis Cluster Tutorial - https://redis.io/docs/management/scaling/
  4. Redis Replication - https://redis.io/docs/management/replication/
  5. Redis Persistence (RDB/AOF) - https://redis.io/docs/management/persistence/
  6. redis-py Cluster Client - https://redis-py.readthedocs.io/en/stable/clustering.html

Sentinel モードと Cluster モードの比較

アーキテクチャ比較表

項目SentinelCluster
目的高可用性 (HA)高可用性 + 水平スケーリング
最小ノード数Sentinel 3 + Master 1 + Replica 1Master 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

プロダクション運用チェックリスト

デプロイ前に必ず以下の項目を点検する:

構成段階

モニタリング

障害への備え

OS レベル

モニタリングと通知の構成

#!/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-writemaxmemory の設定は、最も頻繁な障害原因を事前に断つ中核の設定なので、必ず適用してほしい。

参考資料

コメント

まだコメントはありません。

ログインするとコメントできます