Redisを「ただの速いKVS」だと思っているなら、君の設計したシステムはいずれ本番環境で火を噴くことになるだろう。
私はこれまで、秒間数十万クエリを捌く巨大なクラスタから、ミリ秒の遅延すら許されない金融系システムまで、数多くのRedis運用現場を渡り歩いてきた。そこで目にした悲劇の多くは、「メモリ内にあるデータがいかにしてディスクという『低速な物理デバイス』へと書き出されるか」という、永続化(Persistence)のメカニズムに対する無理解から生じている。
今日は、Redisのメインスレッドを殺し、クライアントのリクエストを停滞させる「ディスクI/O競合」の正体と、それをねじ伏せるための極限の設計論を伝授しよう。
—
1. 「非同期だから安全」という幻想を捨てろ
Redisの永続化には主にRDB(スナップショット)とAOF(ログ追記)の2種類がある。ドキュメントには「バックグラウンドで実行される」と書かれているが、これを「メインスレッドに影響がない」と解釈するのは致命的な誤りだ。
2つの急所:fork() と fsync()
RedisがRDBの保存やAOFのリライトを行う際、`fork()` システムコールを発行して子プロセスを生成する。ここで2つの問題が発生する。
1. Copy-on-Write (CoW) の代償: 子プロセス生成後、親プロセス(メインスレッド)がメモリを書き換えると、OSはページのコピーを作成する。書き込みが激しいシステムでは、メモリ使用量が突如2倍近くまで跳ね上がり、スワップが発生してシステムが沈黙する。
2. ページテーブルのコピー: 数百GBのメモリを積んでいる場合、`fork()` 自体で数秒間メインスレッドが停止(STW: Stop The World)することがある。
そして、今回の本題であるディスクI/Oだ。
—
2. AOF fsync がメインスレッドをブロックする仕組み
もっとも一般的な設定である `appendfsync everysec` を例に取ろう。これは「1秒に1回 fsync を実行する」という妥協案だが、ここには隠れたブロッキング挙動が存在する。
Redisのメインスレッドは、AOFへの書き込みを行う際、前回の `fsync`(バックグラウンドスレッドで実行)がまだ進行中かどうかを確認する。もしディスクI/Oが過負荷で、2秒以上 `fsync` が完了していない場合、Redisはメインスレッドを意図的に停止させる。
> 「データの一貫性を守るために、これ以上書き込みを進めるわけにはいかない」というRedisの悲鳴だ。
実務で見るべきログ:
[Asynchronous AOF fsync is taking too long (disk is busy?).
Writing the AOF buffer without waiting will short-circuit to
prevent blocking the server, but this could lead to data loss.]
このログが出ているなら、君のシステムのI/O設計はすでに破綻している。
—
3. ディスクI/O競合を打破する「鉄血の構成」
この問題を回避し、堅牢なシステムを構築するためのプラクティスを提示する。
① `no-appendfsync-on-rewrite` を `yes` にせよ
BGSAVEやBGREWRITEAOF(リライト)が走っている最中は、大量のディスクI/Oが発生する。この間に `fsync` を重ねると、ディスクのI/Oキューが埋まり、メインスレッドのブロッキングを誘発する。
AOFリライト中にfsyncを実行しない設定
no-appendfsync-on-rewrite yes
これを `yes` にすることで、リライト中の `fsync` を抑制できる。最大30秒ほどのデータロスのリスクはあるが、システムの可用性を取るなら必須の設定だ。
② `aof-use-rdb-preamble` は現代の標準
AOFの肥大化とリカバリ時間の長さは、かつてのRedisの弱点だった。
AOFの先頭をRDB形式にする(ハイブリッド永続化)
aof-use-rdb-preamble yes
これを有効にすると、AOFリライト時のディスク書き込み量が劇的に減り、結果としてI/O負荷を抑え、再起動速度も爆速になる。
③ 永続化を「レプリカのみ」に隔離する
これが私がもっとも推奨するアーキテクチャだ。
- Master: 永続化を完全にオフ(RDBもAOFもなし)。最高のI/Oパフォーマンスを維持。
- Replica: ここでAOFやRDBを全力で回す。
ただし、マスターがダウンした際に空の状態で再起動し、レプリカを同期して全データを消去するという悲劇(空同期)を防ぐため、監視ツール(Sentinel等)による適切なフェイルオーバー制御がセットであることが条件だ。
—
4. OSレイヤーでのチューニング:Linuxの「深淵」へ
Redisの設定だけで満足してはいけない。カーネルの挙動がRedisの足を引っ張る。
vm.dirty_ratio の調整
Linuxカーネルは、ディスクへの書き込みをメモリ(ページキャッシュ)に溜め込み、後でまとめて書き出す。この「まとめて書き出し」が走る瞬間にI/Oスパイクが発生する。
ダーティページがメモリ全体の何%になったら書き出しを始めるか
sysctl -w vm.dirty_background_ratio=3
sysctl -w vm.dirty_ratio=10
これらの値を小さく設定することで、少量のデータを頻繁に書き出させ、巨大なI/Oスパイクを平滑化する。
I/Oスケジューラの選択
クラウド環境やSSD/NVMe環境であれば、`deadline` や `none` を選択すべきだ。古い `cfq` などのスケジューラは、Redisのようなレイテンシに敏感なアプリケーションには不向きだ。
—
5. 結論:アーキテクトとしての決断
Redisの永続化設計において、「銀の弾丸」は存在しない。あるのは「データ保護とパフォーマンスのトレードオフ」だけだ。
1. 極限の速度が必要なら: マスターの永続化を捨て、レプリカに重荷を背負わせろ。
2. 1秒のロスも許されないなら: NVMe SSDを採用し、`appendfsync always` に耐えうる帯域を確保しろ。
3. 現実的な解なら: `everysec` を使いつつ、`no-appendfsync-on-rewrite yes` と OSのカーネルパラメータを最適化しろ。
Redisはシンプルだが、その裏側にあるディスクI/Oとの格闘を理解して初めて、一流のエンジニアと言える。君のコードと設定が、物理レイヤーの制約に負けないことを期待している。
コメント