WAL(先行書き込みログ)の仕組み
コミットしたデータが障害でも消えないのはなぜか。データ本体より先にログを書く WAL の原理を、クラッシュリカバリ・チェックポイント・fsync・グループコミットまで一気に解き明かします。
- 1.WAL は「データページを書き換える前に、その変更内容をログとして先に永続化する」規律。これによりコミットの永続性とクラッシュ後の復旧を両立する。
- 2.コミット時はランダムなデータページではなくシーケンシャルなログだけを fsync すればよく、チェックポイントでログの再生範囲を区切り、グループコミットで複数コミットの fsync をまとめて高速化する。
- 3.WAL は ARIES の中核要素であり、redo/undo・LSN・チェックポイントという発想がそのまま実用 DBMS のリカバリ機構につながる。
なぜ「ログを先に書く」のか
トランザクションの永続性(Durability)は「コミットしたら障害でも消えない」を意味します。素直に実現するなら、コミットのたびに変更したデータページをすべてディスクへ書き戻せばよさそうです。しかしこれは2つの理由で破綻します。
- ランダム I/O が遅い: 更新したページはディスク上で散らばっており、毎コミットでそれらを
fsync(OS のページキャッシュを実デバイスまで確実に書き込む同期)するのは、ランダム書き込みの嵐になります。 - 部分書き込みで壊れる: ページの書き戻し途中で電源が落ちると、ページが半分だけ更新された torn page(破れたページ)になり、リカバリの手がかりすら失われます。
そこで採るのが WAL(Write-Ahead Logging、先行書き込みログ) です。規律はただ一つ。
データページを永続ストレージへ書き戻すより前に、そのページに対する変更を記述したログレコードを永続化しておく。
ログは追記専用なのでシーケンシャル書き込みになり、ランダムなページ書き戻しよりはるかに速く fsync できます。コミット時に永続化すべきは「散らばったデータページ」ではなく「連続したログの末尾まで」だけになる、これが WAL の本質的な利得です。
厳密には2つの規則があります。(1) Write-Ahead 規則: あるデータページを書き戻す前に、それを変更した全 redo/undo ログを先に永続化する。(2) Commit 規則(Force-Log-at-Commit): トランザクションをコミット確定とする前に、そのコミットログレコードまでを永続化する。前者が「未コミット変更を巻き戻せること(undo の前提)」を、後者が「コミット済み変更を必ず再現できること(redo の前提)」を保証します。
LSN とログレコード
各ログレコードには単調増加する LSN(Log Sequence Number、ログ列番号) が振られます。LSN は実質的に「ログ上の書き込み位置(オフセット)」であり、順序と位置を同時に表します。
各データページのヘッダには、そのページに最後に適用された変更の LSN(pageLSN)が記録されます。この一点が Write-Ahead 規則を機械的に実行可能にします。バッファ管理が汚れたページを書き戻す直前に、flushedLSN >= page.pageLSN を確認すればよいのです。満たさないなら、先にログをそこまで fsync してからページを書く。こうして「データより先にログ」が常に成立します。
ログレコードは典型的に redo 情報(やり直しに必要な新しい値や操作)と undo 情報(取り消しに必要な旧い値や逆操作)を持ちます。redo はコミット済み変更の再現に、undo は未コミット変更の巻き戻しに使われます。
クラッシュリカバリはどう保証されるか
クラッシュ後、ディスク上のデータページは「いつの時点まで反映されているか」がページごとにバラバラです。あるページはコミット前の更新まで書かれ、別のページはコミット済み更新がまだ書かれていない、という状態が普通に起こります。WAL を使ったリカバリは、これをログから決定的に修復します。
| フェーズ | やること | 目的 |
|---|---|---|
| 解析(Analysis) | 最後のチェックポイントからログを読み、クラッシュ時に実行中だったトランザクションと、書き戻し未確定のページを特定する | redo の開始点と undo 対象を決める |
| 再実行(Redo) | 必要範囲のログを LSN 順に再適用し、ディスクをクラッシュ直前の論理状態まで進める | コミット済み変更を確実に反映(永続性) |
| 巻き戻し(Undo) | クラッシュ時に未コミットだったトランザクションの変更を、undo 情報で逆順に取り消す | 原子性(中途半端を残さない) |
ここで効くのが 冪等性です。redo はページの pageLSN を見て「そのログの LSN 以下なら適用済み」と判断し、二重適用を避けます。だからリカバリ自体が途中でまた落ちても、最初からやり直して同じ正しい結果に収束します。コミット済みは必ず再現され(redo)、未コミットは必ず消える(undo)。これが WAL によるクラッシュリカバリ保証の核心です。
fsync は「OS のページキャッシュから実デバイスへ確実に書く」ためのものですが、デバイスやコントローラが内部に揮発キャッシュを持ち、fsync 完了を返しても実際は未着地、というケースがあります。これが起きると WAL の永続性保証は崩れます。ストレージのライトキャッシュは無効化するか、停電時保護(バッテリ/キャパシタ)付きであることを確認してください。
チェックポイント――ログ再生を無限に伸ばさない
WAL だけだと、リカバリ時に「DB 開始時点から全ログを redo」する羽目になり、ログは無限に伸び、復旧は無限に遅くなります。これを区切るのが チェックポイント です。
チェックポイントは、その時点でバッファ上の汚れたページを実際にディスクへ書き戻し、「ここまではデータページに反映済み」という基準点をログに刻みます。リカバリは原則として最後のチェックポイント以降のログだけを見ればよくなり、再生時間が有界になります。同時に、それより古い WAL は(アーカイブ等の用途がなければ)回収・再利用できます。
頻繁にチェックポイントすれば復旧は速い反面、汚れたページの書き戻しでランダム I/O が集中し、平常時のスループットが波打ちます。逆に間隔を空けると平常時は軽いがリカバリが長引きます。実装の多くは書き戻しを一定時間に分散(spread checkpoint)して I/O のピークをならします。fuzzy checkpoint 方式なら、全ページの書き戻し完了を待たずチェックポイントを進められます。
fsync とグループコミットの原理
コミットの待ち時間を最終的に決めるのは、コミットログを fsync し終えるまでの時間です。fsync はストレージへの物理的な同期であり、1 回あたり数百マイクロ秒〜ミリ秒級。コミットごとに律儀に 1 回ずつ呼ぶと、fsync のレイテンシがそのままスループットの上限になります。
これを破るのが グループコミットです。短い時間窓の間に到着した複数トランザクションのコミットログをログバッファ上でまとめ、1 回の fsync で一括して永続化します。N 件をまとめれば fsync 回数が N 分の 1 になり、高並行下のスループットが劇的に上がります。各トランザクションは「自分のコミット LSN まで flushedLSN が進んだ」のを待ってから完了応答するため、まとめても永続性は損なわれません。
個別コミット: T1 → fsync, T2 → fsync, T3 → fsync (fsync 3 回)
グループコミット: T1,T2,T3 のコミットログを連結 → fsync 1 回で全員確定
「コミットが速いのにデータページはまだディスクに無い、なぜ壊れないか」――答えは WAL。コミット時に永続化されるのはシーケンシャルなログだけで、データページの書き戻しは後回し(チェックポイントやバックグラウンド書き出し)にできる。永続性はログが、原子性は undo が、復旧の有界性はチェックポイントが担う、と切り分けて答えられると強いです。
ARIES への橋渡し
ここまでの「LSN・pageLSN・redo/undo・チェックポイント・解析/再実行/巻き戻しの3フェーズ」は、そのまま ARIES(Algorithm for Recovery and Isolation Exploiting Semantics)というリカバリ手法の骨格です。ARIES はこれに2つの強力な発想を加えます。
- Repeating History(履歴の再現): redo フェーズで、未コミットだったトランザクションの操作も含めてクラッシュ直前の状態を一旦すべて再現し、その後で undo する。これにより、リカバリ中にまた落ちても状態が一貫し、復旧の再開が単純になります。
- CLR(Compensation Log Record、補償ログ): undo で取り消した操作も「取り消したという事実」をログに残します。これにより、undo の最中にクラッシュしても同じ undo を二度実行しない。リカバリ全体が完全に冪等になります。
ARIES は「No-Force / Steal」を前提にします。No-Force はコミット時にデータページの書き戻しを強制しない(だから速い、redo が要る)、Steal は未コミットの汚れたページもバッファ都合で書き戻してよい(だから undo が要る)。WAL はこの自由を安全に成立させる土台そのものです。
WAL があってもログ自体が載るストレージが壊れれば元も子もありません。コミット済みを取り戻すには、健全な WAL が不可欠です。レプリケーションでの WAL 転送(レプリケーションとシャーディング)、WAL のアーカイブによる任意時点復元(バックアップとリカバリの PITR)まで含めて、初めて運用上の永続性が完成します。
まとめ
WAL の発想は「データより先にログを書く」という一行に集約されますが、そこからコミットの高速化(シーケンシャル書き込み+グループコミット)、復旧の保証(redo/undo の冪等な3フェーズ)、再生範囲の有界化(チェックポイント)が一貫した原理で導けます。並行制御の側面(ロックと MVCC や分離レベル)と組み合わせて初めて ACID 全体が成立する、その「D」と「A」を物理層で支えているのが WAL です。
データベース Article
WAL(先行書き込みログ)の仕組みを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
WAL
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
コミット時はランダムなデータページではなくシーケンシャルなログだけを fsync すればよく、チェックポイントでログの再生範囲を区切り、グループコミットで複数コミットの fsync をまとめて高速化する。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「WAL / クラッシュリカバリ」に近いか確認する。
- 強みである「WAL は「データページを書き換える前に、その変更内容をログとして先に永続化する」規律。これによりコミットの永続性とクラッシュ後の復旧を両立する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。