【実務・中級編】 AOF書き換え(Rewrite) – Redis

こんにちは。システム全体のアーキテクチャとパフォーマンスのボトルネックに日々頭を悩ませているあなたへ。

今回は、Redisの永続化の心臓部であり、実務において最も「踏み絵」になりやすいテーマ、AOF(Append Only File)の書き換え(Rewrite)メカニズムについて、アーキテクトの視点から一切の妥協なく解説する。

「Redisのメモリが突然枯渇した」「フェイルオーバー時にマスターがフリーズした」「AOFの肥大化でコンテナの再起動に10分以上かかる」。
これらはすべて、AOF書き換えの挙動を解像度高く理解していないことが原因で引き起こされる人災だ。

公式ドキュメントには載っていない、OSの挙動、メモリ管理、そして実務で生き残るための設計パターンを叩き込む。覚悟して読み進めてほしい。

—

1. なぜAOFは「肥大化」し、なぜ「書き換え」が必要なのか

RedisのAOFは、すべての書き込み操作(`SET`, `HSET`, `LPUSH`など)を追記していくテキストログだ。極めてシンプルだが、ここには致命的な宿命がある。

例えば、ひとつのキーに対して以下のような操作を繰り返したとする。

SET counter 1
SET counter 2
SET counter 3
…
SET counter 1000000

AOFファイルには、この100万行のログがすべて残る。しかし、最終的に必要なデータは `SET counter 1000000` の1行だけだ。
このまま放置すれば、ファイルサイズは無限に膨れ上がり、Redisの再起動時(AOFの再生時)にすべてのコマンドを再実行するため、起動完了までに絶望的な時間がかかることになる。

そこで登場するのが `BGREWRITEAOF`(AOF書き換え) である。
これは、現在のメモリ上のデータセットを逆算し、「今の状態を最小限のコマンドで再現するためのクリーンなAOFファイル」を裏側で再構築するプロセスだ。

—

2. 内部メカニズム:フォーク(Fork)とCopy-on-Writeの深淵

「書き換え中もRedisは止まらずに書き込みを受け付け続けられる」
この魔法のような挙動を支えているのが、OSの機能である `fork()` と Copy-on-Write (CoW) だ。

アーキテクトとして、この内部で何が起きているのかを正確に把握しておく必要がある。

[Redis メインプロセス]
│
├─ 1. fork() 実行 (メモリ空間のコピーではなく、ページテーブルの複製のみ)
│ │
│ v
[子プロセス (BGREWRITEAOF)] ──> 2. メモリのスナップショットを読み取り、
│ 最小限のAOFコマンドを一時ファイルに書き出す
│
└─ [親プロセス (通常処理)] ────> 3. クライアントからの新規書き込みを受付
│
v
4. 【重要】AOF Rewriting Buffer にも書き込みを蓄積

プロセスの裏側で起きていることの真実

1. `fork()` の瞬間のレイテンシ:
メモリが数十GBに達している巨大なRedisインスタンスで `fork()` を呼ぶと、親プロセスのページテーブルをコピーする数ミリ秒〜数十ミリ秒の間、メインスレッドが完全にフリーズ(ブロック)する。これが「Redisが突然数秒間無応答になった」という障害の典型的な原因だ。
2. Copy-on-Writeの罠:
子プロセスがファイル書き出しを行っている最中、親プロセス(メイン)に書き込みリクエストが来ると、OSのCoW機能により、更新対象のメモリページが複製される。
書き込み(Churn)が激しいシステムでは、メモリ使用量が理論上最大で2倍に膨れ上がる。メモリの空き容量がカツカツの環境でこれをやると、LinuxのOOM Killerに容赦なくRedisが撃ち抜かれる。
3. AOF Rewriting Buffer:
子プロセスが書き換え作業をしている最中にも、クライアントからの新しい書き込みは発生する。これを取りこぼさないために、Redisは「AOF書き換えバッファ」を用意し、新しい書き込みをここに蓄積し続ける。子プロセスが完了した直後、このバッファの差分データを新しいAOFファイルに追記してアトミックに差し替える。

