【テクニカル・上級編】 no-appendfsync-on-rewrite設定 – Redis

Redisの深層:`no-appendfsync-on-rewrite` が引き起こすI/O地獄と、そのアーキテクチャ的必然

大規模なRedisクラスタの運用において、ディスクI/Oのレイテンシスパイクは常にエンジニアを悩ませる悪夢の一つだ。特に、AOF(Append-Only File)の永続化を有効にしている環境下では、フォークとバックグラウンド書き換え(AOFリライト)のプロセスが、システム全体のパフォーマンスを静かに、しかし確実に蝕んでいく。

その核心に位置するのが `no-appendfsync-on-rewrite` という、一見地味ながら極めてクリティカルな設定パラメータである。

本稿では、この設定がRedisの内部アーキテクチャ、OSのページキャッシュ、そしてストレージサブシステムとどのようにインタラクションするのか、その低レイヤのメカニズムを剥き出しにして解説する。

—

1. AOFリライトの裏側:何が起きているのか

`no-appendfsync-on-rewrite` の真価を理解するには、まずRedisがAOFリライトを実行する際のOSレベルの挙動を完全に把握する必要がある。

Redisは、AOFファイルの肥大化を防ぐために `BGREWRITEAOF` コマンドを発行する。この時、以下のプロセスツリーとI/O競合が発生する。

1. `fork()` によるCOW(Copy-On-Write)の発生
Redisのメインプロセスは `fork()` を呼び出し、子プロセスを生成する。この子プロセスがメモリ上の全データをスキャンし、新しいAOFファイルのシーケンスを構築してディスクに吐き出す。
2. 親プロセスによるコマンドの継続処理とAOF追記
リライト中も、親プロセスはクライアントからのリクエストを受け付け、メモリ上のデータ構造を更新し続ける。同時に、新たな操作ログは既存のAOFファイル(と、リライト中はバッファに)追記される。
3. OSの `fsync` 競合(ここがボトルネック)
デフォルトの設定では、`appendfsync` は `everysec` (1秒に1回)に設定されていることが多い。この設定下では、バックグラウンドのスレッド(またはメインプロセスから派生したI/Oハンドラ)が定期的に `fsync(2)` システムコールを呼び出し、OSのページキャッシュ上のダーティページを物理ディスクにフラッシュする。

問題は、子プロセスが巨大なAOFファイルをシーケンシャルに高速書き込みしている最中に、親プロセスの `fsync` が割り込む点にある。

HDDであれ、高速なNVMe SSDであれ、大量の書き込みストリームと同期的な `fsync` が同時に発生すると、ストレージコントローラやOSのI/Oスケジューラで激しい競合が発生する。結果として何が起きるか? `fsync` の完了がブロックされ、ディスクI/Oのレイテンシが数十ミリ秒から数百ミリ秒に跳ね上がる。

Redisはシングルスレッドで動くインメモリデータベースであるため、この `fsync` のブロックが完了するまで、メインスレッドの後続処理(他のクライアントのリクエスト)が完全に停止する。これが「AOFリライト中のレイテンシスパイク」の正体である。

—

2. `no-appendfsync-on-rewrite` のメカニズム:救済か、リスクか

このI/O競合を回避するために用意されたのが、`no-appendfsync-on-rewrite` である。

redis.conf

デフォルトは “no”
no-appendfsync-on-rewrite yes

この値を `yes` に設定した場合、AOFリライト(`BGREWRITEAOF` または RDBスナップショットのバックグラウンドセーブ)が実行されている間、Redisは親プロセスからの `fsync` リクエストを一時的に停止(実質的に `no` と同等の挙動に)する。

内部挙動のタイムライン

[通常時]
Client —> [Redis Main] –(write)–> AOF Buffer –(fsync every 1s)–> Disk

[AOFリライト中 (no-appendfsync-on-rewrite yes)]
Child Process —> [BGREWRITEAOF] ———————————> Disk (大量のI/O)
Client ———-> [Redis Main] –(write)–> AOF Buffer –(fsync一時停止)–> 保持 (Disk I/Oを守る)

子プロセスがディスク帯域を専有してリライト作業を行っている間、親プロセス側の `fsync` を止めることで、ストレージへの負荷分散を図るわけだ。これにより、AOFリライト中のレイテンシスパイクは劇的に抑制される。

—

