【実務・中級編】 レプリケーションバッファのメモリ管理 – Redis

Redisレプリケーションの暗黒面:メモリ枯渇を防ぐための「攻めの設計」

プロダクトが成長し、Read負荷分散のためにRedisのレプリケーション構成(Master-Replica)を導入する。ここまではどのチームも通る黄金のパスだ。しかし、トラフィックがピークに達した瞬間、あるいはネットワークがわずかに一瞬瞬断したその時――マスターノードが突如としてOOM Killer(Out of Memory Killer)の餌食になり、クラシックな障害を引き起こす。

原因の多くは、レプリケーションバッファ(Replication Buffer / Replication Backlog)のサイレントな肥大化にある。

私はこれまで数々の大規模Redisクラスターの障害対応を行ってきたが、「なぜかマスターのメモリが徐々に削られ、最終的にクラッシュする」という現場の悲鳴を上げるエンジニアに何度も会ってきた。
今回は、Redisのメモリ管理の心臓部の一つである「レプリケーションバッファ」のメカニズムを解剖し、実務で絶対に踏むべき設計パターンを伝授する。

—

1. 根本原因の解明:レプリケーションバッファの正体

レプリケーション時、マスターはスレーブへデータ・ストリームを流し込む。この際、内部で何が起きているのか。メモリ管理の観点から2つのバッファを混同しているエンジニアが多すぎるため、まずここを明確に切り分ける。

① Replication Backlog (リングバッファ)

  • 役割: 部分再同期(Partial Resynchronization: `PSYNC`)を可能にするための事前割り当て済みリングバッファ。
  • メモリ挙動: `repl-backlog-size` で指定されたサイズ(例: `64mb`)をマスター起動時に一括で確保し、基本的には固定サイズを維持する。

② Replication Output Buffer (クライアント出力バッファ)

  • 役割: 各スレーブ(クライアントとして接続している)へ送信するコマンドを一時的に溜めておくためのバッファ。
  • メモリ挙動: 動的に拡大する。 ここが実務における最大の罠だ。

スレーブのネットワーク帯域が細い、あるいはスレーブ側で重い処理が走ってレプリケーションの追従が遅れたとき、マスターはこの「出力バッファ」を際限なく拡大させながらデータを溜め込もうとする。結果、マスターのメモリは瞬く間に食い潰される。

—

2. 現場で使える監視とメトリクス

まずは、現在のあなたのRedisクラスターがどの程度危険な状態にあるのかを把握するためのコマンドを確認する。

マスター側で実行
127.0.0.1:6379> INFO replication

出力結果の重要なポイント:

role:master
connected_slaves:2
slave0:ip=192.168.1.10,state=online,offset=123456789,lag=0
slave1:ip=192.168.1.11,state=online,offset=123456700,lag=12
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:122408213
repl_backlog_len:1048576

注目すべきは `lag` と `repl_backlog_len` だ。`lag` が常に数秒以上あるスレーブが存在する場合、それはバッファ肥大化の予兆である。

さらに、クライアント出力バッファのリアルタイムな消費量を確認するには `CLIENT LIST` を叩く。

127.0.0.1:6379> CLIENT LIST type normal
あるいはレプリケーション接続に絞る場合
127.0.0.1:6379> CLIENT LIST type replica

出力中の `omem`(Output Memory)フィールドに注目してほしい。ここが数十MB、あるいは数百MBに膨れ上がっているスレーブがいるなら、即座に対策が必要だ。

—

3. 堅牢な設計パターン:どう設定し、どう守るか

設計レビューで「`repl-backlog-size` をとりあえず大きくしておけば安心ですね」と言われたら、私はその場で差し戻す。メモリは無限ではない。トレードオフを理解した上で、以下の3つの防衛線を張る必要がある。

防衛線 A: クライアント出力バッファのハード・ソフトリミットの設定

Redis 7(および近年のRedis 6)では、スレーブ(およびPub/Subクライアント)に対する出力バッファの制限を `redis.conf` で厳格に制御できる。

構文: client-output-buffer-limit
client-output-buffer-limit replica 256mb 64mb 60

  • Hard Limit (`256mb`): 出力バッファがこのサイズを超えた瞬間、即座にそのスレーブとの接続を切断する。マスターのOOMを防ぐための最後の砦。
  • Soft Limit (`64mb` / `60`秒): バッファが `64mb` を超えた状態が 60秒間継続 した場合に接続を切断する。

【設計の勘所】
ビッグデータを扱うシステムでは、フルsync(`FULLRESYNC`)が発生した直後にこの制限に引っかかり、無限ループ(切断→再接続→フルsync→バッファ溢れ→切断)に陥ることがある。マスターの物理メモリ量と、スレーブのネットワーク転送速度(例: 1秒間に転送できるデータ量)を逆算してこの値をチューニングすること。

防衛線 B: バックログサイズの適切なサイジング

`repl-backlog-size` は、「スレーブがマスターから切断されてから、再接続して `PSYNC` に成功するまでの間に、マスターが書き込まれるデータ量」をカバーできなければ意味がない。

計算式はこうだ:
> [許容する最大切断時間(秒)] × [1秒あたりの平均書き込みバイト数] = 最低限必要なバックログサイズ

例えば、秒間 10MB の書き込みがあり、ネットワークの瞬断やフェイルオーバーの検知・再接続までに最大 10秒かかると想定するなら:

10MB/s × 10s = 100MB

よって、`repl-backlog-size 100mb` と設定すべきである。これをデフォルトの `1mb` のまま放置しているシステムは、少しの負荷で常にフルsyncが発生し、マスターのCPUとネットワーク帯域をドブに捨てているようなものだ。

—

4. チーフアーキテクトからの提言:コードとインフラの境界線

メモリ管理の最適化は、Redisのコンフィグをいじるだけでは完結しない。アプリケーションレイヤーの設計と密接に連携する必要がある。

1. 巨額な一括書き込み(MSET, EVAL, HUGE HSETなど)を避ける

  • 数MBに及ぶ巨大なJSON文字列をひとつのキーに頻繁に書き込んだり、巨大なトランザクションを実行したりすると、レプリケーションバッファはその瞬間の一撃で跳ね上がる。データ構造は可能な限り細分化せよ。

2. スレーブのスケールアウトに上限を設ける

  • マスター1台に対してスレーブを10台もぶら下げる構成はアンチパターンだ。マスターはすべてのスレーブに対して個別にバッファを抱えるため、スレーブの数に比例してメモリ負荷が乗算される。多段レプリケーション(Cascading Replication: Master -> Replica 1 -> Replica 2)の構成を検討せよ。

3. 監視アラートの必須化

  • `stat_rdb_saves` や `connected_slaves` だけでなく、`evicted_keys` や前述の `omem` のメトリクスを DatadogやPrometheusなどでスクレイピングし、「スレーブの遅延が30秒を超えたら警告、バッファがハードリミットの8割に達したら緊急アラート」というフローを必ず構築すること。

まとめ

レプリケーションバッファの管理は、単なるパラメータチューニングではない。それは「分散システムにおける一貫性と可用性のバランスを、メモリというリソースの上でどう調停するか」というアーキテクチャそのものだ。

「動いているから触らない」ではなく、最悪のシナリオ(ネットワークの分断やスレーブの急激な遅延)をシミュレーションし、意図通りに切断やフォールバックが機能する状態を作ること。それこそが、プロフェッショナルなエンジニアリングである。次回の設計レビューでは、これらの数値的根拠を伴った設定値が提示されることを期待している。

コメント

タイトルとURLをコピーしました