【実務・中級編】 BGREWRITEAOFコマンド – Redis

Redisを本番環境で運用するにあたり、最もエンジニアの力量が試されるのが「永続化(Persistence)の設計」だ。インメモリの圧倒的なスループットを維持しつつ、障害復旧に耐えうる堅牢性をどう担保するか。

今回は、AOF(Append Only File)運用において避けて通れないコア技術である `BGREWRITEAOF`(バックグラウンドAOFリライト) について、アーキテクチャの深層から本番運用のリアルなプラクティスまでを解説する。

安易な設定で本番障害を引き起こす前に、裏で何が起きているのかを完全に理解しておこう。

—

1. なぜAOFリライトが必要なのか?

AOFは、Redisが受け取った書き込みコマンドを追記(Append)し続けるログ構造だ。
例えば、同一キーに対して100万回 `INCR counter` を実行した場合、AOFファイルには100万行の `INCR` が書き込まれる。

[追記され続けるAOFログ]
INCR counter (1)
INCR counter (2)
…
INCR counter (1000000)

これをそのまま放置すれば、ディスク容量を圧迫するだけでなく、インスタンス再起動時のリストア(リカバリ)に膨大な時間がかかり、SLAを著しく損なうことになる。

そこで登場するのが `BGREWRITEAOF` だ。
このコマンドは、過去の膨大な履歴ログを解析・圧縮するのではない。「現時点のインメモリデータセット」を直接スキャンし、その状態を復元するために必要最小限のコマンド列(最短の表現)を新規生成する。

上記の例なら、最終的な出力は `SET counter 1000000` のたった1行に集約される。

—

2. BGREWRITEAOFの内部挙動とメカニズム

メインスレッドをブロックせずに巨大なデータセットをファイル出力するために、RedisはLinuxのプロセスアーキテクチャを極限まで活用している。

[Main Process (Redis Engine)]
│
├─ fork() ──────────┐
│ ▼
[Handles Traffic] [Child Process]
(Copy-on-Write) – メモリ状態を走査
│ – 最小のAOFファイルを生成
▼ ▼
[Disk / Base AOF] ◀── [Atomic Rename / Merge]

① `fork()` と Copy-on-Write (CoW)

`BGREWRITEAOF` が呼ばれると、Redisは `fork()` システムコールを発行し、子プロセスを生成する。
子プロセスは親プロセスと同一のメモリ空間を共有(参照)するが、物理的な全メモリコピーは発生しない。これがOSの Copy-on-Write (CoW) 機構だ。

子プロセスが固定されたメモリのスナップショットを順次ディスクに書き出している間、親プロセスはクライアントからの新規リクエストを処理し続ける。親プロセスがメモリを書き換えたページだけが、OSによって複製される。

② Redis 7.0以降の革命:Multi-Part AOF (MP-AOF)

Redis 6以前のリライト処理は、リライト中に発生した差分を親プロセスが「メモリバッファ」に溜め込み、最後に巨大な差分をディスクにフラッシュして結合していた。これがメモリ浪費とI/Oスパイクの温床だった。

Redis 7.0で導入された Multi-Part AOF では、この設計が根本から刷新されている。
AOFは以下の3種に分割管理されるようになった。

1. Base File: リライト時に生成されるスナップショットデータ。
2. Incremental File: リライト中に親プロセスが書き込む差分ログファイル。
3. Manifest File: どのBaseとIncrementalが有効かを追跡するメタデータ。

リライトが完了した瞬間、Manifestファイルをアトミックに更新するだけで終了するため、従来のメモリ消費スパイクや結合フェーズの遅延が劇的に排除されている。

—

3. コマンドの実行と自動トリガー設計

明示的実行

Redis CLIからの非同期実行
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started

進行状況の確認
127.0.0.1:6379> INFO persistence
persistence
aof_rewrite_in_progress:1 # 1ならリライト中
aof_current_rewrite_time_sec:3 # 経過秒数
aof_last_bgrewrite_status:ok # 前回の成否

自動実行(`redis.conf` の黄金律)

本番環境では、ファイルの成長率とサイズをトリガーにして自動実行させるのが一般的だ。

前回リライト完了時のサイズから何%増加したら実行するか(100 = 2倍になったら)
auto-aof-rewrite-percentage 100

