【実務・中級編】 no-appendfsync-on-rewrite設定 – Redis

【Redis深淵】`no-appendfsync-on-rewrite` が救う低レイテンシと、その裏に潜む「30秒のデータ消失リスク」の真実

本番環境でRedisを運用していて、「数時間に一度、ミリ秒単位の謎のレイテンシスパイクが発生し、APIのレスポンスタイムが悪化する」という現象に遭遇したことはないでしょうか。

APM(Application Performance Monitoring)のグラフを睨み、ネットワークやCPU使用率を調べても異常は見当たらない。しかし、Redisのログやメトリクスを注意深く観察すると、そのスパイクは「AOF(Append Only File)の書き換え(Rewrite)」のタイミングと完全に同期している――。

この問題の特効薬として語られるのが `no-appendfsync-on-rewrite` 設定です。しかし、この設定の裏には、パフォーマンスと引き換えにする強力なデータ消失リスク(トレードオフ)が隠されています。

今回は、テクニカルリードの視点から、この設定がカーネルレベルで何を引き起こしているのか、そして本番システムで「どう設計すべきか」の決定基準をロジカルに解説します。

—

1. 予備知識:なぜAOF書き換え時にディスクI/Oが激突するのか?

仕組みを正しく理解するために、まずはRedisの永続化(AOF)と、カーネル、そしてディスクI/Oの関係性を整理しましょう。

1-1. AOFの基本と `appendfsync everysec`

多くの実務環境では、永続化ポリシーとして `appendfsync everysec` を選択しているはずです。これは「1秒に1回、バックグラウンドで `fsync(2)` システムコールを呼び出し、カーネルのページキャッシュにあるデータを物理ディスクに強制同期(フラッシュ)する」という、パフォーマンスと信頼性のバランスに優れた設定です。

このとき、Redisはメインスレッドのブロッキングを防ぐため、`fsync` の実行を `bio`(Background I/O)スレッド に委譲します。

[Redis Main Thread] — (write) —> [OS Page Cache]
|
[Redis bio Thread ] — (fsync) ——–+—> [Physical Disk]

1-2. BGREWRITEAOF(AOF書き換え)の襲来

AOFはコマンドの履歴を愚直に追記していくため、時間が経つとファイルサイズが肥大化します。これを防ぐために、Redisは定期的に、または手動で `BGREWRITEAOF` を実行します。

`BGREWRITEAOF` が実行されると、Redisは `fork(2)` を行い、子プロセスを生成します。この子プロセスは、現在のメモリ上のデータから「最小限のコマンド群」を再構成し、新しいAOFファイルをディスクに一気書きします。

ここで問題が発生します。
子プロセスが数GBから数十GBのデータを物理ディスクに猛烈な勢いで書き込んでいる(大量の `write`)まさにその最中にも、親プロセス(正確には `bio` スレッド)は `appendfsync everysec` のルールに従って、1秒に1回の `fsync` を呼び出し続けるのです。

—

2. カーネルの悲鳴:なぜメインスレッドがブロックするのか?

「`fsync` はバックグラウンドの `bio` スレッドが実行しているのだから、メインスレッドのレイテンシには影響しないはずでは?」

そう考えたなら、一歩踏み込みが足りません。ここに、多くのエンジニアが陥る落とし穴があります。

2-1. `fsync` のブロッキング特性

Linuxカーネルにおいて、同一ディスクデバイス(あるいは同一ファイルシステム)に対して大量の dirty ページ(未書き込みデータ)の書き出し(子プロセスのAOF書き換え処理)が走っている最中に `fsync` を呼び出すと、その `fsync` は先行する大量のI/O要求が完了するまでカーネルレベルでブロックされます。

2-2. Redisメインスレッドの自衛ロジック(aof_delayed_fsync)

Redisのメインスレッドは、クライアントからのリクエストを処理し、コマンドをメモリ上のAOFバッファに `write(2)` しようとします。

しかし、前述の通り `bio` スレッドが `fsync` のブロックに捕まっていると、前回の `fsync` がまだ完了していません。Redisは「前回の `fsync` から2秒以上経過している場合、データの安全性を担保するため、メインスレッド自身を停止させて `fsync` の完了を待つ」というハードコードされた自衛ロジックを持っています。

[Main Thread] — write() –> [Page Cache]
^
| (2秒以上 fsync が終わっていない場合、メインスレッドがブロック!)
v
[bio Thread ] — fsync() –> [Disk (BGREWRITEAOFの大量I/Oで大渋滞中…)]

これが、AOF書き換え中にRedisの応答が突然数ミリ秒〜数秒間ストップする(レイテンシスパイク)の真のメカニズムです。Redisのログに以下の警告が出力されている場合、まさにこの現象が発生しています。

Asynchronous AOF fsync is taking too long (whichever performant option). Writing to the parent dir cannot be completed in under two seconds.

—

3. `no-appendfsync-on-rewrite` の挙動とトレードオフ

このI/Oのデッドロック状態を回避するために用意されたのが、`no-appendfsync-on-rewrite` 設定です。

redis.conf
no-appendfsync-on-rewrite yes

設定値の挙動比較

| 設定値 | メリット | デメリット(データ消失リスク) |
| :— | :— | :— |
| `no` (デフォルト) | AOF書き換え中であっても、常に `everysec`(1秒間隔)での `fsync` を試み、データ損失を最小限に抑える。 | 子プロセスによる大量I/Oと競合し、メインスレッドで深刻なレイテンシスパイク(ブロッキング)を引き起こす。 |
| `yes` | 子プロセスが書き換え(またはRDB保存)を実行している間、親プロセスは `fsync` の実行を意図的に一時停止(スキップ)する。これによりディスク競合がなくなり、レイテンシが極めて安定する。 | 書き換えが実行されている間(数秒〜数分間)、データは物理ディスクに同期されず、カーネルのページキャッシュ上に留まる。この間にサーバーがクラッシュ(停電、カーネルパニック等)すると、書き換え開始から終了までの全データが消失する。 |

