Redisレプリケーションの要塞設計:PSYNCの内部構造と障害に強いトポロジー構築
こんにちは。開発チームの設計レビューをしていると、Redisを単なる「高速なキーバリューストア」として捉え、レプリケーションやフェイルオーバーの挙動をブラックボックスにしたまま本番稼働させているケースに遭遇します。
「マスターが落ちたらレプリカ昇格させればいいんでしょ?」
「フルシンクロナイゼーション?バックアップ用途なんだから初回くらい遅くてもいいよ」
――もし、あなたが本番環境の可用性を担保する立場なら、この認識は今すぐ捨ててください。
大規模トラフィックを捌くシステムにおいて、Redisのレプリケーション機構、特に`PSYNC`プロトコルの内部状態遷移とバックプレッシャーの理解不足は、ネットワーク分断時のスプリットブレインや、マスター再起動時の雪崩のようなCPUスパイク(Replication Storm)を引き起こす致命傷になります。
今回は、Redisのレプリケーションが内部でどう動き、実務の現場でどう設計・運用すべきか、チーフアーキテクトの視点から容赦なく解説します。
—
1. マスター・レプリカ構成の解剖:データはどのように流れるか
Redisのレプリケーションは、デフォルトで非同期(Asynchronous)です。ここを誤解していると、マスターへの書き込み直後にレプリカを参照して「データがない」という幽霊バグに悩まされます。
[Client] —> WRITE —> [Master Redis] (即座にOKを返す)
|
| (非同期ストリーム)
v
[Replica Redis]
非同期レプリケーションのトレードオフ
- メリット: マスターはレプリカからのACKを待たずにクライアントに応答するため、書き込みスループットが極限まで高い。
- デメリット: マスターが突然クラッシュした場合、レプリカに同期しきっていなかった直近の書き込みデータは永久に消失する(RPO: Recovery Point Objective > 0)。
金融系や厳密な整合性が求められるトランザクションでこれを使うのは自殺行為です。必要に応じて `WAIT` コマンドを適切に組み込み、同期レプリケーションに近い挙動をインフラ層・アプリケーション層で担保する必要があります。
—
2. PSYNCの深層:フル同期と部分同期のメカニズム
Redis 2.8以前は、ネットワークが一時的に切断されるたびに、マスター側で全データをdumpして転送する「フルレプリケーション(SYNC)」が発生していました。これが巨大なデータセット(数十GB〜)で起きると、マスターのforkによるメモリコピー(Copy-on-Write)とI/O帯域の圧迫で、サービス全体が数秒間沈黙します。
これを解決したのが `PSYNC`(Partial Synchronization) です。
内部で何が起きているのか?(状態遷移の真実)
レプリカがマスターに接続する際、内部では以下のコンポーネントがやり取りされます。
1. Replication ID (RunID): 各Redisインスタンスが起動時に生成する40文字のランダムなHEX文字列。
2. Replication Offset: マスターがレプリカに送信したデータのバイト数を累積したカウンター。
3. Replication Backlog (バックログバッファ): マスターがメモリ上に保持する、直近のコマンドストリームを蓄積するリングバッファ。
ケースA:部分同期(Partial Resynchronization)の成功
ネットワークの一瞬の瞬断などでレプリカとの接続が切れた場合:
1. レプリカはマスターに `PSYNC
2. マスターは、自身のRunIDが一致し、要求された `Offset` のデータが Replication Backlog内に残っている か確認する。
3. 残っていれば、不足分の差分データだけを送信する。(フル同期を回避!)
ケースB:フル同期(Full Resynchronization)へのフォールバック
以下の場合、容赦なくフル同期(RDBスナップショット転送)に格下げされます。
- 初回接続時。
- レプリカが保持する `Master-RunID` が現在のマスターのものと異なる(マスターがフェイルオーバー等で切り替わった場合)。
- `Offset` が古すぎて、Replication Backlogのリングバッファからすでに押し出されている(溢れている)。
> 【アーキテクトの知見】Backlogサイズチューニングの極意
> デフォルトのバックログサイズ(`repl-backlog-size`)は通常 1MB です。これでは大規模システムにおいて数秒のネットワーク断ですぐにバッファが溢れ、フル同期の嵐(Replication Storm)が発生します。
> 実務では、「マスターが1秒間に書き込むデータ量 × ネットワーク復旧に許容したい最大秒数(例: 60秒)」を目安に、最低でも数十MB〜数GBに拡張すべきです。
>
>
> # redis.conf の推奨設定例
> repl-backlog-size 64mb
> repl-backlog-ttl 3600
>
—
3. フェイルオーバーの仕組みと「スプリットブレイン」の防衛
マスターが沈黙した時、自動でレプリカを昇格させる仕組みが不可欠です。ここで登場するのが Redis Sentinel または Redis Cluster です。
ここではSentinelを例に、フェイルオーバーの裏側を覗きます。
[Client]
|
v (監視)
[Sentinel] —> (S_DOWN / O_DOWN 判定) —> [Failover執行] —> [新しいMaster選出]
障害検知のフェーズ
1. S_DOWN (Subjectively Down / 主観的ダウン): 個々のSentinelがマスターへのPing応答がないと判断した状態。
2. O_DOWN (Objectively Down / 客観的ダウン): クォーラム(過半数)のSentinelが「マスターが落ちている」と合意した状態。ここで初めてフェイルオーバーのプロセスがトリガーされます。
現場で必ず起きる「スプリットブレイン(脳梁離断症候群)」の罠
ネットワークのルーティング障害により、マスターが「孤立」し、クライアントからの書き込みを受け付け続けつつ、レプリカ側は「マスターが死んだ」と勘違いして昇格する現象がスプリットブレインです。ネットワークが復旧した瞬間、データの不整合(データロストと上書き)が発生します。
これを防ぐための実務的設定が `redis.conf` に存在します。
最低N台のレプリカが接続しており、かつ遅延がM秒以内である場合のみ書き込みを受け付ける
min-replicas-to-write 1
min-replicas-max-lag 10
この設定を入れておけば、孤立したマスターはレプリカからの生存確認(ACK)が取れなくなるため、クライアントからの書き込みを拒絶し、データの破壊を防ぎます。Mission-Criticalなシステムでは必須の設定です。
—
4. 実務で踏み抜くアンチパターンとトラブルシューティング
最後に、コードレビューや障害対応で私が口酸っぱく指摘するポイントをまとめます。
アンチパターン1: `replica-serve-stale-data yes` のまま放置
デフォルトでは、マスターとの接続が切れたレプリカは、古い(Staleな)データをクライアントに返し続けます。「古いデータでもいいから応答してほしい」要件なら良いですが、整合性が命のシステムでは致命傷になります。
マスターとの接続断時はエラーを返すようにする(堅牢性の向上)
replica-serve-stale-data no
アンチパターン2: 巨大なRedisインスタンスのレプリケーション
メモリが100GBあるマスターをフル同期させようとすると、マスター側で `fork()` が走り、OSのページテーブルコピーとディスクI/O(RDB書き出し)で数秒間、Redis全体が完全停止(フリーズ)します。
- 対策: マスターのメモリサイズは最大でも20〜30GB程度にとどめ、データ量が増えたらRedis Clusterでシャージング(分散)してください。
—
5. まとめ
Redisのレプリケーションは、単に「データをコピーする機能」ではありません。
- 非同期の特性を理解し、RPOの許容範囲を定義する。
- `PSYNC` と Backlogバッファを適切にサイジングし、フル同期の発生を防ぐ。
- `min-replicas-to-write` を用いてスプリットブレインからシステムを防衛する。
これらを設計の初期段階で組み込むことこそが、真に堅牢なインフラストラクチャを作り上げるエンジニアの仕事です。次の設計レビューでは、これらの設定がきちんと担保されているか、厳しくチェックしてください。
コメント