リライトを発火させる最小ファイルサイズ(小さすぎるファイルのリライト頻発を防ぐ)
auto-aof-rewrite-min-size 64mb

設計レビューでの指摘ポイント:

初期データが数GBある環境で `auto-aof-rewrite-min-size 64mb` のデフォルトのまま運用すると、起動直後から頻繁にリライトが走り、不要なI/O負荷を生む。初期データセットの規模に応じて、この値は `1gb` や `4gb` など適切に引き上げるべきだ。

—

4. 本番環境で踏みがちな罠とパフォーマンス対策

ここからがアーキテクトとして最も注視すべきポイントだ。

① `fork()` のレイテンシスパイク

`fork()` はCoWのおかげで高速だが、「ページテーブルの複製」自体はメインスレッドで同期的に行われる。
メモリ使用量が数十GB〜数百GBに達する場合、ページテーブルのコピーだけでメインスレッドが数十ミリ秒〜数百ミリ秒ブロックされることがある。Redisにおいてミリ秒単位の停止はタイムアウトを意味する。

  • 対策: 1インスタンスあたりのメモリサイズを大きくしすぎない(スケールアップより、クラスター構成によるスケールアウトを推奨。1インスタンスあたり最大でも16GB〜32GB程度に抑える)。

② Copy-on-Write によるメモリ倍増と OOM Killer

リライト実行中に大量の書き込み(Update/Delete)が発生すると、CoWによって変更されたページが次々と実メモリに複製される。最悪の場合、元のメモリ使用量の2倍近くまで実メモリ消費が跳ね上がり、OSのOOM Killerにプロセスを強制終了される。

  • Linuxカーネル必須設定:

# メモリオーバーコミットを許可(必須)
sysctl vm.overcommit_memory=1

# Transparent Huge Pages (THP) の無効化(超重要)
# 有効だと2MB単位でCoWが走り、メモリ消費とI/Oが爆発する
echo never > /sys/kernel/mm/transparent_hugepage/enabled

③ ディスクI/O競合の回避

リライトによる連続的なディスク書き込みが、メインスレッドの定常的な `fsync` と競合し、メインスレッドがブロックされるケースがある。

  • 設定による緩和策:

# BGSAVEやBGREWRITEAOFの実行中はメインプロセスのfsync呼び出しを一時抑制する
no-appendfsync-on-rewrite yes

※注意: この設定を有効にすると、リライト中にOSがクラッシュした場合に最大数秒〜30秒程度のデータロストのリスクを許容することになる。可用性要件と相談して決めること。

—

5. テクニカルリードが選ぶ「最強の永続化パターン」

現代のRedis本番運用において、最も洗練されたアプローチは 「RDB-AOF ハイブリッド形式」 だ。

redis.conf
aof-use-rdb-preamble yes

この設定を有効にすると、`BGREWRITEAOF` はBaseファイルとしてコンパクトかつロードが極めて高速なバイナリ形式(RDB形式)を書き出し、その後の差分だけを従来のAOFテキスト形式で追記する。

[AOFファイルの構造(ハイブリッド)]
+————————–+——————————+
| RDB Header (Binary) | Incremental Commands (Text) |
| 状態の完全スナップショット | リライト以降の差分ログ |
+————————–+——————————+

ハイブリッド形式の圧倒的メリット:

1. リライト後のファイルサイズが劇的に小さい
2. インスタンス障害復旧時のロード時間が圧倒的に速い(テキスト解析が最小限で済むため、復旧速度が数倍〜10倍向上する)

—

まとめ

`BGREWRITEAOF` は、単なる「ログの掃除屋」ではない。Redisのインメモリ性能とリカバリ速度のバランスを成立させている心臓部の一つだ。

設計レビューやインフラ構築時には、以下のチェックリストを必ず確認してほしい。

1. `aof-use-rdb-preamble yes`(ハイブリッド構成)が有効になっているか?
2. OSの `THP` は無効化され、`overcommit_memory=1` に設定されているか?
3. データ更新頻度に対して、リライト中のCoW用メモリマージンは確保されているか?
4. 自動リライトのトリガー(`percentage` / `min-size`)はデータ規模に見合っているか?

内部の仕組みを理解し、ハードウェアとカーネルの挙動まで見据えた堅牢なRedisクラスタを設計しよう。

コメント

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