—

3. 実務で直面する設定値のチューニング(`redis.conf`)

デフォルトのままで本番運用するのは、シートベルトなしでF1を運転するようなものだ。以下のパラメータは、システムのワークロードに合わせて必ずレビューし、コードベース(IaC)で管理すべきだ。

— 自動書き換えのトリガー設定 —
前回の書き換え完了時のサイズと比較して、サイズが100%(2倍)に増加したら自動実行
auto-aof-rewrite-percentage 100

AOFファイルの最小サイズが64mb未満の場合は、いくら割合が増えても書き換えを行わない
auto-aof-rewrite-min-size 64mb

⚠️ チューニングの急所

高スループットなキャッシュ・セッションストアとしてRedisを使っている場合、`auto-aof-rewrite-min-size` をデフォルトの `64mb` のままにしていると、定常的に書き換えが走り続け、CPUとディスクI/Oが慢性的に圧迫される。
データセットが10GBあるような環境であれば、この値は `5gb` や `10gb` といった大きめの値に引き上げるべきだ。頻繁すぎる書き換えは百害あって一利なしである。

—

4. 堅牢な設計パターンとアンチパターン

設計レビューにおいて、私がエンジニアたちに必ず確認するポイントを伝授する。

【アンチパターン】ディスクのI/O性能をケチる

AOF書き換えは、裏で巨大なファイルをシーケンシャルにガリガリとディスクへ書き出す。
もしストレージに低速なネットワークストレージ(NIOなど)や、IOPS制限の厳しい安価なクラウドストレージを使っている場合、書き換え中のディスク遅延がRedis全体のレイテンシ(LATENCY)を悪化させる。

【設計パターン】永続化の分離とレプリケーション戦略

大規模なプロダクション環境では、「マスターではAOFを無効化し、レプリカ(従属ノード)でのみAOFを有効化する」という設計が極めて有効だ。

  • マスター (Master):

パフォーマンス最優先。AOFもRDBもオフ、あるいはRDBのみ軽度に行い、書き込みレイテンシを極限まで削る。

  • レプリカ (Replica):

マスターからのストリームを受け取り、ここでAOFを有効化して書き換えも行わせる。万が一の障害時には、このレプリカのAOFから復旧する。

この構成をとることで、マスターのCPUとディスクI/OをAOF書き換えの呪縛から完全に解放できる。

—

5. トラブルシューティング:書き換えが失敗・遅延するとき

もし「`AOF rewrite in progress`」のログが無限に続き、一向に終わらない、あるいはエラーで落ちる場合は、以下のコマンドとメトリクスを確認せよ。

現在のRedisの内部状態(メモリ、forkの状況などを確認)
INFO persistence

リアルタイムのレイテンシ履歴を確認
LATENCY latest

  • `aof_rewrite_in_progress:1` が常態化している場合:

ディスクの書き込み速度が、Redisのメモリ変更速度(Churn)に追いついていない。書き込み負荷がピークの時間帯を避け、夜間に手動で `BGREWRITEAOF` を叩く仕組み(あるいはアプリ側からの制御)を検討する必要がある。

  • `fork()` 自体が数秒かかる(レイテンシスパイク):

Linuxの Transparent Huge Pages (THP) が有効になっていないか確認しろ。データベースワークロードにおいて THP は百害あって一利なしだ。即座に無効化(`never`)設定にせよ。

—

チーフアーキテクトからの総括

AOFの書き換えは、単なる「ファイルの掃除」ではない。
「メモリの整合性」「OSのプロセス管理(fork/CoW)」「ストレージのI/O性能」の3つが高度に均衡して初めて成り立つ、高難度なバックグラウンド処理である。

「動いているから触らない」ではなく、データ量の増加曲線とハードウェアリソースを逆算し、適切なトリガー設定とプロセス分離の設計を行うこと。それこそが、障害に強い真にレジリエントなシステムを作り上げるエンジニアの仕事だ。

次の設計レビューでは、君たちのRedisがどのようなポリシーで永続化されているか、その口からロジカルに説明してもらおうか。期待している。

コメント

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