【実務・中級編】 save設定ディレクティブ – Redis

Redis永続化の急所:`save`ディレクティブと正しく向き合う技術

テックリードの私だ。コードレビューや設計レビューで、こんな`redis.conf`を見かけるたびに私は冷や汗が出る。

デフォルトのまま放置された悲劇のディレクティブ
save 900 1
save 300 10
save 60 10000

「とりあえずデフォルトだから大丈夫だろう」「RDBをとっておけば安心だよね」——そんな甘い認識で本番環境の設計をしているなら、今すぐその手を止めなさい。

Redisは「インメモリデータベース」というその性質上、永続化の設計を誤ると、大規模障害時のデータロス(RPOの増大)か、I/O枯渇によるレイテンシスパイク(可用性の崩壊)の二者択一を迫られることになる。

今回は、Redisの自動スナップショットの核心である`save`設定ディレクティブについて、実務の現場で生き残るための「極限の知見」を授けよう。

—

1. `save`ディレクティブのメカニズム:何が起きているのか?

`save <秒> <変化回数>` は、指定した時間内に指定した回数以上のキーの更新(Write)が発生した場合に、バックグラウンドでRDB(Redis Database)スナップショットを生成するトリガーだ。

だが、ここでエンジニアとして理解していなければならないのは、その裏側のメカニズムである。

fork() の呪縛と Copy-on-Write

Redisはスナップショットを生成する際、親プロセスから `fork()` を呼び出し、子プロセスを作ってディスクへの書き込みを行わせる。

[Redis Main Process (メモリ上で書き込み受付中)]
│
├─► fork() ──► [Child Process (親のメモリのスナップショットを保持しディスクへ吐き出す)]
│
└─► 通常のクライアントリクエスト処理を継続 (Copy-on-Writeでメモリ増大の危険)

ここで問題になるのが Copy-on-Write (CoW) だ。
子プロセスが生存している間に親プロセスが既存のキーを更新すると、OSレベルでそのメモリページのコピーが発生する。つまり、書き込み負荷(Write Heavy)が高いシステムで頻繁にRDBセーブが走ると、メモリ使用量が理論上の最大2倍に膨れ上がるリスクがある。メモリが枯渇すれば、LinuxのOOM KillerにRedisが無慈悲に狩られるか、スワップアウトが発生してレイテンシが数秒〜数分ハネ上がる。

—

2. 実務における設計アンチパターンと致命傷

レビューでよく見る「やってはいけない設定」を挙げておこう。

アンチパターンA: 高頻度すぎるセーブ

「データロスの許容範囲を最小限にしたい」という営業部門のプレッシャーに屈し、以下のような設定にする愚行。

絶望的な設定例
save 1 1 # 1秒以内に1回でも変わったら保存
save 10 100 # 10秒以内に100回変わったら保存

結果:
CPUは `fork()` のオーバーヘッドで常に喘ぎ、ディスクI/Oは飽和し、Redis本来の超高速なレイテンシ(サブミリ秒)は完全に破壊される。RedisをRDBベースのリレーショナルDBのよう扱ってはいけない。

アンチパターンB: デフォルト放置による「データ消失の罠」

逆に、開発環境のままで実務に投入し、アクセス数が少ないために `save 900 1` が発動せず、デプロイ時の再起動や予期せぬプロセス停止で数十分分のデータが綺麗に吹き飛ぶケース。キャッシュ層としての割り切りなら良いが、セッションストアやカウンターとして使っている場合は致命傷になる。

—

3. 堅牢な設計パターン:どう設定し、どう運用すべきか

では、テクニカルリードとして我々はどう設計すべきか。結論から言えば、「RDB単体に依存しない、AOFとのハイブリッド運用」 または 「要件に応じた厳密なスナップショットチューニング」 が必須となる。

アプローチ1: セッション・キャッシュ用途(データロス許容型)

データの消失がビジネス上致命的ではなく、後からRDBやAPIで再構築できる場合の設計。

過剰なRDBセーブを抑制し、パフォーマンスを最優先する
save “”
※自動スナップショットを完全に切る。その代わり、手動でのSAVE/BGSAVEやレプリケーションで担保する。

理由: 無駄な `fork()` を発生させず、純粋なインメモリ性能を極限まで引き出す。

アプローチ2: 永続データ・重要カウンター用途(堅牢性重視型)

データロスを最小限(RPOを数分以内)に抑えつつ、システムへの負荷を制御する設計。

負荷を分散させつつ、一定の堅牢性を担保する現実的なバランス
save 900 1 余裕があるときは15分に1回
save 300 10 少し動いたら5分に1回
save 60 10000 高負荷時は1分間に1万回以上の変更があれば強制退避

さらに、実務ではこれに加えて必ず AOF (Append Only File) を有効化する。

appendonly yes
appendfsync everysec

`appendfsync everysec` を併用することで、RDBの隙間をAOFが毎秒のログで埋め、万が一のクラッシュ時にも直近1秒のデータロスに抑えることができる。これがモダンなRedis運用のスタンダードだ。

—

4. パフォーマンス上の注意点と監視メトリクス

`save` ディレクティブを運用する上で、SREチームと開発チームが必ず監視すべきPrometheus等でのメトリクスがある。

1. `rdb_last_bgsave_status`
最後に実行されたバックグラウンドセーブが成功したか。これが `0` になったらアラートを飛ばせ。ディスク容量不足やメモリ不足(`fork` の失敗)の兆候だ。
2. `rdb_changes_since_last_save`
最後のセーブから何回の変更が未保存か。ここが意図せず跳ね上がっている場合、`save` 条件が厳しすぎて保存が走っていない可能性を疑え。
3. `latest_fork_usec`
`fork()` に要したマイクロ秒。データセットが大きくなると(例: 30GB)、`fork()` 自体で数秒間Redisがフリーズする現象(Blocked by fork)が発生する。これを監視し、必要であれば `vm.overcommit_memory = 1` のカーネルパラメータ調整や、クラウド環境であればインスタンスのスケールアップを検討すること。

—

5. リードからの総括

`save` 設定ディレクティブは、単なる「ファイル保存のタイマー」ではない。
それは、「パフォーマンス(レイテンシ)」と「安全性(データロス耐性)」のトレードオフの境界線をコードで定義する行為である。

「なんとなく動く」から卒業し、システムの特性(Write/Read比率、データ量、障害時の許容範囲)を見極めた上で、`redis.conf` の一行を誇りを持って記述してほしい。

君たちの書くコンフィグが、深夜の障害アラートからチームを救う盾となることを期待している。

コメント

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