【テクニカル・上級編】 rdb-save-incremental-fsync設定 – Redis

RDB永続化の暗黒面:`rdb-save-incremental-fsync` が救うI/Oストールの極限最適化

Redisのアーキテクチャにおいて、永続化は常にパフォーマンスとのトレードオフの歴史だった。
インメモリデータベースとしての圧倒的なスループットを維持しながら、如何にしてデータのロストを防ぐか。その二律背反に挑む中で、RDB(Redis Database)のスナップショット生成、すなわち `BGSAVE` は最も根が深い問題を抱えてきた。

特に、数十年規模のキャリアを持つシニアエンジニアであれば、大規模なRedisインスタンス(例えば64GB〜128GBのRAMを消費するノード)で `BGSAVE` が完了した瞬間、アプリケーションのレイテンシが突如として跳ね上がり、数秒間の完全なフリーズ(ストール)を引き起こした悪夢を一度や二度は経験しているはずだ。

原因は明確である。OSのページキャッシュとストレージサブシステムの帯域を巡る「I/Oの暴走」だ。

この致命的なボトルネックを直撃し、現代のLinuxカーネルの挙動を熟知した上でねじ伏せるために導入された設定が `rdb-save-incremental-fsync` である。
本稿では、このパラメータがRedisの内部メカニズムとOSのカーネル空間で何を引き起こしているのか、その極限の低レイヤ知見を解き明かす。

—

1. なぜ `BGSAVE` はストレージを破壊するのか?(カーネルの闇)

問題の根源を理解するには、Redisプロセスが `fork()` し、親プロセスのメモリ空間をコピーオンライト(COW)でシリアライズし、ディスクに吐き出すプロセスを低レイヤから見つめ直す必要がある。

[Redis Parent] –(fork)–> [Redis Child (BGSAVE)]
│
▼ (write() system call)
[OS Page Cache]
│
(Dirty pages accumulation)
│
▼ (fsync() – デフォルトの挙動)
[Storage Subsystem (SSD/HDD)]

  • ここでI/Oスパイク(IO Stall)が発生する

子プロセスは `write(2)` システムコールを連続して呼び出し、直列化されたRDBデータをファイルディスクリプタに流し込む。このデータは一度、OSのページキャッシュ(Page Cache)に蓄積される。ここまではメモリ上の転送であるため、高速に処理される。

問題は、このバッファリングされたデータがフラッシュ(同期)される瞬間だ。

デフォルト挙動の罠:バーストする `fsync`

デフォルトでは、RDBファイルの書き込みが完了した際、あるいはカーネルのバックグラウンドデーモン(`pdflush` / `flush` / `kswapd`)や明示的な処理によって、OSはページキャッシュ上のダーティページをストレージデバイスへ強制的にフラッシュする。

数ギガバイトに及ぶRDBファイルが生成された場合、OSは短時間に膨大な量のI/Oリクエストをストレージコントローラーへ叩き込む。
これにより何が起きるか?

1. I/Oキューの飽和: ストレージデバイス(NVMe SSDであっても)のキューが溢れ、レイテンシが急増する。
2. システム全体のブロック: `fsync(2)` を呼び出したプロセス、あるいはファイルシステム(ext4やXFS)のジャーナリング層でロック競合が発生し、ストレージの応答待ち(Dステート:Uninterruptible Sleep)のプロセスが激増する。
3. Redis親プロセスの巻き添え: 親プロセス側はメモリ上の処理を行っているだけに見えるが、OSがメモリ管理やディスクI/Oのフラッシュにリソース(CPU/バス)を奪われたり、AOFの書き込みやスワップの発生によって、ミリ秒単位のレイテンシバースト(スパイク)を引き起こす。

—

2. `rdb-save-incremental-fsync` の内部メカニズム

このI/Oの「津波」をせき止め、定常的な流路にコントロールするために導入されたのが、`rdb-save-incremental-fsync yes` という設定である。

この設定を有効にすると、Redisの子プロセスはRDBファイルをディスクに書き出す際、単に `write()` を続け、最後にまとめて `fsync()` を呼ぶのではなく、一定量(デフォルトでは32MBごと)の書き込みが進行するごとに、明示的に `sync_file_range(2)` または `fsync(2)` を呼び出すようになる。

/ Redisのソースコード(rdb.c周辺の概念的擬似コード)から読み解く挙動 /
off_t written_since_last_fsync = 0;
size_t fsync_bytes_limit = 32 1024 1024; // 32MB毎にfsync

while (data_remains) {
ssize_t n = write(fd, buf, chunk_size);
written_since_last_fsync += n;

if (server.rdb_save_incremental_fsync &&
written_since_last_fsync >= fsync_bytes_limit) {

// カーネルに対して、この範囲のページキャッシュをストレージへフラッシュするよう指示
// これにより、I/Oが細かく分散され、スパイクが抑制される
rioWriteFlush(&rdb);
if (redis_fsync(fd) == -1) {
// エラーハンドリング
}
written_since_last_fsync = 0;
}
}

