Redis永続化の防衛線:`stop-writes-on-bgsave-error` の低レイヤアーキテクチャと障害防衛策
大規模分散システムのアーキテクチャにおいて、インメモリデータストアの永続化エラーは、システム全体の生死を分けるクリティカルな境界線である。データロスを恐れて可用性を犠牲にするか、可用性を維持してデータ不整合のリスク(スプリットブレインやレプリカ間の乖離)を受け入れるか。
Redisはこの永遠のジレンマに対し、`stop-writes-on-bgsave-error` という極めて明示的なスイッチを用意している。
本稿では、この設定値がRedisのコアエンジン、特にフォークメカニズムとバックグラウンド保存プロセス(BGSAVE / AOF重書き)の失敗時にどう作用するのか、その内部実装と実運用における致命的な罠について、チーフアーキテクトの視点から徹底的に解剖する。
—
1. 内部メカニズム:なぜBGSAVEは失敗し、Redisはどう検知するのか
まず、`BGSAVE` が裏側で何を行っているかを正確に理解する必要がある。
Redisはシングルスレッドでコマンドを処理するアーキテクチャを採用しているため、メモリ上の全データセットをディスクに吐き出す際、親プロセスがブロックするのを避けるために `fork(2)` システムコールを発行する。
ここで発生するのが有名な Copy-on-Write (CoW) メカニズムだ。
[Redis Main Process (Parent)]
│
├── 1. fork() ──> [Child Process (BGSAVE)]
│ │
│ └── 2. dump.rdb へのシリアライズ書き込み
│
├── 3. クライアントからの WRITE (例: SET key value)
│ └─ OSのCoWにより、変更されたメモリページが親プロセス側に複製される
│
└─ 4. フォーク失敗 / ディスク容量不足 / 権限エラー等が発生
BGSAVEが失敗する主なトリガー
1. OSのメモリ枯渇(OOM Killerによる子プロセスの殺害):
`vm.overcommit_memory = 0` の環境下で、親プロセスのメモリ使用量が物理メモリの半分を超えている場合、`fork()` 自体が失敗するか、子プロセスが起動直後にOSによって刈り取られる。
2. ディスク容量枯渇・I/Oエラー:
`dir` および `dbfilename` で指定されたパスへの書き込み権限喪失、またはディスクフル。
3. Linux Kernelの制限:
巨大な巨艦クラスのRedisインスタンス(例: 64GB以上)において、透明巨大ページ(THP: Transparent Huge Pages)が有効な場合、CoWのオーバーヘッドが爆発し、フォークそのものがタイムアウトまたは失敗する。
—
2. `stop-writes-on-bgsave-error` の真の挙動
`redis.conf` におけるデフォルト値は `yes` である。
バックグラウンド保存が失敗した場合、Redisは書き込みを停止するか?
stop-writes-on-bgsave-error yes
この設定が `yes` の状態で `BGSAVE`(またはAOFのバックグラウンド書き換え)がエラー終了すると、Redisのグローバル状態フラグが変化し、次から届くすべての書き込み系コマンド(`SET`, `HSET`, `DEL` など、メモリを変更する操作)に対して、以下のエラーを即座に返すようになる。
MISCONF Redis is configured to save RDB snapshots, but is currently unable to persist to disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
ソースコードレベルでの挙動(Server Stateの監査)
Redisのソースコード(`server.c` や `rdb.c`)を覗くと、バックグラウンドプロセスの終了コード(`wait3` 等で回収)を検知した際、`server.rdb_save_last_status` が `C_ERR` に設定される。
書き込みコマンドの実行前フック(`processCommand` 関数内)において、以下の条件判定が行われている。
// 疑似コード的な概念実装
if (server.stop_writes_on_bgsave_error &&
server.rdb_save_last_status == C_ERR &&
server.loading == 0)
{
// AOFが有効で、かつAOFのバックグラウンド書き換えエラーでない場合のフォールバックも考慮されるが、
// 基本的にRDBの永続化失敗フラグが立っているとここで弾かれる。
addReplyError(c, “MISCONF Redis is configured to save RDB snapshots…”);
return C_ERR;
}
つまり、「ディスクへの永続化が担保されていない状態で、メモリ上のデータをこれ以上改変させない(=データロスの拡大や、不整合なスナップショットの常態化を防ぐ)」という、強い整合性担保のための防衛機能である。
—
3. なぜシニアエンジニアはこの設定を「無効(no)」にすることを嫌うのか?
しばしば、可用性(Availability)を最優先する現場において、次のような議論がなされる。
> 「ディスクが一時的に詰まっただけで、アプリケーションからの書き込みがすべてエラーになるのは困る。`stop-writes-on-bgsave-error no` にしよう」
これはアーキテクチャの観点から見ると悪魔の選択である。
`stop-writes-on-bgsave-error no` にした場合のシナリオ
1. バックグラウンドでのRDB保存が失敗する(例: ディスク容量オーバー)。
2. 設定が `no` のため、Redisは平然とクライアントからの書き込みを受け付け、メモリ上のデータを更新し続ける。
3. 運用者はディスクアラートに気づくのが遅れる。
4. その状態でサーバーが再起動(あるいはOOMや障害でクラッシュ)。
5. 最後に成功した古いRDB以降のデータは完全に蒸発し、リカバリ不能なデータロスが発生する。
さらに厄介なのは、Master-Replica 構成における挙動だ。
マスター側で永続化エラーが発生し、書き込みが継続された場合、レプリカとのデータ乖離が深刻化する。最悪の場合、レプリケーションストリームの整合性が崩れ、フルシンク(`PSYNC` の失敗による `SYNC`)が頻発してネットワーク帯域とマスターのCPUを焼き尽くす。
—
4. 極限環境におけるベストプラクティスとアーキテクチャ設計
この問題の本質は、「永続化エラーを検知したあとに書き込みを止めるか止めるまいか」ではなく、「そもそもバックグラウンド保存を失敗させないインフラストラクチャの構築」と「迅速なモニタリング」にある。
A. OS・カーネルパラメータの極限チューニング
巨艦化するRedisインスタンスにおいて、フォーク失敗によるBGSAVEエラーを防ぐための必須設定:
1. 透明巨大ページ (THP) の無効化 (カーネルレベル)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
2. Linuxのメモリオーバーコミット設定の適正化
物理メモリ + スワップを超える割り当てを防ぎ、fork() の失敗を確実に防ぐ
sysctl -w vm.overcommit_memory=1
B. モニタリングとアラートの急所
`stop-writes-on-bgsave-error` が `yes` の場合、障害発生時にアプリケーション層で `MISCONF` エラーが観測される。これを待ち構えるのではなく、事前のメトリクスで検知しなければならない。
`INFO persistence` コマンドを定期的にポーリングし、以下の数値を監視する。
- `rdb_last_bgsave_status`: `ok` でなければ即座にPagerduty等を発報。
- `rdb_changes_since_last_save`: 最後に保存されてからどれだけデータが変更されたか。この値が異常に増加している場合、実質的にバックグラウンド保存が機能不全に陥っている。
監視スクリプトやPrometheus Exporter等で確認すべきメトリクスの例
redis-cli INFO persistence | grep -E “rdb_last_bgsave_status|rdb_changes_since_last_save”
C. 永続化戦略の再定義(RDB + AOFのハイブリッド)
現代の大規模Redis運用において、RDB単体に依存する構成はリスクが高い。
`appendonly yes` を有効化し、`appendfsync everysec` または `always` を組み合わせることで、万が一のBGSAVE失敗時であっても、AOFログ側で直前の書き込みが保護される堅牢なアーキテクチャを構築すべきだ。
ただし、AOFの重書き(BGREWRITEAOF)も同様に `fork()` を伴うため、このエラーも `stop-writes-on-bgsave-error` の影響下にある点を忘れてはならない。
—
結言
`stop-writes-on-bgsave-error` は、単なるエラーハンドリングのスイッチではない。それは、「整合性を失うくらいなら、システムは沈黙せよ(Fail-Stop)」という、分散システムにおける最も高潔な設計思想の表れである。
可用性の名のもとにこの防衛線を安易に解除することは、データロスのリスクを闇夜に放置することに他ならない。インフラストラクチャの限界を把握し、フォークの失敗要因を排除した上で、この防衛線を有効に保ち続けること――それこそが、真にプロフェッショナルなRedisアーキテクトの仕事である。
コメント