「30秒のデータ消失リスク」の正体

`no-appendfsync-on-rewrite yes` に設定している間、データの物理ディスクへの書き出しは、Linuxカーネルの標準的な書き戻し(writeback)アルゴリズムに完全に委ねられます。

Linuxのデフォルト設定(`/proc/sys/vm/dirty_expire_centisecs`)では、ページキャッシュの保持期限は 30秒 です。つまり、最悪のシナリオ(ハードウェアの突然死やOSハングアップ)においては、最大30秒分の書き込みデータが完全に消え去ることを意味します。

—

4. 実務における「決断基準」と設計パターン

テクニカルリードとして、この設定を `yes` にすべきか `no` にすべきか、どう判断すべきでしょうか。単なる勘ではなく、システムの非機能要件からロジカルに導き出します。

パターンA:極低レイテンシが至上命題のシステム(推奨:`yes`)

  • 対象: ソーシャルゲームのセッション管理、キャッシュレイヤー、リアルタイムのランキングなど。
  • 判断: 数ミリ秒の遅延がユーザー体験の致命的な悪化(エラーやタイムアウト)に直結するため、`yes` を選択します。
  • リスクヘッジ:
  • Redisのプロセスがクラッシュ(`SIGKILL` など)しただけであれば、カーネル内のページキャッシュは生きているため、データは失われません。失われるのは「物理的な電源断」や「ハイパーバイザーのクラッシュ」の時だけです。
  • 物理ハードウェアや仮想インスタンスの冗長化(Redis Sentinel や Redis Cluster)を徹底し、マスタが死んだら即座にレプリカへフェイルオーバーする構成にします。

パターンB:金融取引、決済、メッセージキューなど1件のロスも許されないシステム(推奨:`no`)

  • 対象: 簡易的な台帳、ジョブキュー(Celery等)、ユーザーの資産情報を一時的に保持する領域。
  • 判断: レイテンシの安定よりも「データの永続性」が最優先されるため、デフォルトの `no` を維持します。
  • リスクヘッジ:
  • ディスクI/Oのボトルネックをハードウェア(インフラ)レイヤーで解決します。
  • AWS環境であれば、EBSのボリュームタイプを `gp3`(IOPSやスループットを個別にプロビジョニング)にするか、より高速な `io2` を選択します。
  • ローカルNVMe SSDを搭載したインスタンスを選択し、I/O性能の限界値を引き上げます。

—

5. 運用の現場で使える実践テクニック

「レイテンシは安定させたいが、データ消失リスクも極力下げたい」という妥協なきエンジニアのために、現場で実践すべき3つのアプローチを紹介します。

5-1. `aof_delayed_fsync` の監視

Redisが実際に `fsync` の遅延によってブロックされた回数は、`INFO` コマンドでリアルタイムに監視できます。

$ redis-cli INFO stats | grep aof_delayed_fsync
aof_delayed_fsync:124

  • 監視設計: このカウンター(`aof_delayed_fsync`)の上昇率を監視システム(Datadog, Prometheus等)でメトリック化してください。AOF書き換えのタイミングでこの値が急増している場合、ディスクI/Oが限界に達している証拠です。

5-2. 動的な設定変更(ランタイムチューニング)

`no-appendfsync-on-rewrite` は、Redisを再起動することなく、オンラインで安全に変更可能です。

現在の設定を確認
127.0.0.1:6379> CONFIG GET no-appendfsync-on-rewrite
1) “no-appendfsync-on-rewrite”
2) “no”

オンラインで “yes” に変更(即座に反映される)
127.0.0.1:6379> CONFIG SET no-appendfsync-on-rewrite yes
OK

この特性を利用し、「通常時は `no`(データ保護優先)とし、バッチ処理や深夜のメンテナンス時間帯など、意図的に重い書き換えをトリガーする直前だけ `yes` に切り替える」というトリッキーかつ合理的な運用も可能です。

5-3. 自動書き換えの閾値調整による、I/O発生頻度のコントロール

デフォルトでは、AOFファイルが前回の書き換え時から100%増加すると自動的に `BGREWRITEAOF` が走ります。これを大きめの値に設定し、書き換えの発生頻度自体を減らすアプローチも有効です。

redis.conf
auto-aof-rewrite-percentage 200 # 100%から200%(3倍のサイズ)に引き上げ
auto-aof-rewrite-min-size 128mb # 最低でも128MB以上でトリガー

これにより、ピークタイム中の突発的な書き換え発生確率を下げ、ディスク競合の機会損失を最小化します。

—

まとめ:チーフアーキテクトからのアドバイス

`no-appendfsync-on-rewrite` の選択は、単なる「パフォーマンス設定」ではありません。それは、「あなたのシステムは、インフラの物理障害時に最大30秒のデータ巻き戻りを許容できるか?」という、ビジネス要件そのものに対する問いです。

  • 「レイテンシの安定が生命線」なら、迷わず `yes` に設定し、その代わりレプリケーション構成や電源の冗長化を徹底すること。
  • 「データの整合性が生命線」なら、`no` のまま維持し、高速なSSD(NVMeなど)やプロビジョニングされたI/O帯域へ投資すること。

この二者択一をロジカルに整理し、プロジェクトの非機能要件定義書に落とし込むことこそが、アーキテクトとしての真の仕事です。あなたのシステムの「生命線」がどちらにあるか、今一度見極めてみてください。

コメント

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