カーネル空間での挙動変化

このインクリメンタルな `fsync` がもたらす最大のメリットは、OSのダーティページの蓄積量をコントロールできる点にある。

  • OFFの場合: 10GBの書き込み中、OSはページキャッシュにひたすらデータを溜め込み、最後に一気にフラッシュを試みる(I/Oの暴力)。
  • ONの場合: カーネルは32MBごとに「このブロックをストレージに書き込め」という指示を細かく受ける。これにより、ストレージのI/Oコントローラーはワークロードを平準化し、バックグラウンドで細かくフラッシュを処理できる。

結果として、ストレージ帯域の占有率が均し(ナラシ)られ、Redis本体のレイテンシメトリクス(`latency-monitor`)に見られるような、`BGSAVE` 終了時のレイテンシスパイクが綺麗に消失する。

—

3. 運用・アーキテクチャ上のトレードオフ:実務的知見

「それなら、常に `rdb-save-incremental-fsync` は `yes` にすべきだ」と短絡的に結論づけるのは、アーキテクト失格である。すべてのトレードオフを理解し、システム全体の特性に合わせてチューニングするのがプロの仕事だ。

メリット

1. レイテンシの予測可能性(Predictability):
大規模なキャッシュサーバーにおいて、レイテンシの「テール(99パーセンタイルや99.9パーセンタイル)」を安定させる効果は絶大である。SLAが厳格なマイクロサービス基盤では必須の設定と言える。
2. OOM Killerやカーネルパニックの回避:
巨大なダーティページがメモリ上に居座り続けると、メモリプレッシャーが高まった際にカーネルのフラッシャービヘイビアが変わり、思わぬシステム全体のたわみを生むことがある。これを防ぐ。

デメリットとリスク

1. 全体の書き込みスループット(Throughput)の低下:
`fsync` を頻繁に呼び出すということは、それだけCPUがディスクコントローラーからの完了を待つ(あるいはカーネルのI/Oパイプラインが細切れになる)ことを意味する。
極限まで `BGSAVE` の完了時間を短縮したい(=ストレージの最大帯域を限界まで使い切りたい)環境では、トータルのバックアップ完了時間がわずかに伸びる可能性がある。
2. ストレージの寿命(書き込みアンプ):
SSDへのフラッシュ回数が増えるため、フラッシュコントローラーのガベージコレクションに影響を与える可能性があるが、通常のRDB生成頻度であれば実用上問題になるレベルではない。

—

4. チューニングと検証の実践

この設定を本番環境に投入する際は、単に `redis.conf` を書き換えるだけではなく、必ずオブザーバビリティを確保した状態で負荷試験を行うべきである。

設定値の確認と動的変更

Redis 7.0以降では、`CONFIG SET` を用いて動的にこの挙動を制御・検証できる。

現在の設定値を確認
127.0.0.1:6379> CONFIG GET rdb-save-incremental-fsync
1) “rdb-save-incremental-fsync”
2) “yes”

必要に応じて切り替え(基本的には yes 推奨)
127.0.0.1:6379> CONFIG SET rdb-save-incremental-fsync yes
OK

観測すべきメトリクス

検証時には、単にRedisのレイテンシだけでなく、OSレベルのI/O統計を必ず同時に監視すること。

iostatでストレージのw/s(書き込みIOPS)とawait(平均待機時間)を監視
$ iostat -xz 1 10

  • 設定 `no` の場合: `BGSAVE` 中盤までは静かだが、終了間際に `await` が数百ミリ秒〜数千ミリ秒に跳ね上がり、`util` が 100% に張り付く。
  • 設定 `yes` の場合: `BGSAVE` 期間中を通じて `await` が低く抑えられ、スパイクの山がなだらかな丘のようになる。

—

結論:プロフェッショナルが取るべき選択

`rdb-save-incremental-fsync` は、大規模データベースの運用における「知られざる急所」を保護するための、極めて洗練されたエンジニアリングの結晶である。

メモリ容量が数十GBを超え、RDBのファイルサイズがストレージのキャッシュ容量を超えるようなモダンなインフラストラクチャにおいて、デフォルトの挙動(一括フラッシュ)を放置することは、自らシステムに「定期的な爆弾」を仕掛けているようなものだ。

特にレイテンシの平滑化がビジネスの生死を分けるFintechやリアルタイム・アドテク、大規模セッションストアの基盤においては、この設定を `yes` に固定し、システムの挙動を計測・証明することが、プロフェッショナルなインフラストラクチャ・エンジニアの条件である。

細部を疎かにする者に、高負荷な分散システムの神は微笑まない。カーネルとストレージ、そしてRedisの呼吸を合わせること。それこそが真のアーキテクチャである。

コメント

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