【Redis設計レビュー】`appendfsync`の選択を誤るな:データロストとレイテンシの極限トレードオフを制する
テックリードの私だ。今日のコードレビュー、あるいはアーキテクチャ設計レビューで、また「Redisの永続化?とりあえずデフォルトのままで」という甘い考えを見かけた。
Redisは単なる「速いキャッシュ」ではない。現代のマイクロサービスアーキテクチャにおいて、低レイテンシなインメモリデータベース、さらにはセッションストアやリアルタイムのストリーム基盤として、その信頼性はシステム全体の生死を握っている。
特に `appendfsync` の設定は、「どれだけのデータ消失(RPO)を許容できるか」と「どれだけのスループットとレイテンシを担保するか」という、エンジニアリングの永遠のジレンマが凝縮された核心部分だ。
今回は、AOF(Append Only File)の心臓部である `appendfsync` の3つのモード(`always` / `everysec` / `no`)を丸裸にし、本番環境で踏み抜く地雷と、プロとして取るべき最適解を伝授する。
—
1. そもそも `appendfsync` とは何をしているのか?
AOFは、Redisに対するすべての書き込みコマンドをログとして追記していく方式だ。しかし、Redisが `write(2)` システムコールでデータをディスク(厳密にはOSのカーネルバッファ)に書き込んだだけでは、まだ安全ではない。電源が落ちればカーネルバッファのデータは消える。
OSからストレージデバイス(SSD/HDD)の物理メディアまでデータをフラッシュさせるためには、`fsync(2)`(または `fdatasync(2)`)を呼び出す必要がある。
この `fsync` をどのタイミングで実行するかを制御するのが `appendfsync` である。
[Redisプロセス]
│
│ 1. write(2) (メモリ上のカーネルバッファへ)
▼
[OS カーネルバッファ]
│
│ 2. fsync(2) (★ここを制御するのが appendfsync)
▼
[物理ディスク (SSD/HDD)]
この `fsync` は極めて重い同期I/Oブロック操作だ。これをどうハンドリングするかで、システムの運命が決まる。
—
2. 3つのモードの解剖と実務的な評価
`redis.conf` で設定するこの3つの値について、現場の視点で容赦なく分解していこう。
① `appendfsync always` —— 「データロストは1バイトも許さない」という幻想
すべてのコマンド書き込みごとに `fsync` を実行するモード。
- RPO(目標復旧時点): ほぼ 0(コマンド実行完了=ディスク書き込み完了)
- パフォーマンス: 最悪
- 実務的評価:
近代的なNVMe SSDを使ってい حتى、`fsync` の都度ディスクの同期を待つため、Redisのシングルスレッドループは完全にブロックされる。スループットは劇的に低下し(数百〜数千QPS程度に落ち込む)、レイテンシは跳ね上がる。「金融系の絶対にデータを失えない台帳だ」と言いたくなる気持ちはわかるが、現代の分散システムにおいては、Redis単体に `always` を強いる設計はアンチパターンであることが多い。冗長化(レプリケーション)と組み合わせるべきだ。
② `everysec` —— 現実解としてのゴールドスタンダード
1秒に1回、バックグラウンドスレッドが `fsync` を実行するモード。
- RPO: 最大1秒(直近1秒間の書き込みが消える可能性)
- パフォーマンス: 高水準で安定
- 実務的評価:
Redisのデフォルト設定であり、95%のユースケースにおける正解。
`write` は通常通り行い、`fsync` は別スレッド(I/Oスレッド)に逃がす。もし前回の `fsync` がまだ終わっていない場合、Redisは最大2秒間 `write` 自体をブロックすることがあるが、実用上、スループットと耐久性のバランスが最も優れている。
「たかが1秒のデータロスト」を許容できないビジネスロジック側をどうにかするか(冪等性の担保やRDBとの併用)、あるいはアプリケーション層で担保するのがプロの仕事だ。
③ `no` —— OS任せのギャンブル、あるいは極限のパフォーマンス追求
Redis側からは `fsync` を一切呼ばず、OSが自前のタイミング(通常30秒おきなど)でフラッシュするのに任せるモード。
- RPO: 不確定(OSのバッファフラッシュ間隔に依存。数秒〜数十秒)
- パフォーマンス: 最大
- 実務的評価:
「データが飛んでも、キャッシュだから再構築すればいい」という割り切りができる、あるいはインフラ側で完全なUPS(無停電電源装置)や特殊なハードウェア保証がある場合を除き、本番の永続データストアとしては使ってはならない。
OSがフラッシュするタイミングでI/Oスパイクが発生し、突発的なレイテンシ悪化(ラテンスパイク)を引き起こす原因にもなる。
—
3. レビューで使える!堅牢な設計パターン
では、実際のシステム設計ではどのように `appendfsync` と向き合うべきか。私のチームでレビューを行う際、以下の基準をパスしなければマージを許可しない。
パターンA:標準Webアプリケーション・セッションストア(推奨: `everysec`)
- 構成: Redis Master-Replica(各ノードでAOF有効)
- 設定: `appendfsync everysec`
- 理由: 万が一のマスター障害時、SentinelやRedis Clusterによるフェイルオーバーが発生する。その際、最大1秒分のデータロスが起きる可能性があるが、アプリケーション側で「セッション再ログインを強いる」「DBから再フェッチする」などのフォールバックを実装していれば実害はない。
パターンB:極限のスループットが必要なカウンター・ランキング(推奨: AOF無効 or `no` + RDBバックアップ)
- 構成: 揮発性キャッシュとして割り切る
- 設定: `appendfsync no`(または `save` ディレクティブによるRDBスナップショットのみ)
- 理由: 厳密な永続化が不要で、秒間10万QPSを超える書き込みを捌く必要がある場合、AOFのオーバーヘッド自体がボトルネックになる。データを失うリスクをインフラのアーキテクチャ(マルチAZ、リードレプリカ)ではなく、ビジネス特性(消えても困らないデータか)で割り切る。
—
4. パフォーマンス上の致命的な罠:「fsync遅延」の恐怖
`everysec` や `always` を使っていると、ログに突如として以下のような警告が出現することがある。
Asynchronous AOF fsync is taking longer than usual (background fsync was slow). Saving the database is safe, but can’t reply to write commands.
これは何を意味するか?
バックグラウンドの `fsync` 処理が遅延している(多くの場合、裏で別のバッチ処理やログ出力、OSのガベージコレクション等でストレージのI/O帯域が枯渇している)ために、メインスレッドが次の `write` を安全に行うためにブロックされている状態だ。
対策:
1. ストレージの選定:
AWS等のクラウド環境であれば、GP2からIOPSプロビジョンドな gp3 や io2 へ移行し、ベースラインのIOPSとスループット(Throughput)を明示的に確保する。安価なHDDやバースト型のストレージで `everysec` を動かすのは自殺行為だ。
2. `no-appendfsync-on-rewrite` の活用:
no-appendfsync-on-rewrite yes
この設定を `yes` にすると、AOFの書き換え(BGREWRITEAOF)やRDBの保存(BGSAVE)が走っている最中は、実質的に `fsync` を一時停止(`no` 相当)する。ディスクI/Oのコンテンション(競合)によるレイテンシスパイクを防ぐための実務的な延命措置として有効だが、「裏で重い処理をしている最中に落ちたら、データロス時間が伸びる」というトレードオフを理解して有効化すること。
—
結びにかえて
Redisの `appendfsync` は、単なるコンフィグの選択肢ではない。それは、「ビジネスが許容するリスク」と「インフラストラクチャの物理的限界」を繋ぐ契約書だ。
「デフォルトだから」「ネットの記事にこう書いてあったから」という理由で選ぶのではなく、システムの要件(RPO)、想定されるQPS、そしてストレージのI/O性能をロジカルに突き詰めた上で、その設定値をコード(IaC)に刻み込んでほしい。
君たちの書くコードとアーキテクチャが、深夜の障害アラートで鳴り響かない堅牢なものであることを期待している。レビューは以上だ。
コメント