AOFリライトの深淵:`no-appendfsync-on-rewrite` が救うI/Oストールの悪夢
大規模なインメモリデータストアの運用において、永続化レイヤーの挙動はシステム全体の生死を分ける境界線だ。
Redisはその圧倒的なスループットの裏で、ディスクとの同期という物理的な制約と常に格闘している。
Append Only File (AOF) は、Redisのデータ安全性を担保する上で不可欠なメカニズムだが、データの肥大化に伴い避けて通れないのが AOFリライト(`BGREWRITEAOF`) である。
そして本稿でメスを入れるのは、このリライトが実行されている最中の `fsync` の挙動、すなわち設定ディレクティブ `no-appendfsync-on-rewrite` の内部メカニズムとその極限のチューニング知見だ。
教科書的な説明は省く。ここでは、OSのカーネル空間とRedisのプロセスモデルの境界で何が起きているのか、その実務的な真実だけに焦点を当てる。
—
1. AOFリライトと `fsync` の根本的矛盾
RedisがAOFを使用する場合、通常は `appendfsync` ディレクティブによって同期的または非同期的なディスクフラッシュが制御される。
選択肢は主に以下の3つだ。
- `always`: すべての書き込みコマンドごとに `fsync` を実行(安全だが遅い)
- `everysec`: 1秒に1回、バックグラウンドスレッドで `fsync` を実行(推奨されるデフォルト)
- `no`: OSのページキャッシュのフラッシュに委ねる(最速だが危険)
ここで問題になるのが、AOFリライト中に発生する巨大なI/O負荷だ。
`BGREWRITEAOF` がトリガーされると、親プロセスは `fork()` し、子プロセスが現在のメモリ上のデータセットを走査して、それを再構築するための最小限のコマンド群として新しいAOFファイルの生成を開始する。
この子プロセスがディスクへデータを吐き出している間、親プロセスは通常通りクライアントからのリクエストを処理し、旧AOFファイルへの追記(Append)と、OSのページキャッシュへの書き込みを続ける。
悪夢のシナリオ:I/Oブロッキングの発生
もし `appendfsync` が `always` あるいは `everysec` に設定されている状態で、子プロセスが激しくディスクへAOFリライトのデータを書き込んでいるとき、何が起きるか?
Linuxカーネルのブロックレイヤーやファイルシステム(ext4やXFS)は、大量の連続した書き込み(Sequential Write)と、親プロセス側から発生するランダム/小刻みな追記I/Oを処理しきれなくなる。
この時、バックグラウンドの `fsync`(または親プロセスのメインスレッドからの同期的 `fsync`)が発行されると、OSの `fsync` システムコールはディスクコントローラからの完了通知(Flush Cache)を受け取るまでブロックされる。
結果として何が起きるか?
- `fsync` がブロックされている間、その背後にあるすべてのファイル書き込みがスタックする。
- 親プロセスが `write()` システムコールを発行した際、カーネルのページキャッシュがダーティページの閾値(`vm.dirty_background_ratio` / `vm.dirty_ratio`)に達していると、`write()` 自体もブロックされる。
- Redisのメインスレッドが完全にフリーズ(I/Oストール)し、レイテンシが数千ミリ秒に跳ね上がる。
—
2. 救世主か、諸刃の剣か:`no-appendfsync-on-rewrite` の内部メカニズム
このI/O競合とストールを防ぐために用意されたのが、以下の設定だ。
no-appendfsync-on-rewrite yes
このパラメータを `yes` に設定した場合のRedisの内部挙動を正確に理解しているエンジニアは意外と少ない。単に「リライト中は `fsync` を止める」というフワッとした理解では、本番障害を防ぐことはできない。
ソースコードレベルの挙動(簡略化された概念モデル)
RedisのバックグラウンドI/O処理(`bio.c`)およびAOF書き込みのロジックにおいて、リライトフラグ(`aof_rewrite_in_progress`)が真の間、`appendfsync` が `everysec` または `always` であっても、`fsync()` の実行要求は一時的に保留(スキップ)される。
/ 疑似コード:AOF fsyncの判定ロジック /
if (server.aof_state == AOF_ON) {
if (server.aof_no_fsync_on_rewrite &&
(server.aof_child_pid != -1 || server.rdb_child_pid != -1)) {
// リライト中またはRDB保存中は fsync のスケジュールをバイパスする
return;
}
// 通常通りの fsync キューイング
bioCreateBackgroundJob(BIO_FAO_FSYNC, …);
}
つまり、`no-appendfsync-on-rewrite yes` は、リライトという極限のI/O負荷がかかっている最中に、さらなるI/O待ち(`fsync`)を重ね合わせることで発生するレイテンシの雪崩を意図的に回避するための機構である。
—
3. 「トレードオフ」の正体:データ消失のリスクと境界条件
チーフアーキテクトとして警鐘を鳴らさなければならないのは、この設定がもたらすリスクの正確な見積もりだ。
`no-appendfsync-on-rewrite yes` に設定した場合、AOFリライトが実行されている期間(数分から、データ量によっては数時間におよぶこともある)において、実質的に `appendfsync no` と同等の状態になる。
万が一の障害時の挙動
もし、AOFリライトの最中にOSがパニックを起こしたり、電源がロストしたりした場合:
1. リライト中の新しいAOFファイルは破損するか、あるいは生成が完了していないため使い物にならない。
2. 同時に、親プロセス側でも `fsync` が長期間実行されていなかったため、旧AOFファイルに追記されたはずの直近のデータ(通常は1秒以内だが、この場合はリライト中の全期間)が、OSのページキャッシュ上に残留したままロストする可能性がある。
「リライト中の数分間にサーバーがクラッシュした場合、その間のデータ損失のウィンドウが最大化する」
これが、この設定を受け入れる代償だ。
—
4. 極限の現場におけるアーキテクチャ判断
では、我々はどうこの設定を扱うべきか? 実務的な指針を提示しよう。
パターンA: 書き込みスループットとレイテンシを極限まで優先するシステム(推奨設定)
- 対象: キャッシュ層、セッションストア、データが消失しても上流から再構築可能なシステム。
- 設定: `no-appendfsync-on-rewrite yes`
- 理由: I/Oストールによるコネクション枯渇や、それに伴う周辺マイクロサービスの連鎖的タイムアウト(Cascading Failure)の方が、データ損失リスクよりも圧倒的に悪影響が大きいからだ。
パターンB: 金融トランザクションや絶対にデータを落とせないシステム
- 対象: 厳密な台帳、カウンター、永続的なストアとしてRedisを使ざるを得ないアーキテクチャ。
- 設定: `no-appendfsync-on-rewrite no` (デフォルト)
- 代替の対策:
- Redis単体でのAOFリライトに頼るのではなく、NVMe SSD等の超高速なI/Oサブシステム(IOPSおよびFUA性能が高いデバイス)へデータディレクトリを配置する。
- あるいは、リライトが高頻度で発生しないように、メモリ使用量のピークを見越してインスタンスサイズを適切に分割し、リライト時間を物理的に最小化する(数秒〜数十秒で終わらせる)。
- そもそも、極度に厳密な永続性が求められるユースケースでは、Redis以外のストレージエンジン(PostgreSQL, Spannerなど)を選択するべきであり、アーキテクチャの適材適所を見直す。
—
5. OSレイヤーのチューニングと併用すべき鉄則
`no-appendfsync-on-rewrite` を有効にするだけでは、真のプロフェッショナルとは言えない。Linuxカーネルのメモリ・I/Oパラメータを同時に調律して初めて、システムの限界を突破できる。
以下のカーネルパラメータが適切に設定されているか確認せよ。
ダーティページがディスクへフラッシュされ始める割合を適切に抑え、
まとまったI/Oスパイクを防ぐ
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
デフォルトのままだと、カーネルはメモリの20%〜40%がダーティになるまでフラッシュを怠り、限界に達した瞬間にすさまじい量のI/Oをディスクに叩きつける。これがRedisの `fsync` と競合したとき、最悪のレイテンシスパイクが完成する。
結論
`no-appendfsync-on-rewrite` は、「ディスクI/Oの物理的限界によるサービスの完全停止(ストール)」を防ぐための安全弁である。
リライト中のデータ損失リスクというトレードオフを正しく理解し、システム全体の可用性とレイテンシの契約(SLA)に照らし合わせて適用を判断せよ。技術の本質を知る者にとって、設定ファイルのデフォルト値を疑うことこそが、高可用性システム構築の第一歩なのだから。
コメント