Redisの急所:`aof-load-truncated` が引き起こすデータ損失のパラドックスと、極限環境におけるストレージ設計の真実
データベースの稼働中、突如として訪れる電源断やOSのカーネルパニック。ストレージのI/Oサブシステムが書き込み途中で息絶えたとき、そこに残されるのは「不完全なログ」だ。
Redisにおいて、Append-Only File(AOF)の末尾が切り詰められた(Truncated)状態で起動を迎えた時、データベースエンジンはどう振る舞うべきか。この問いに対するRedisの答えが、今回深掘りする `aof-load-truncated` という設定である。
一般の入門書や浅いリファレンスであれば、「起動時にAOFファイルの末尾が壊れていても読み込めるようにするフラグです」の一言で片付けられるだろう。しかし、世界最高峰のアーキテクチャを志す我々にとって、それは議論のスタートラインにすら立っていない。
このフラグの背後には、「可用性(Availability)の維持」と「データ完全性(Consistency)の死守」という、分散システムにおける永遠のジレンマが横たわっている。今回は、Redisのソースコードレベルの挙動、OSのファイルシステムとディスクI/Oの物理的制約、そして大規模インフラにおける真の最適解を紐解く。
—
1. 内部メカニズム:AOFパーサーと破損検出の瞬間
まず、RedisがどのようにAOFファイルを読み込んでいるのか、その内部レイヤを覗いてみよう。
Redisのメインループが起動し、AOFによる永続化が有効(`appendonly yes`)である場合、サーバーは `loadAppendOnlyFile()` 関数を呼び出す。AOFはRESP(REdis Serialization Protocol)のコマンド列が順にシリアライズされたテキストファイルに他ならない。
ここで発生する破損は、主に以下の2パターンに分類される。
1. 中間部分の破損: ディスクのセクタ不良やビット腐敗(Bit Rot)によるもの。
2. 末尾の破損: 書き込み途中のクラッシュによる、マルチバイトコマンドやRESP配列の途中での切断。
`aof-load-truncated` が直接制御するのは、「2番目の末尾の破損」に遭遇した瞬間の挙動だ。
ソースコードの挙動と `aof-load-truncated` の分岐
Redisの内部実装(ざっくりとした概念的擬似コード)を見てみよう。
// RedisのAOFローディングロジックの概念モデル
while (read_resp_command(&target_client, aof_file) == C_OK) {
// コマンドの実行とメモリへのロード
if (replication_kernel_feed(target_client) != C_OK) {
// 致命的なエラー処理
}
}
// ループを抜けた=ファイルの終端(EOF)に達した、あるいは…
if (ftell(aof_file) < file_size) {
// ここでパースエラー、あるいは不完全なコマンド検知
if (server.aof_load_truncated) {
serverLog(LL_WARNING, "Exhausted AOF, but truncated. Loading as much as possible.");
// 壊れた末尾を無視して起動を許可
} else {
serverLog(LL_WARNING, "AOF file: file is truncated by a crash. Aborting.");
exit(1); // 起動プロセスを強制停止
}
}
デフォルトでは、`aof-load-truncated` は `yes` に設定されている。つまり、Redisは「途中で切れたコマンドは捨て、その直前までの整合性が取れたデータセットのみをメモリ上に再構築して起動する」道を選ぶ。
—
2. アーキテクトの警鐘:なぜデフォルトの `yes` は危険な罠になり得るのか
「可用性が最優先されるWebアプリケーションにおいて、自動でリカバリして起動してくれるなら `yes` のままで完璧ではないか」——そう考えたエンジニアほど、本番環境の障害時に深手を負うことになる。
ここに、データエンジニアリングにおける深刻なトレードオフがある。
シナリオ:金融系・トランザクションデータの場合
1. クライアントが `HSET account:1001 balance 50000` を送信し、Redisはこれをメモリ上に適用。
2. OSのページキャッシュからディスクへのフラッシュ(fsync)の最中に、サーバーがハードウェア障害でシャットダウン。
3. AOFファイルには `3\r\n$4\r\nHSET\r\n…` の途中のバイト列までしか書き込まれていない。
4. 再起動時、`aof-load-truncated yes` のおかげでRedisは無事起動。しかし、最後の5万円の入金トランザクションは静かに消え去っている。
ここで恐ろしいのは、Redisが「データが失われたこと」を警告ログとしては出すものの、アプリケーション層にはエラーを返さず、何食わぬ顔で古い状態でサービスインしてしまう点である。
データの整合性(Consistency)を極限まで重視するシステムにおいて、無言のデータロスは、誤った残高計算や二重処理といった二次災害を引き起こす時限爆弾となる。
—
3. 運用パラダイムシフト:`aof-load-truncated no` という硬派な選択
では、堅牢性を極限まで高めるために `aof-load-truncated no` に設定した場合、何が起きるのか。
結論から言えば、破損したAOFを検知した瞬間、Redisは起動を拒絶してクラッシュ(Exit)する。
[12345] 01 Jan 2023 00:00:00.000 Reading RDB preamble from AOF file…
[12345] 01 Jan 2023 00:00:00.001 Reading the remaining AOF file…
[12345] 01 Jan 2023 00:00:00.015 # Bad file format reading the append-only file: make a backup of your AOF file, then use ./redis-check-aof –fix
[12345] 01 Jan 2023 00:00:00.015 # Aborting now.
エンジニアはこのログを目撃した時、パニックを起こしてはならない。これはRedisからの「私は壊れたデータで勝手に動き出すことを拒否しました。人間の手で正しく判断してください」というプロフェッショナルなシグナルなのだ。
障害復旧の正しい手順(SREの作法)
`aof-load-truncated no` 環境下でクラッシュが発生した場合の、厳格なオペレーション手順を記す。
1. 現行AOFの退避(最重要)
cp appendonly.aof appendonly.aof.bak.$(date +%s)
焦って上書き保存をしてはならない。すべての証拠保全が先決だ。
2. `redis-check-aof` による診断と修復
Redis公式が提供する静的解析・修復ツールを用いる。
redis-check-aof –fix appendonly.aof
このツールは、ファイルの末尾をスキャンし、不完全なRESPコマンドの境界までファイルを切り捨てる(Truncate)。
3. 差分の精査と人間による監査
修復ツールがどこを削ったのか、元のバックアップとの差分(`diff`)を確認する余裕があるなら、プロフェッショナルとして望ましい。
4. サービスの再起動
—
4. 低レイヤ最適化:ファイルシステムと `fsync` ポリシーの相関
`aof-load-truncated` の挙動を語る上で欠かせないのが、底辺を支えるストレージI/Oと `appendfsync` 設定のインタラクションだ。
AOFの末尾が破損する原因のほとんどは、「OSがメモリ上に持っていたダーティページを、ストレージの不揮発領域にフラッシュしきる前に電源が落ちたこと」にある。
- `appendfsync always`:
書き込みごとに `fsync` を発行する。パフォーマンスは極限まで低下するが、末尾の破損リスクは最小限(直近の1回を除く)に抑えられる。このモードであれば、`aof-load-truncated` が火を吹くシチュエーション自体が稀少になる。
- `appendfsync everysec`:
バックグラウンドスレッドが毎秒 `fsync` を実行する(デフォルト)。最も推奨される設定だが、この「1秒の窓」の間にクラッシュが起きた場合、数メガバイト分のデータが宙に浮き、結果としてファイル末尾の切り詰めが発生する。
- `appendfsync no`:
OSのカーネルがよしなにフラッシュするのを待つ。I/O性能は最高だが、クラッシュ時の破損リスクとデータロスは最大化する。
アーキテクトとしてシステムを設計する際、「ストレージのI/O性能、`appendfsync` のポリシー、そして `aof-load-truncated` のフラグ」は、必ず三位一体で設計されなければならない。
高速なNVMe SSDを採用し、バッテリーバックアップ付き書込キャッシュ(BBWC / FBWC)を備えたコントローラーを使用している環境であっても、OSカーネルとファイルシステム(ext4 / xfs)のバリアオプション(`barrier=1` など)が正しく構成されていなければ、意図せぬ順序でブロックがディスクに書き込まれ、AOFの構造破壊を招く。
—
5. チーフアーキテクトからの最終提言
実務において、`aof-load-truncated` をどう設定すべきか。私の結論は明確だ。
1. キャッシュレイヤとしての利用(データが消えても上流から再構築可能な場合)
- デフォルトの `yes` で問題ない。可用性を優先し、クラッシュ後も即座にサービスを復旧させるべきだ。
2. 一次データストア / セッションストア / 金融・重要トランザクションの場合
- `no` に設定し、ファイル破損時には即座にプロセスを停止させるべきである。
- 同時に、監視システム(Prometheus + Alertmanager等)でRedisのプロセスダウンを検知し、自動再起動ループ(Infinite Restart Loop)に陥らないよう、`restart=always` などの安易な設定を避け、エンジニアによる手動介入または厳密な監査フローを組み込むこと。
「動くこと」と「正しいこと」は、大規模分散システムの設計においてしばしば矛盾する。Redisの小さな一設定に過ぎない `aof-load-truncated` は、まさにそのエンジニアリングの哲学を我々に問いかけているのだ。
コメント