RDB、AOF、そして失ってもよいもの
一言でいうと
永続化の設定は、「何秒分を失ってもよいか」という問いに対する答えです。答えが「1件もだめ」なら、Redisが適切な道具なのかから、見直す必要があります。
なぜ必要なのか
Redisはインメモリストレージです。プロセスが死ぬと、メモリは消えます。そのため、2つの永続化方式を提供しています。
RDBは定期的なスナップショットです。設定例を見ると、save 3600 1、save 300 100、save 60 10000で、1時間以内に1件以上の変更、5分以内に100件、1分以内に10000件の変更があれば、スナップショットを取ります。利点は、ファイルが1つなのでバックアップと復旧が簡単で、再起動が速いことです。欠点は、最後のスナップショット以降の書き込みをすべて失うことです。
AOFは、すべての書き込みコマンドをログとして残します。appendfsync everysecが実務のデフォルトで、最悪の場合に1秒分を失います。alwaysにすると消失はほとんどありませんが、書き込みごとにfsyncなのでスループットが大きく落ちます。noはOSに任せるので、消失の範囲が広がります。
どう動くのか
実務の標準は、2つを一緒に使う混合モードです。AOFを有効にしてaof-use-rdb-preamble yesにすると、AOFのリライト時に前半部分をRDB形式で保存し、その後ろに増分コマンドを付けます。復旧が速く、消失の範囲も1秒に保たれます。
Redis 7のMulti-Part AOFは、これをさらに整理して、専用ディレクトリにbaseファイルとincrファイルを分けて置きます。リライト中にディスク容量が2倍に跳ね上がる問題が減りました。
ここで正直に指摘しておくべきことがあります。永続化を有効にしても、Redisはデータベースではありません。レプリケーションは非同期なので、マスターが落ちる瞬間に、レプリカに届いていない書き込みがありうるのです。WAITコマンドで一部改善できますが、完全な同期レプリケーションではありません。そのため、「絶対に失ってはいけない」データの一次ストレージとしては、適切ではありません。
現場での姿
fsyncのコストを過小評価するケースが多くあります。appendfsync alwaysに変えたらスループットが半分以下に落ちたという報告はよくあります。ディスクが遅い環境では、AOFの書き込みが滞って、メインスレッドがブロックされることもあります。
RDBのスナップショットもタダではありません。forkで子プロセスを作ってスナップショットを取りますが、copy-on-writeのために、書き込みが多いと、瞬間的にメモリが大きく増えます。4GBのインスタンスが、スナップショット中に6GBを使うことが起きます。maxmemoryを物理メモリにぴったり合わせると、このときにOOMが起きます。通常は、物理メモリの半分くらいをmaxmemoryに設定します。
2つの方式を並べると
| RDB(スナップショット) | AOF(コマンドログ) | |
|---|---|---|
| 保存するもの | その時点の全データ | 変更したコマンドを順番に |
| ファイルサイズ | 小さい(圧縮されたバイナリ) | 大きい(リライトで減らす) |
| 再起動の速度 | 速い | 遅い(コマンドを再実行) |
| 最悪の消失 | 最後のスナップショット以降すべて | appendfsyncによる |
| 負荷 | forkの瞬間にメモリが跳ねる | 書き続けるが緩やか |
appendfsyncがAOFの核心のつまみです。
always 쓸 때마다 fsync — 거의 안 잃지만 아주 느리다
everysec 1초마다 fsync — 최악 1초 유실. 사실상 표준
no OS 에 맡김 — 빠르지만 몇 초를 잃을 수 있다
2つを一緒に有効にするのが基本形です。AOFで直近1秒までを守り、RDBで速い復旧とバックアップファイルを得ます。Redisは、再起動するときにAOFがあればそちらを優先して使います。
forkが起こすメモリの急増
RDBスナップショットとAOFのリライトは、子プロセスをforkして行います。Linuxのcopy-on-writeのおかげで、最初はメモリを共有しますが、保存している間に親が変更するページごとに、コピーが作られます。
書き込みが多い瞬間にスナップショットが重なると、メモリが最大2倍まで上がります。コンテナの上限に引っかかってOOMで死ぬ事故が、ここで起きます。
maxmemory 4gb ← 데이터 상한
컨테이너 한도 6~8gb ← fork 여유를 남긴다
vm.overcommit_memory = 1が推奨される理由も、これです。この値が0だと、カーネルが「メモリが足りなくなりそうだ」としてforkを拒否し、保存そのものが失敗します。Redisはログに警告を残しますが、静かに見過ごされやすいです。
何を失ってもよいかを先に決める
- 純粋なキャッシュ: 永続性は不要です。両方切れば、forkの負担もなくなります。埋め直せば済みます。
- セッションストア: 失うと全員がログアウトされます。AOF everysec程度です。
- キュー・ジョブの状態: 失うと仕事が消えます。AOFとレプリカです。
- 元のデータ: Redisを原本として使わないほうがよいです。あえて使うなら、AOF alwaysとレプリケーション、そして定期バックアップまで必要です。
最もよくある間違いは、キャッシュなのに永続化を有効にしておくことです。不要なforkのコストとディスクI/Oを払いながら、いざ復旧するときには古いキャッシュが復活して問題を起こします。
次の確認で見ること
永続化の設定変更は、ラボのPodのほかのラボに影響しうるので、クイズで代えます。RDB・AOFの消失の範囲と復旧コストを、状況別に区別できるかを確認して、コースを終えます。