Redis永続化の真実:チーフアーキテクトが教える、妥協なきストレージ設計の極意
システム設計のレビュー会で、こんなセリフを耳にしたことはないか?
> 「とりあえずRedisの永続化は、RDBとAOFの両方(`appendfsync everysec`)を有効にしておけば安全ですよね」
この瞬間、私はコードレビューの手を止め、エンジニアの目を見る。そして静かに問いかける。
「その設計、CPUとストレージのI/Oコスト、そして万が一の障害時のRTO(目標復旧時間)を本当に計算したのか?」と。
Redisは、単なる「速いインメモリキャッシュ」ではない。使い方を誤ればシステム全体のボトルネックになり、正しく使いこなせば最強のデータストアへと変貌する。今回は、Redisの永続化メカニズム(RDBとAOF)の本質を解き明かし、ユースケースに応じた「絶対に外さない」選定基準をロジカルに伝授しよう。
—
1. 永続化エンジンの裏側:RDBとAOFの「物理」を理解する
まず、Redisがデータをディスクに書き出す仕組みの根本を押さえる。ここを理解していないと、本番環境で痛い目をみる。
RDB (Redis Database)
- 仕組み: 指定した間隔(例: 60秒以内に1万回以上の更新)でメモリ上のデータセットのスナップショットをバイナリファイル(`dump.rdb`)として書き出す。
- 裏側の挙動: 親プロセスから `fork()` を呼び出し、子プロセスがディスクへの書き込みを行う(Copy-on-Write)。
AOF (Append Only File)
- 仕組み: Redisが実行したすべての書き込みコマンドを、独自のプロトコル形式でログファイルに逐次追記していく。
- 裏側の挙動: OSのページキャッシュに書き出され、設定に応じて(毎秒、あるいはコマンド毎に)`fsync` システムコールで物理ディスクにフラッシュされる。
—
2. ユースケース別・永続化戦略の選択基準
「キャッシュ」か「プライマリデータストア」か。システムにおけるRedisの役割によって、選択すべき永続化戦略は180度変わる。
パターンA:純粋なキャッシュ層(セッションストア、APIレスポンスキャッシュ)
- 要件: データの消失は許容できる(最悪、下流のDBから再構築可能)、あるいはセッション切れとして扱える。パフォーマンスが最優先。
- 推奨設定: 完全無効化(Persistence Disabled)
redis.conf
スナップショット設定をすべてコメントアウト、または無効化
save “”
appendonly no
チーフアーキテクトの視点:
「キャッシュだから消えてもいい」という理由だけでRDBを回し続けるチームがあるが、これは悪手だ。データ量が増えた際、`fork()` によるメモリの二重化(Copy-on-Write)でメモリが枯渇し、OOM KillerにRedisが屠られる事故が後を絶たない。キャッシュであれば、永続化など不要。潔く捨てるべきだ。
—
パターンB:高速なインメモリDB(マスターデータ、ユーザー設定、リアルタイムカウンター)
- 要件: サーバーの再起動や予期せぬクラッシュ時に、データ損失を最小限に抑えたいが、AOFの毎秒fsyncほどのオーバーヘッドは避けたい。
- 推奨設定: RDB中心の運用(適度なスナップショット)
redis.conf
900秒(15分)以内に1回以上の変更
save 900 1
300秒(5分)以内に10回以上の変更
save 300 10
60秒以内に10000回以上の変更
save 60 10000
appendonly no
rdbcompression yes
rdbchecksum yes
チーフアーキテクトの視点:
データが消えても「数分前の状態」に戻ればビジネス上許容できるケースの大半は、これで十分だ。`fork()` の頻度を適切にコントロールし、CPUとI/Oのバランスを取る。
—
パターンC:ミッションクリティカルなプライマリデータストア(Pub/Subの永続化、ジョブキュー、重要メタデータ)
- 要件: データのロストが許されない。サーバーが爆発しても、直前までの書き込みが復元されなければならない。
- 推奨設定: AOF(`appendfsync everysec`)の単体利用
redis.conf
save “” # RDBはCPU負荷とfork時のメモリ枯渇のリスクがあるため無効化(または極端に長めの間隔に)
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
ここで重要なのは、RDBとAOFを同時に有効にしないことだ。多くのエンジニアが「念のため両方」と言って両方有効にするが、これは最悪のアンチパターンである。バックアップ時にRDBの `fork()` とAOFの重い処理が同時に走り、レイテンシが跳ね上がる。AOFを毎秒(`everysec`)で回していれば、RDBは不要、あるいはAOFリライト時のバックアップ用として割り切るべきだ。
—
3. 堅牢な設計パターン:レプリケーションとの組み合わせ
単一ノードの永続化に頼るな。本番環境における真の堅牢性は、「レプリケーション」と「永続化」の融合によって初めて達成される。
[Primary Node (AOF: everysec)]
│
├─ (Async Replication) ──> [Replica Node A (RDB only)]
│
└─ (Async Replication) ──> [Replica Node B (Persistence Off: Read-Only/Failover Candidate)]
チーフアーキテクトからの設計レビュー指摘事項:
1. 書き込みを受け持つプライマリ(Master)では、AOF(`appendfsync everysec`)を有効にし、データの耐久性を担保する。
2. 読み取り専用のレプリカ(Replica)では、あえて永続化を無効化するか、RDBのみにする。これにより、レプリカ側でのディスクI/Oボトルネックを排除し、参照性能を極限まで高める。
3. フェイルオーバー(Redis Sentinel / Redis Cluster)を前提とする場合、マスターがクラッシュした際に古いデータを持つレプリカが昇格してデータを巻き戻す(スプリットブレイン的なデータロスト)を防ぐため、レプリカの選定基準と `min-replicas-to-write` などの設定を必ず吟味すること。
—
4. パフォーマンス上の罠と回避策
最後に、実務で必ず直面する「永続化に起因するレイテンシ悪化」への対策を授ける。
- 罠1: `fork()` によるレイテンシのスパイク(Latency Spike)
- 原因: メモリが数十GBに達したRedisでRDBのセーブやAOFのリライトが発生すると、`fork()` 自体に数秒かかることがあり、その間Redisが完全にフリーズする(シングルスレッドモデルの悲劇)。
- 対策: Linuxのカーネルパラメータ `vm.overcommit_memory = 1` を必ず設定し、物理メモリの空きが足りない状態での `fork()` 失敗を防ぐ。また、物理メモリの半分以上をRedisに割り当てない(Copy-on-Writeの余白を残す)。
- 罠2: `fsync` によるブロッキング
- 原因: `appendfsync always` に設定している場合、すべての書き込みごとにディスクの同期書き込みを待つため、SSDの性能がそのままRedisの性能上限になる。
- 対策: 特殊な金融系トランザクションでもない限り、`always` は使わない。実務上のデフォルトは `everysec` 一択である。
—
結びにかえて
Redisの永続化設計は、「速度(Performance)」と「耐久性(Durability)」のトレードオフの最適化に他ならない。
「なんとなく安全そうだから」という思考停止の設計は、高負荷時に必ずシステムを崩壊させる。自社のシステムが求めるデータの一貫性レベル(RPO/RTO)を冷徹に見極め、コードと設定ファイルにその意図を深く刻み込んでほしい。
あなたの書いたその設定ファイルが、深夜の障害アラートからチームを救う盾となることを期待している。
コメント