【極限のRedis永続化】`appendonly yes` が引き起こす深淵:ディスクI/Oの真実とプロダクション設計
「Redisはインメモリデータベースだから高速だ」――この認識は半分正しく、半分は極めて危険な誤解を孕んでいる。
データに「失われない信頼性」を求めた瞬間、Redisはメモリというユートピアから、ディスクI/Oという過酷な現実世界へと引きずり下ろされる。その境界線を決定づける設定こそが、`appendonly yes`(AOF: Append-Only File)だ。
プロジェクトのテクニカルリードとして、私は数々の「Redisが詰まった」「スループットが10分の1に落ちた」という悲鳴を耳にしてきた。その大半は、AOFの挙動とOSのカーネルレベルの挙動を理解せず、単に「耐久性が必要だから」と設定を有効にしたことに起因する。
本稿では、AOF永続化の真のアーキテクチャ、カーネルレベルでの挙動、本番環境で必ず直面する「AOFブロッキング」のメカニズム、そしてそれらをねじ伏せる実践的な設計パターンを徹底的に解説する。
—
1. AOF(Append-Only File)の正体と、RDBとの本質的違い
Redisの永続化には、大きく分けてRDB(スナップショット)とAOF(ログ先行書き込み)の2つが存在する。この違いを単なる「静止画」と「動画」と捉えるだけでは、設計レビューを通すことはできない。
| 特性 | RDB (Snapshotting) | AOF (Append-Only File) |
| :— | :— | :— |
| 動作原理 | 特定時点の全メモリデータをバイナリ(`.rdb`)として一括出力 | 実行された書き込みコマンドをRESPプロトコル形式で追記(`.aof`) |
| データ損失リスク | 最後のスナップショット以降のデータ(通常数分〜数時間分) | 最悪でも直近1〜2秒程度(設定依存) |
| ディスクI/O特性 | 一時的な大量書き込み(Spiky I/O) | 継続的な小〜中規模書き込み(Continuous I/O) |
| 復旧速度 | メモリに直接ロードするため極めて高速 | コマンドを再実行(再生)するため低速 |
多くのシステムにおいて、「1秒たりともデータを失いたくない、しかし再起動時の復旧速度も担保したい」という要求が突きつけられる。このトレードオフを支配するのが、`appendonly`設定とそのサブパラメータ群である。
—
2. カーネルレベルで見る `appendfsync` の真実
`appendonly yes` を有効にしただけでは、あなたのデータはまだ安全ではない。
Redisがクライアントから書き込みコマンドを受信し、それをディスクに書き込むプロセスは、OSのファイルシステムレイヤーを介した以下の3ステップで実行される。
[Client]
│ (Write Command)
▼
[Redis Process] ──(1) write(2)システムコール ──> [OS Page Cache]
│ │
│ (2) fsync(2)システムコール
│ │
▼ ▼
[Active Memory] [Physical Disk] (SSD/NVMe)
1. `write(2)` システムコール: RedisはデータをOSのページキャッシュに書き込む。この時点でRedis側は「書き込み完了」と認識するが、物理ディスクにはまだ書き込まれていない(OSがクラッシュすれば消える)。
2. `fsync(2)` システムコール: OSに対し、ページキャッシュのデータを物理ディスク(SSD等)に強制同期させる。
この `fsync` をどのタイミングで実行するかを制御するのが `appendfsync` 設定である。ここにエンジニアの「覚悟」が試される。
`appendfsync always`(絶対安全の罠)
毎回の書き込みイベントループごとに `fsync(2)` を実行する。
- メリット: データ損失はほぼゼロ。
- デメリット: パフォーマンスの破滅。Redisのシングルスレッド特性により、ディスクの書き込み速度(I/Oレイテンシ)がそのままRedisのスループットの限界(秒間数百〜数千Ops)になる。SSD寿命も著しく縮めるため、原則として本番環境での採用は避けるべきだ。
`appendfsync no`(OS任せの怠慢)
`fsync` をRedis側から明示的に呼ばず、OSのフラッシュ(Linuxの場合、通常30秒間隔)に委ねる。
- メリット: パフォーマンスはメモリ速度に肉薄する。
- デメリット: 最大30秒分のデータが失われる可能性があり、永続化ストアとしての体をなさない。
`appendfsync everysec`(妥協なき現実解)
バックグラウンドスレッド(`bio`スレッド)が、毎秒1回 `fsync(2)` を実行する。
- メリット: パフォーマンスを維持しつつ、クラッシュ時のデータ損失を最大2秒(※後述のブロッキングにより最大2秒となる)に抑える。
- 結論: プロダクション環境における唯一の現実解。
—
3. AOF Rewrite(再構築)と「AOFブロッキング」の恐怖
AOFはすべての書き込みコマンドを追記するため、運用を続けるとファイルサイズが無限に肥大化する。例えば、同じキーに対して `INCR` を100万回実行した場合、AOFには100万行の履歴が残るが、最終状態は1つの数値に過ぎない。
これを解決するのが AOF Rewrite(AOF再構築:`BGREWRITEAOF`) だ。現在のメモリ状態を元に、最小限のコマンドセットでAOFファイルを再生成する。
しかし、このクリーンな仕組みの裏には、本番環境を凍りつかせる「AOF fsync latency spike(AOFブロッキング)」という罠が潜んでいる。
AOFブロッキングのメカニズム
`appendfsync everysec` を選択している場合、Redisのメインスレッドは以下のように動作する。
1. メインスレッドは、前回の `fsync` がバックグラウンドスレッドでまだ実行中かどうかをチェックする。
2. もし実行中の場合、Redisは `write(2)` の呼び出しを最大2秒間保留(ディレイ)する。これは、ディスクI/Oが飽和している状態でさらに書き込むと、カーネルレベルでスレッドがブロックされるのを避けるためだ。
3. しかし、前回の `fsync` 開始から2秒以上経過してもまだ処理が完了していない場合、Redisはメインスレッドを停止させてでも `write(2)` を強行する。
結果、Redisのメインスレッドが完全にブロックされ、数秒間にわたり応答を停止(Latency Spike)する。
[Main Thread] ──(write)──> [OS Cache] ──(2秒以上fsyncが滞留)──> [ BLOCK! ] ──(クライアントへの応答停止)
[Helper Thread] ───────────────────────────(重いfsync(2)の実行中)───────────────────────────
特に、AOF Rewrite中に子プロセス(`redis-aof-rewrite`)がディスクに大量の書き込みを行っている最中、この現象は極めて発生しやすい。
—
4. プロダクションレディなAOF設定テンプレート
この過酷なディスクI/Oの特性を踏まえ、本番環境で耐えうる堅牢な `redis.conf` の設定例を提示する。単に `appendonly yes` にするだけでは、プロの仕事とは言えない。
==============================================================================
Redis Production AOF Configuration Template
==============================================================================
AOF永続化の有効化(必須)
appendonly yes
AOFファイル名および格納ディレクトリの設定
appendfilename “appendonly.aof”
appenddirname “appendonlydir”
同期ポリシー:実務における最適解
appendfsync everysec
——————————————————————————
AOF Rewrite 時のブロッキング緩和策
——————————————————————————
BGSAVE(RDB作成)や BGREWRITEAOF の実行中、メインスレッドでの fsync(2) の呼び出しを抑制するか。
yes: ディスクI/O競合によるメインスレッドのブロッキングを防ぐ(パフォーマンス優先)。
ただし、Rewrite中にシステムがクラッシュした場合、最大30秒分のデータが失われるリスクを許容する。
no : 安全性を最優先。Rewrite中も fsync を継続するため、ディスク負荷時にレイテンシスパイクが発生する。
設計判断:高トラフィック環境でミリ秒単位の応答速度を死守する場合、ここを ‘yes’ にする。
no-appendfsync-on-rewrite yes
——————————————————————————
AOF自動書き換え(Auto Rewrite)のトリガー設定
——————————————————————————
前回のRewrite完了時のAOFサイズと比較して、何%増加したら再構築を行うか。
あまりに頻繁なRewriteはI/O負荷を高めるため、100%(2倍)以上に設定するのが標準。
auto-aof-rewrite-percentage 100
自動Rewriteを実行する最小ファイルサイズ。
ファイルが小さいうちはRewriteする意味がないため、最低でも 64mb〜1gb 程度に設定する。
メモリ容量とディスク帯域に余裕があるなら、256mb 程度からスタートするのが実務的に健全。
auto-aof-rewrite-min-size 256mb
——————————————————————————
異常終了時の自己修復設定
——————————————————————————
クラッシュ等でAOFファイルの末尾が破損(Truncated)していた場合、
起動時にエラーにせず、可能な限りロードして警告ログを出力して起動するか。
yes: 起動を優先。最後の不完全なコマンドは破棄される。
no : 安全を優先。起動を停止し、管理者に ‘redis-check-aof’ での修復を促す。
aof-load-truncated yes
AOFとRDBのハイブリッド永続化の有効化(Redis 5.0以上)
AOFの先頭をRDBバイナリにし、それ以降の差分をRESPテキストで追記する。
これにより、再起動時のロード時間を劇的に短縮(RDB並み)しつつ、AOFのリアルタイム性を維持できる。
【超推奨設定】
aof-use-rdb-preamble yes
—
5. テクニカルリードとしての設計チェックリスト
もしあなたが設計レビューで「Redisの永続化にAOFを採用します」という提案を受けたなら、以下の3点を鋭く問い詰めてほしい。
1. 「ホストのディスクスペック(IOPS)を把握しているか?」
EBS(AWS)の `gp2` などのバーストバケットタイプを使っている場合、I/Oクレジットが枯渇した瞬間に `fsync` が詰まり、Redis全体が停止する。本番環境、特にAOFを有効にする場合は `gp3`(十分なIOPS/スループットを確保)または `io2`、オンプレミスならエンタープライズ向けNVMe SSDが必須である。
2. 「`no-appendfsync-on-rewrite yes` のデータロストリスクをビジネスサイドと合意したか?」
これを `yes` に設定した場合、自動書き換え中のクラッシュで最大30秒分の書き込みデータが消える可能性がある。これが許容できない金融系などのデータであれば、`no` に設定した上で、ディスクI/Oに圧倒的なプロビジョニングを施す必要がある。
3. 「ハイブリッド永続化(`aof-use-rdb-preamble yes`)を有効にしているか?」
これを無効にしたまま数GBクラスのAOFを運用すると、障害復旧時のRedis再起動に数十分を要し、サービス復旧のSLAを著しく毀損する。
まとめ
`appendonly yes` は、Redisに「信頼性」という翼を授ける強力な設定だ。しかし、それはOSのファイルキャッシュ、ディスクの物理的限界、そしてスレッドブロッキングとの戦いの始まりを意味する。
インフラ、OSカーネル、そしてRedisの内部アーキテクチャ。これらすべてを立体的に捉え、最適なパラメータを引き出してこそ、真の超高速・高信頼なシステムを構築することができる。あなたの設計が、堅牢なシステムの一助となることを願う。
コメント