3. 熟知すべきトレードオフ:データ消失の窓(Window of Vulnerability)

しかし、チーフアーキテクトとして警鐘を鳴らさなければならないのは、この設定が「可用性とパフォーマンスのトレードオフにおいて、耐久性を一時的に犠牲にする」という事実である。

`no-appendfsync-on-rewrite yes` に設定している間、最大でどれだけのデータ損失リスクが生じるだろうか?

  • 通常、`appendfsync everysec` では、最悪の場合でも過去1秒分のデータが失われるリスクにとどまる。
  • しかし、AOFリライトが数分間(データ量が数十GBに達していればそれ以上)続く間、`fsync` が完全にブロック(抑止)された場合、そのリライト期間全体(数分間)に書き込まれたデータが、OSのカーネル空間のページキャッシュ上にしか存在しない状態になる。

もしこの瞬間に、
1. Redisプロセスがクラフトした(Segfault等)
2. カーネルパニックが発生した
3. 物理サーバーの電源が予期せぬ形で喪失した(ロストパワー等)

こうした障害が発生した場合、リライト期間中のすべての更新データが完全に消失する。 `everysec` の保証が、数分単位のデータロスに化ける瞬間である。

—

4. 実戦的アーキテクチャ判断:この設定をどう扱うべきか

では、我々インフラストラクチャの設計者は、このパラメータをどのように扱うべきなのか。実戦知見に基づく結論を提示する。

ケース A: `no no` (デフォルト)を維持すべきワークロード

  • 金融系、決済系、厳密なカウンター管理など、1バイトのデータロスも許されないシステム。
  • ストレージに超高性能なPCIe Gen4/Gen5 NVMeを採用しており、IOPSおよびスループットのヘッドルーム(余裕)が十分に確保されている場合。OSのI/Oキュー深度やバッファキャッシュが競合を吸収できる環境であれば、リスクを冒してこの設定をいじる必要はない。

ケース B: `yes` に設定を倒すべきワークロード

  • セッションストア、キャッシュ層、リアルタイム分析のインジェストバッファなど、データロスの影響が限定的(または再構築が容易)なシステム。
  • 大規模データ(メモリ使用量数十GB〜数百GB超)を保持しており、AOFリライトのたびにクライアントのレイテンシが跳ね上がり、APIのタイムアウトや外形監視のアラートが発報してしまう環境。
  • この場合、ビジネス継続性とレイテンシの平滑化を優先し、`no-appendfsync-on-rewrite yes` を採用するべきだ。

—

5. 究極のアーキテクチャ解:AOFからの脱却と次世代の選択

ここまで `no-appendfsync-on-rewrite` の深層を解説してきたが、Redisの限界を突き詰めるトップエンジニアであれば、そもそも「巨大なAOFのリライトとディスクI/Oの競合」という構造自体を疑うべきである。

もしあなたが大規模なモダンインフラストラクチャを設計しているのであれば、以下のアーキテクチャを検討し尽くすべきだ。

1. RDB (Snapshotting) + AOFのハイブリッド運用を疑う
Redis 4.0以降導入されたハイブリッド永続化(RDBの先端にAOFをアペンドする方式)は強力だが、依然としてディスクI/Oの呪縛からは逃れられない。
2. クラウドネイティブストレージとの分離
ローカルSSDのI/O帯域に依存するのではなく、データ永続化の責任をRedisプロセスから切り離す。あるいは、可用性をRedis Clusterのレプリケーション(マルチAZ展開)に完全に委ね、単体ノードの永続化設定(AOF)をオフにする、あるいはRDBのスナップショットのみに絞るという選択肢だ。

  • 「メモリ上のデータは消えても、レプリカから同期できれば良い。もしくは非同期レプリカが数秒遅れているだけなら許容する」 という割り切りは、超大規模Webサービスにおいては極めて合理的である。

結言

`no-appendfsync-on-rewrite` は、単なる設定の一項目ではない。それは、「レイテンシ(パフォーマンス)」と「耐久性(データロス耐性)」という、分散システムにおける永遠のジレンマを、エンジニアがどのポイントで調停するのかを示す鋭い刃である。

デフォルト値の意味を盲信するのではなく、自社システムのワークロード、ストレージ特性、ビジネス上の許容リスクを極限まで見極め、アーキテクチャの全責任を背負った上で設定値を決定してほしい。それこそが、真のRedisエンジニアリングである。

コメント

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