【Redisアーキテクチャ診断】RDB vs AOF:実戦で踏み抜く前に知るべき「永続化の哲学」
テックリードの私だ。今日のコードレビューで、あるジュニアエンジニアが「RedisはメモリDBだから、とりあえず`save`連打しとけば安全ですよね?」と平然と言い放ったのを聞いて、私は思わずコーヒーを吹き出しそうになった。
インメモリデータベースであるRedisを「単なる高速なキャッシュ」として使う分には、揮発性(データが消えること)など恐るに足りない。だが、セッションストア、レートリミッター、あるいはリアルタイムのリーダーボードとしてRedisがマスタデータの一部を保持し始めた瞬間、「永続化(Persistence)」のメカニズムを理解しているか否かが、システム全体の生死を分ける。
今回は、Redisが誇る2つの永続化エンジン、RDB(Redis Database)とAOF(Append Only File)の全体像を解剖し、実務の現場でどう設計・選択すべきかをロジカルに伝授する。
—
1. Redis永続化の全体像:なぜ「2つの道」が存在するのか
Redisはインメモリデータベースでありながら、なぜディスクへの書き込みを行うのか。答えはシンプルだ。「超高速なインメモリの特性」と「クラッシュ時のデータ耐久性(Durability)」という、トレードオフの二律背反を解決するためである。
Redisは、この相反する要求を満たすために、まったくアプローチの異なる2つの永続化方式を用意している。
1. RDB (Snapshotting): ある特定の瞬間の「メモリのスナップショット」をバイナリでそのままディスクに叩き出す。
2. AOF (Append Only File): Redisが実行した「すべての書き込みコマンド」を時系列でログとして追記していく。
これらは競合するものではない。現代の堅牢なRedisアーキテクチャでは、両者を協調動作させるか、システムの性質に合わせてどちらかを極限までチューニングして使うのがプロの作法だ。
—
2. RDB:瞬間を切り取る美学と、その代償
概念とメカニズム
RDBは、指定した条件(例: 「過去60秒以内に1万回以上の更新があった場合」)を満たすと、裏側でOSの機能である`fork()`を呼び出し、子プロセスを生成する。この子プロセスが、メモリ上のデータをディスク上のdump.rdbファイルへと黙々と書き出す(Copy-on-Write機構)。
メリット
- 圧倒的なリカバリ速度: バイナリのスナップショットをそのままメモリにロードするため、再起動(復旧)時間が極めて短い。大容量データでも一瞬で起動する。
- 省ストレージ: データ構造そのものではなく圧縮されたバイナリであるため、AOFに比べてファイルサイズが小さくなりにくい傾向がある。
デメリット(実務での罠)
- データの消失リスク(Window Loss): RDBは「定期的なスナップショット」であるため、前回の保存からクラッシュまでの間に発生したデータは容赦なく消え去る。数秒〜数分のロストが許されないシステムには不向きだ。
- `fork()`のコスト: メモリ使用量が数十GB規模に達する巨大なRedisインスタンスでRDBの保存が走ると、`fork()`自体のオーバーヘッド(ページテーブルのコピー)やCopy-on-Writeによるメモリ圧迫により、数ミリ秒〜数百ミリ秒のレイテンシースパイク(応答遅延)が発生する。これが本番環境で「謎のタイムアウト」を引き起こす主原因だ。
—
3. AOF:全歴史を記録する執念と、トレードオフ
概念とメカニズム
AOFは、クライアントから受け取った書き込みコマンド(`SET`, `HSET`, `LPUSH`など)を、Redisプロトコル形式でそのままファイル(`appendonly.aof`)の末尾に追記していく。まさに「監査ログ」の概念だ。
永続性の制御:`appendfsync` の哲学
AOFの生死を握るのが、ディスクへの同期タイミングを決める設定値である。
redis.conf
毎回の書き込みごとにfsyncを呼ぶ(最も安全だが、ディスクI/Oの限界がスループットの限界になる)
appendfsync always
1秒に1回fsyncを呼ぶ(推奨値。堅牢性とパフォーマンスの黄金比)
appendfsync everysec
OSにディスクへの書き込みを完全に委ねる(最速だが、OSクラッシュ時にデータが飛ぶ)
appendfsync no
実務におけるデフォルト、かつベストプラクティスは `everysec` だ。これにより、万が一サーバーがクラッシュしても最大1秒分のデータ損失に抑えつつ、I/Oボトルネックを回避できる。
メリット
- 極めて低いデータ損失リスク: `everysec`であれば、失われるデータは最大でも直近1秒間のみ。
- 人間が読める(Human-readable)ログ: コマンドがそのままテキスト(正確にはRESPプロトコル)で記録されているため、万が一データが破損した場合でも、テキストエディタで不正なコマンド行を削るなどの荒業(復旧作業)が可能。
デメリット
- ファイル肥大化問題: コマンドを追記し続けるため、同じキーに対する更新が100回あれば100行のログが溜まる。結果、ファイルサイズが爆発的に膨れ上がる。
- リライト(重労働): 肥大化したファイルをスッキリさせるため、Redisはバックグラウンドで現在のメモリ状態を再現する最小限のコマンド群を再構築する(AOF Rewrite)。これにはCPUとI/Oのパワーを消費する。
- 復旧の遅さ: 起動時にAOFファイル内のすべてのコマンドをもう一度実行(リプレイ)してメモリを復元するため、RDBに比べて起動に時間がかかる。
—
4. チーフアーキテクトが伝授する「堅牢な設計パターン」
現場のシステム要件に応じて、永続化は以下のように設計せよ。
パターンA:完全な「キャッシュ」として割り切る場合
- 対象: セッション情報、APIレスポンスのキャッシュ、一時的な計算結果。
- 設計: RDBもAOFも完全無効(Persistence Off)。
- 理由: ディスクI/Oのオーバーヘッドをゼロにし、最大限のパフォーマンスを引き出す。万が一Redisが落ちても、DBや上流から再構築できるデータ構造にのみ適用する。
パターンB:ミッションクリティカルなデータストア(現在の主流)
- 対象: カート情報、リアルタイムのカウンター、一部の業務データ。
- 設計: RDBとAOFのハイブリッド運用(Redis 4.0以降)。
- 理由: 起動時は高速なRDBからロードし、実行中のデータ保護はAOF(`everysec`)でカバーする。Redis 5/6/7系ではこのハイブリッドモードが標準的であり、最もバランスの取れた堅牢性を発揮する。
—
5. パフォーマンス上の注意点:実戦でハマる地雷原
最後に、現場でエンジニアが踏み抜きがちな「永続化にまつわるアンチパターン」を挙げておく。
1. メモリの許容量管理(OOM Killerの恐怖)
RDBのスナップショット生成時やAOFのリライト時には、一時的にメモリ消費量が最大2倍に跳ね上がることがある(Copy-on-Writeのため)。物理メモリの最低でも50%以上は空き(ヘッドルーム)を残したサイジングを厳守しろ。「メモリ80%まで使ってよし」なんて設計は、障害時に自殺行為だ。
2. ディスクのI/O性能(SSDの選定)
AOFを有効にするということは、すべての書き込みがランダム/シーケンシャルI/Oを伴うことを意味する。Cloud環境であれば、IOPS(Input/Output Operations Per Second)が保証されたgp3やio2などのSSDを選択し、HDDを使うなどという愚行は絶対に避けること。
3. `save` コマンドの誤爆禁止
本番環境のCLIから、うっかりブロッキングを伴う同期的な `SAVE` コマンドを叩くな。Redisが完全にフリーズし、すべてのクライアントからのリクエストがタイムアウトする地獄絵図を作り出すことになる。非同期で実行したい場合は必ず `BGSAVE` を使え(もっとも、設定ファイルで適切に管理されていれば手動で叩く必要すらないはずだ)。
—
結びにかえて
Redisの永続化は、単なる「設定項目のオン・オフ」ではない。それは「可用性と一貫性のトレードオフ」という分散システムの根幹に向き合う作業だ。
「なぜこの設定にしているのか」をコードレビューで説明できないうちは、あなたのRedisはいつか必ず本番環境で牙をむく。今日の知見を胸に、今一度、プロダクトのRedis設定ファイル(`redis.conf`)を検分してみることを強く勧める。
コメント