深夜のキャッシュ障害
金曜日の午前2時、トラフィックが毎秒15,000リクエストに急増したとします。プライマリのRedisインスタンスがメモリ不足に陥りクラッシュしました。もしスタンドアロンのRedisノードを運用していた場合、事態は急速に悪化します。ユーザーセッションは消失し、APIレートリミットは機能しなくなり、毎分50,000件ものバックエンドデータベースクエリが一斉にPostgreSQLやMySQLデータベースへと押し寄せます。この連鎖的な障害こそが、典型的なキャッシュスタンピード現象です。
Redisはインメモリキャッシュやセッション管理に極めて優れています。しかし、スタンドアロン構成のRedisには自動復旧機能が一切ありません。標準的なマスター・レプリカ構成のレプリケーションは読み取り負荷をオフロードできますが、マスターが停止した際に書き込み処理が不能になります。オーケストレーション機構がなければ、レプリカが自動的にマスターへ昇格することはありません。エンジニアが夜中に叩き起こされ、サーバーにログインし、REPLICAOF NO ONEを実行した上で、稼働中のコンテナやPodすべてのアプリケーション接続文字列を更新しなければならないのです。
このような手動対応には通常15分から30分もの手痛いダウンタイムが伴います。Sentinelはクラスターを常時監視し、健全なレプリカを数秒以内に昇格させることで、このボトルネックを解消します。
Redis Sentinelによるフェイルオーバーの仕組み
Redis Sentinelは、データベースインスタンスと並行して動作する軽量な監視プロセスです。Sentinelは主に次の4つの役割を担います。
- ヘルス監視(Health Monitoring): 定期的な
PINGコマンドにより、マスターおよびレプリカに対して1秒に1回疎通確認を行います。 - アラート通知(Alerting): ノードの状態が変化した際に、Pub/Subチャネルを介して通知を発行します。
- 自動フェイルオーバー(Automated Failover): 現在のリーダーが応答しなくなった場合、レプリケーションオフセットが最も進んでいるレプリカをマスターに昇格させます。
- 設定プロバイダー(Configuration Provider): クライアントに対して現在書き込みを受け付けているインスタンスを通知し、アプリケーション側にIPアドレスをハードコードする必要をなくします。
クォーラム(Quorum)とスプリットブレイン対策
本番環境では、異なる障害ドメインに配置された最低3つのSentinelノードが必要です。Sentinelは、インスタンスが客観的にダウンしている(ODOWN)と判定するためにクォーラム(Quorum)の閾値を用います。単一のSentinelがマスターとの通信をdown-after-milliseconds(例:5,000ミリ秒)以上失った場合、そのノードを主観的ダウン(SDOWN)としてマークします。
3台のうち2台のSentinelが障害に合意すると、Raftに似た合意プロトコルによってリーダー選出が開始されます。選出された1台のSentinelがリーダーとなり、昇格コマンドを発行して残りのレプリカを再構成します。3つのノードを使用することで、ネットワーク分断により誤って2つのマスターが競合してしまうスプリットブレイン状態を防止できます。
Deploying Redis Sentinel with Docker Compose
Dockerコンテナのネットワーキングは抽象化レイヤーを追加します。Sentinelは実行時に自身の設定ファイルを書き換えるため、各Sentinelコンテナは単一のホストマウントを共有するのではなく、それぞれ独立した書き込み可能な設定ファイルを必要とします。以下は、1台のマスター、2台のレプリカ、3台のSentinelで構成される実践的なセットアップです。
1. ディレクトリ構造
独立したディレクトリを用意し、専用のSentinel設定ファイルを3つ生成します。
mkdir -p redis-sentinel-docker/{sentinel,redis}
cd redis-sentinel-docker
2. マスターおよびレプリカのRedis設定
redis/redis.confを作成します。共通の認証パスワードですべてのデータベースノードを保護します。
# redis/redis.conf
protected-mode no
requirepass SecretAuth2026!
masterauth SecretAuth2026!
appendonly yes
3. Sentinel設定ファイル
Sentinelは状態が変化すると動的に自身の設定を変更します。同一の初期パラメータを持つ3つの個別ファイル(sentinel/sentinel-1.conf、sentinel/sentinel-2.conf、sentinel/sentinel-3.conf)を作成します。
# sentinel/sentinel-1.conf(2および3でも同様に作成)
port 26379
protected-mode no
dir /tmp
# monitor <マスターグループ名> <ホスト> <ポート> <クォーラム>
sentinel monitor mymaster redis-master 6379 2
sentinel auth-pass mymaster SecretAuth2026!
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1
4. Docker Composeの設定
共有ブリッジネットワーク上で動作する全6ノードをdocker-compose.ymlに定義します。
version: '3.8'
networks:
redis-net:
driver: bridge
services:
redis-master:
image: redis:7.2-alpine
container_name: redis-master
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
ports:
- "6379:6379"
networks:
- redis-net
redis-replica-1:
image: redis:7.2-alpine
container_name: redis-replica-1
command: ["redis-server", "/usr/local/etc/redis/redis.conf", "--replicaof", "redis-master", "6379"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
depends_on:
- redis-master
networks:
- redis-net
redis-replica-2:
image: redis:7.2-alpine
container_name: redis-replica-2
command: ["redis-server", "/usr/local/etc/redis/redis.conf", "--replicaof", "redis-master", "6379"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
depends_on:
- redis-master
networks:
- redis-net
sentinel-1:
image: redis:7.2-alpine
container_name: sentinel-1
command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
volumes:
- ./sentinel/sentinel-1.conf:/etc/redis/sentinel.conf
depends_on:
- redis-master
- redis-replica-1
- redis-replica-2
ports:
- "26379:26379"
networks:
- redis-net
sentinel-2:
image: redis:7.2-alpine
container_name: sentinel-2
command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
volumes:
- ./sentinel/sentinel-2.conf:/etc/redis/sentinel.conf
depends_on:
- redis-master
- redis-replica-1
- redis-replica-2
ports:
- "26380:26379"
networks:
- redis-net
sentinel-3:
image: redis:7.2-alpine
container_name: sentinel-3
command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
volumes:
- ./sentinel/sentinel-3.conf:/etc/redis/sentinel.conf
depends_on:
- redis-master
- redis-replica-1
- redis-replica-2
ports:
- "26381:26379"
networks:
- redis-net
5. クラスター構成の検証
デタッチドモードで環境を起動します。
docker compose up -d
マスターが両方のレプリカを認識していることを確認します。
docker exec -it redis-master redis-cli -a SecretAuth2026! info replication
コマンドの出力にrole:masterが表示され、connected_replicas:2であることが確認できます。次に、sentinel-1に問い合わせてクラスター監視の状態を検証します。
docker exec -it sentinel-1 redis-cli -p 26379 sentinel master mymaster
レスポンスには、マスターのコンテナIPとともにnum-slaves: 2およびnum-other-sentinels: 2が表示されます。
6. フェイルオーバーシーケンスのテスト
コンテナを停止して、予期せぬマスター障害をシミュレートします。
docker stop redis-master
直ちにsentinel-1のログを追跡し、遷移の様子を観察します。
docker logs -f sentinel-1
5〜7秒以内に、ログにリーダー選出のライフサイクルが順次出力されます。
+sdown master mymaster 172.20.0.2 6379: ローカルのpingタイムアウト(5,000ミリ秒)に到達。+odown master mymaster 172.20.0.2 6379 #quorum 2/2: 他のSentinelノードによりクォーラムが確認され、客観的ダウンと判定。+vote-for-leader ...: Sentinel間でフェイルオーバーのコーディネーターを選出。+promoted-slave ...: レプリカ1またはレプリカ2がマスターに昇格。+switch-master mymaster ...: Sentinelがプライマリの接続先を新しいIPに再バインド。
Sentinel経由で現在のアクティブなマスターを確認します。
docker exec -it sentinel-1 redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
Sentinelは新しく昇格したコンテナのIPアドレスを返します。docker start redis-masterを実行して元のマスターを再起動すると、Sentinelは即座にそれを新マスター配下のレプリカとして再構成し、書き込みの整合性を保護します。
アプリケーションとの統合
フェイルオーバー発生時、IPアドレスへの直接接続は切断されます。そのため、アプリケーション側ではSentinel対応のクライアントライブラリ(Node.jsのioredis、PythonのSentinelヘルパーを備えたredis-py、JavaのSpring Data Redisなど)を使用する必要があります。
これらのドライバーは、まずSentinelのポート(26379、26380、26381)に接続して現在のマスターを動的に解決し、+switch-masterイベントをサブスクライブします。ノードで障害が発生した際も、アプリケーションは自動的に新しいリーダーへと再接続するため、書き込みの中断をわずか数秒に抑えることができます。

