データベース●●●●○

WAL

Write-Ahead Loggingウォル

変更をまずログへ追記してからデータ本体に反映することで、障害後も確実に復旧できる仕組み。

概要

WAL(Write-Ahead Logging=ログ先行書き込み)は、データベースが変更を確定させるとき、データ本体のファイルを書き換える前に「何をどう変更するか」の記録を専用のログファイルへ追記する仕組みです。名前の通り「本体より先に(ahead)ログを書く」ことが規律のすべてで、この順序さえ守れば、書き込みの途中で電源が落ちてもログを読み返して変更をやり直せる — つまりトランザクションの耐久性(ACID の D)をクラッシュに対して保証できます。

WAL は PostgreSQL や MySQL/InnoDB(REDO ログと呼ばれます)、SQLite の WAL モードなど、ほぼすべての本格的なデータベースの心臓部にあります。さらに同じ発想はファイルシステムのジャーナリング(ext4 や NTFS)、Kafka のようなログ基盤にも現れており、「まず追記、反映は後」という設計は永続化技術の共通言語になっています。

なぜ生まれたか

データベースの変更は、最終的にはディスク上のデータファイルのあちこちに散らばったページの書き換えです。1つのトランザクションが複数ページを書き換える途中でクラッシュすると、一部だけ反映された中途半端な状態が残り、整合性が壊れます。かといって「コミットのたびに関係する全ページを確実にディスクへ書き切る」方式は、ランダムな位置への書き込みが大量に発生するため絶望的に遅く、ページ単位の書き込み自体が途中で切れる(torn write)問題も残ります。

WAL はこのジレンマを「書き込みの性質の変換」で解決しました。コミット時にディスクへ確実に書くのはログへの追記だけにする。追記はディスクが最も得意とするシーケンシャル書き込みであり、1回の同期で複数トランザクションをまとめて確定することもできます。データ本体への反映は急がず、メモリ上で書き換えた後、都合のよいタイミングでまとめて行えばよい。「遅いランダム書き込みを、速い追記+後回しの反映に変える」 — これが 1980年代の ARIES などで確立され、現在まで使われ続けている WAL の核心です。

詳細

書き込みの流れ — ログが常に先行する

コミット処理の実際の流れを追ってみます。(1)トランザクションがデータを更新すると、変更はまずメモリ上のバッファプール(データページのキャッシュ)に適用されます。(2)同時に「ページXのこの位置をAからBに変更」というログレコードが WAL バッファに積まれます。(3)コミット時、WAL バッファの内容がログファイルへ追記され、fsync(OS のキャッシュを飛ばしてディスクへの物理書き込みを強制するシステムコール)で永続化が確認されます。この時点でデータベースはクライアントに「コミット成功」と応答できます。(4)メモリ上の変更済みページ(ダーティページ)は、後からバックグラウンドで少しずつデータファイルへ書き戻されます。

トランザクションUPDATE / INSERT …メモリバッファプールダーティページを保持WALバッファ変更のログレコードディスクデータファイルランダム書き込み・後回し可WALファイル追記のみ・シーケンシャルコミット時に fsync(ここだけ同期)チェックポイントでまとめて書き戻し
WALの構造 — コミット時に同期するのはログへの追記だけで、データ本体への反映は後回しにできる

図の要点は、実線(同期・コミットの必須経路)がログ側にしかないことです。クラッシュした瞬間にデータファイルがどれだけ古くても、ログさえ無事なら変更を再現できるため、遅いランダム書き込みをコミットの経路から追い出せます。

クラッシュリカバリ — ログを読み返してやり直す

クラッシュ後の再起動時、データベースはまず WAL を読み、データファイルに未反映の変更を先頭から適用し直します(REDO)。コミット済みのトランザクションの変更はすべてログにあるので、これで失われません。逆に、コミットに至らなかったトランザクションの変更が一部ページに書き戻されてしまっていた場合は、それを取り消します(UNDO。PostgreSQL は MVCC の古いバージョンを利用するため明示的な UNDO ログを持たない、など実装差があります)。「ログを最初から再生すれば状態を復元できる」という性質は、イベントソーシングの発想とも深くつながっています。

fsync とチェックポイント — 性能を左右する2つのつまみ

WAL の性能と信頼性は、2つの運用パラメータでほぼ決まります。1つ目は fsync の頻度です。コミットごとに毎回 fsync すれば最も安全ですが、fsync はディスクの物理書き込みを待つ高価な操作です。そこで複数のコミットを1回の fsync に相乗りさせるグループコミットや、「最大で直近数百ミリ秒分のコミットを失う可能性を受け入れて fsync を遅延する」設定(PostgreSQL の synchronous_commit = off など)が用意されています。耐久性を何ミリ秒分まで賭けてよいかという、明示的なトレードオフの選択です。

2つ目はチェックポイントです。ログは追記され続けるので、放っておくと無限に伸び、リカバリ時の再生時間も伸びます。そこで定期的に「この時点までのダーティページはすべてデータファイルへ書き戻した」という区切りを記録するのがチェックポイントで、これより古いログは削除(または再利用)でき、リカバリはチェックポイント以降の再生だけで済みます。ただしチェックポイント実行中は書き戻しの I/O が集中するため、間隔を短くすればリカバリは速いが平常時の負荷が増え、長くすればその逆になります。「チェックポイントのたびに性能が波打つ」のは WAL を使うデータベースの典型的な運用課題です。

レプリケーションへの応用 — ログは複製の言語になる

WAL には「データベースへのすべての変更が、順序付きで、1本のストリームとして記録されている」という副産物があります。これはそのままレプリケーションの輸送手段になります。プライマリが WAL をレプリカへ流し、レプリカがそれを再生し続ければ、同じ状態の複製が維持できます。PostgreSQL のストリーミングレプリケーションや MySQL のレプリケーション(binlog)はまさにこの仕組みです。さらに WAL を外部システムが購読して変更イベントとして扱う CDC(Change Data Capture)は、検索インデックスやキャッシュ、ストリーム処理基盤へデータを同期する定番手法になっています。また、ベースバックアップに WAL を継続的に足し込むことで任意時点へ復元する PITR(ポイントインタイムリカバリ)も、ログがあればこそ可能になります。障害対策として生まれた1本のログが、複製・連携・バックアップの共通基盤にまでなっている — WAL が「データベースの心臓部」と呼ばれる理由です。