ライトアヘッドログ(WAL)とは?仕組みとfsync設計・復旧を実装目線で解説
ライトアヘッドログ(WAL、Write-Ahead Logging)とは、データベースへの変更をデータファイルへ書き戻すより先に、その変更内容をログとして永続化しておく方式です。ログが先、データ本体は後。この順序を守るだけで、電源が落ちた瞬間にメモリ上にしかなかった更新を、再起動後にログから作り直せます。この記事で扱う範囲は、コミット応答が返るまでの経路、チェックポイントとfull_page_writesが支える復旧、PostgreSQL 18とMySQL InnoDBの既定値の差、WALが消えずに溜まる運用事故までです。ACIDの定義や分離レベルの一覧はトランザクションの基礎を実装目線でまとめた記事にあるため、ここでは耐久性の実装に絞ります。
まとめ|WALで守れる範囲と、fsyncを緩めて失う保証の境界
結論は次のとおりです。WALが保証するのは「コミット応答を返した更新は、その直後に電源が落ちても再起動後に残っている」という1点だけで、この保証はfsyncがストレージから返った時点で成立します。synchronous_commitをoffにすれば守られる範囲は狭まり、応答を返した更新の直近数百ミリ秒ぶんが消えます。データが壊れるわけではありません。壊れないまま、返事をした更新だけが無かったことになる。この違いを混同すると、入金記録や在庫引当のような「消えたことに気づけない更新」で事故になります。
運用面で効くのはWALの生成量ではなく、生成したWALをいつ捨てられるかです。PostgreSQLはレプリケーションスロットが要求するWALを既定で無制限に保持します(max_slot_wal_keep_sizeの既定は-1)。使わなくなったスロットが1本残るだけで、pg_walはディスクが埋まるまで膨らむ。監視に置くべきは、非アクティブなスロットの有無、アーカイブの失敗回数、pg_stat_walのWAL生成量の3つです。
ライトアヘッドログの定義|データ本体より先にログを固定する順序制約
WALは特定の製品機能の名前ではなく、耐久性を成り立たせるための順序の取り決めです。何を禁じている規則なのかを押さえます。
ログ先行書き込みが保証する順序|データページより先のログ固定
WALの規則は一行で書けます。あるデータページへの変更は、その変更を記述したログレコードが永続格納領域へ書き出された後でなければ、ディスクへ書き出してはならない。禁止しているのは「データが先、ログが後」という順序だけです。この順序を守っていれば、データページの書き戻しがどれだけ遅れても、ログさえ読めば同じ状態を作り直せます。
この考え方の原型は、C. Mohanらが1992年にACM TODSへ発表したARIESという復旧手法にあります。PostgreSQLのpg_walに並ぶ既定16MBのセグメントも、MySQL InnoDBのredo logも、SQLiteの-walファイルも、実装は違えど守る規則は同じ。ランダムな位置に散らばるデータページの書き込みを後回しにし、追記だけで済むログを同期する。性能上の利点はこの置き換えから来ています。
WALレコードとLSNの関係|変更単位に振られる位置情報の役割
WALレコードには、WAL全体の中での位置を示すLSN(Log Sequence Number)が振られます。LSNは単調増加する値で、PostgreSQLでは0/16B3748のような16進表記。各データページのヘッダには、そのページを最後に変更したWALレコードのLSNが記録されています。
復旧時の処理は単純です。ログを先頭から読み、レコードのLSNとページのLSNを比べ、ページのほうが古ければ再適用する。新しければ反映済みなので読み飛ばす。冪等性をこの比較だけで担保しているため、復旧を途中で落として再実行しても結果は変わりません。現在の書き込み位置はpg_current_wal_lsn()で取得できます。
コミット応答が返る時点|fsyncとsynchronous_commitで決まる範囲
生成されたWALレコードは、まず共有メモリ上のWALバッファに置かれます。PostgreSQL 18のwal_buffersの既定は-1で、shared_buffersの約3%(下限64kB・上限16MB)が自動で割り当てられます。ここからwrite()が呼ばれる契機はコミット、バッファ満杯(wal_buffers_fullが増える)、wal_writer_delayの200ms間隔の3つ。ただしwrite()はOSのページキャッシュへ渡すだけで、耐久性が確定するのはfsync(Linuxでは既定でfdatasync)が返った時点です。synchronous_commitは既定on、offはローカルのfsyncすら待たず、localは自分のfsyncまで、remote_applyはスタンバイの適用完了まで待ちます。参照系レプリカで「書いた直後に読めない」問題を潰すならremote_applyで、この判断は同期・非同期のレプリケーション設計とセットです。
クラッシュ復旧の実際|チェックポイントからのREDOと部分書き込み対策
WALは書くだけでは意味がなく、落ちた後に読み直されて初めて役に立ちます。
チェックポイントの役割|REDOの開始位置を前に進める定期処理
WALを先頭から全部読み直していたら復旧に何時間もかかります。そこでDBMSは定期的にチェックポイントを打ち、その時点までの変更をデータファイルへ反映させ、「ここより前のWALは復旧に不要」という印を残す。復旧はこの印(REDOポイント)から始まります。
PostgreSQL 18の既定では、checkpoint_timeoutの5分経過かmax_wal_sizeの1GBぶんが溜まったかの早いほうで走ります。checkpoint_completion_targetの0.9はI/Oのスパイクを平すための引き延ばし設定。合っているかはpg_stat_checkpointerで判定でき、num_requestedがnum_timedを上回っていれば時間ではなくWAL量が引き金です。max_wal_sizeを増やして間隔を延ばせば、次に説明する全ページ書き出しも減ります。
部分書き込みへの備え|full_page_writesが守るページの整合
OSのページサイズは4kB、PostgreSQLのブロックは8kB。この不一致のせいで、電源断のタイミング次第では8kBのうち前半だけが書かれた「破れたページ」が生まれます。壊れたページへWALの差分を適用しても、正しい状態にはなりません。
full_page_writes(既定on)は、チェックポイント後にそのページを最初に変更するとき、差分ではなくページ全体のイメージをWALへ書くことでこれを防ぎます。代償はWAL量。pg_stat_walのwal_fpiが全体の大半を占めるなら、チェックポイント間隔が短すぎます。wal_compressionは既定offですが、全ページイメージが多い基盤では有効にするとWAL量が目に見えて減ります。CPUと引き換えに帯域とディスクを買う設定です。
起動時リカバリの流れ|REDO適用とUNDOで戻る未コミット分
再起動後、DBMSは制御ファイルから最後のチェックポイントのREDOポイントを読み、そこからWALを順に適用します。ここで製品差が出ます。InnoDBはredo logの適用後、undo logで未コミットのトランザクションを巻き戻す。PostgreSQLにはこのUNDOフェーズがありません。
理由は版管理の方式です。PostgreSQLは更新時に古い行を残して新しい版を追記するため、コミットされなかった行は「誰からも見えない版」として残るだけで、巻き戻す作業が要りません。代わりに見えない版を後から回収する処理が付きます。この構造はMVCCと過去版の回収のとおりで、復旧が速い代わりにVACUUMの運用を抱える交換条件です。
PostgreSQL・MySQL・SQLiteの実装差|既定値とパラメータの対応
WALという規則は共通でも、既定値とパラメータ名は製品ごとに違います。移行や比較でつまずきやすい対応を先に整理します。
| 観点 | PostgreSQL 18 | MySQL 8.4 InnoDB | SQLite |
|---|---|---|---|
| ログの呼び名 | WAL | redo log | WALファイル |
| 既定の容量 | 16MBセグメント | 合計100MB | 1000ページで回収 |
| 同期の既定 | 毎コミットで同期 | 毎コミットで同期 | FULL(毎コミット) |
| 緩める設定値 | local または off | 0 または 2 | NORMAL |
| 複製への転用 | ストリーミング複製 | binlogが担当 | 非対応 |
| 変更の取り出し | 論理デコード | binlog経由のCDC | 非対応 |
誤解が多いのは複製の列です。InnoDBのredo logはクラッシュ復旧専用で、レプリケーションやCDCが読むのは別系統のバイナリログ。PostgreSQLが1本のWALで両方を賄うのとは構造が違います。SQLiteはPRAGMA journal_mode=WALで切り替える方式で、同時に書ける接続は1つ、wal-indexを共有メモリに置くためネットワーク越しでは動きません。
PostgreSQLのWAL構成|16MBセグメントとpg_walの既定挙動
PostgreSQLのWALはpg_wal配下に16MBのファイルとして並び、不要なセグメントは削除されるか名前を付け替えて再利用されます。wal_levelの既定はreplicaで、このままストリーミング複製とPITRが使えます。minimalは複製もPITRも使えず、選ぶ場面は初回ロード専用の一時インスタンスくらい。2026年8月時点の最新安定版は18.4、バージョン14は2026年11月12日にサポートが終了します。
MySQL InnoDBのredo log|容量100MB既定と二重書き込み
InnoDBのredo logは、8.0.30以降innodb_redo_log_capacityという単一の変数で総容量を指定します。既定値は104857600バイト、つまり100MB。それ以前のinnodb_log_file_sizeとinnodb_log_files_in_groupはこの変数に置き換えられました。書き込みが多い基盤で100MBのままだと、チェックポイントが頻発します。
耐久性を決めるのはinnodb_flush_log_at_trx_commitで、既定の1がフルACID準拠。2に落とすとフラッシュが毎秒になり、OSごと落ちた場合に直近1秒ぶんを失います。加えてInnoDBはダブルライトバッファを持ち、ページを本来の位置へ書く前に連続領域へ一度書きます。PostgreSQLがfull_page_writesとしてWALの中で解いた問題を、InnoDBは専用領域で解いている構図です。
WALの二次利用|レプリケーション・PITR・CDCが同じログを読む構造
WALは復旧用のログであると同時に、「変更が起きた順に並んだ完全な記録」でもあります。複製・時点復旧・変更データ取り込みは、この性質の上に乗っています。
物理レプリケーションの土台|WAL送信で複製が追随していく仕組み
ストリーミング複製がやっているのは、プライマリが生成したWALをスタンバイへ送り、スタンバイ側で復旧と同じREDO適用を続けることです。復旧を終わらせずに走らせ続けている状態、と言い換えてもかまいません。max_wal_sendersの既定は10。遅延はLSNの差分で測るため、「何秒遅れているか」ではなく「何バイト積み残しているか」で見るのが実務的です。
PITRで戻せる範囲|アーカイブとベースバックアップが必要な理由
PITR(ポイントインタイムリカバリ)は、ベースバックアップとその後のWALで任意の時点を再現する手法です。archive_modeの既定はoffなので、明示的にonにしてアーカイブ先を設定します。設定せずバックアップだけ取っていても、戻せるのは取得時点だけ。「深夜2時のバックアップ+朝9時までのWAL」で朝8時59分に戻せるのは、その間のセグメントが1本残らず揃っている場合に限られます。
論理デコードとCDC|wal_level=logicalで開く変更の取り出し
wal_levelをlogicalにすると、WALから「どのテーブルのどの行がどう変わったか」を行単位で取り出せます。これが論理デコードで、CDCツールが読むのもこの出力です。アプリ側にトリガーやタイムスタンプ列を足さずに変更を捕捉できるため、稼働中の基幹データベースから分析基盤へデータを流す経路になります。方式ごとの比較はCDCの3方式と実装設定にまとめました。
この構成で必ず設計へ含めるのが、次章のスロット管理です。論理レプリケーションスロットは、消費側が止まっている間もWALを保持し続けます。分析基盤側のコネクタが停止したまま週末を越え、月曜の朝に本番のディスクが埋まる。設計段階でスロット監視を組み込んでいれば起きない事故です。データベースからデータ基盤までを一続きで設計するなら、データ分析基盤構築・MLOps構築支援で受託の相談を受け付けています。
WALが消えずに溜まる経路|レプリケーションスロットと監視指標
WALで起きる障害の大半は、書き込み性能ではなくディスク枯渇です。原因は「捨てられるはずのWALが捨てられていない」ことで、経路は2本しかありません。
スロットが止めるWAL削除|max_slot_wal_keep_sizeの既定値
レプリケーションスロットは、消費側がまだ読んでいないWALを保持させる仕組みです。スタンバイが一時停止しても取りこぼさない利点の裏で、消費が止まれば保持も止まりません。max_slot_wal_keep_sizeの既定値は-1、つまり「無制限に保持する」という意味です。wal_keep_sizeの既定0とは別の話なので混同しないでください。
対策は2つ。使わなくなったスロットを残さないことと、max_slot_wal_keep_sizeへ空き容量に見合う上限を設定することです。pg_replication_slotsでactiveがfalseのまま放置されたスロットは、ディスク枯渇の予約に等しいと考えてください。上限を超えたスロットは無効化され、スタンバイやCDCコネクタは初期化からやり直し。レプリカの作り直しと、プライマリのディスク満杯停止。避けるべきは後者です。
アーカイブ失敗の連鎖|archive_commandが返す失敗の扱い
もう1本の経路がアーカイブです。archive_commandは成功時に終了コード0を返す契約で、0以外が返る限り同じセグメントのアーカイブを繰り返し試みます。その間、そのセグメントは削除されません。転送先のディスク満杯、認証情報の期限切れ、ネットワーク断。どれでも結果は同じで、pg_walが静かに膨らみ続けます。
さらに悪いのは、アーカイブコマンドが失敗しているのに0を返す実装です。WALは正常に削除され、バックアップは欠損したまま「成功」として記録されます。気づくのは復旧しようとした日。pg_stat_archiverのfailed_countとlast_failed_timeは監視項目に入れてください。
監視に置く指標|pg_stat_walとpg_stat_ioで見る書き込み量
PostgreSQL 18のpg_stat_walは、wal_records・wal_fpi・wal_bytes・wal_buffers_full・stats_resetの5列です。日常的に見るのはwal_bytesの増加率とwal_fpiの割合。後者が高止まりしているなら、チェックポイント間隔を延ばすだけで生成量が下がります。
18特有の変更点が1つあります。以前pg_stat_walにあったWALのI/O時間の列は、18ではpg_stat_io側へ移りました。track_wal_io_timingを有効にしたうえでwrite_timeとfsync_timeを見ます。17以前のダッシュボードをそのまま持ち込むとこの2指標が欠測するため、バージョンアップ時の確認項目に入れてください。MySQLで同じ観点を見るならInnodb_os_log_writtenの増加率が対応します。
耐久性を落とす判断|synchronous_commitを緩めてよい処理の条件
同期コミットを緩めれば書き込みは速くなります。速くしてよい処理と、速くしてはいけない処理の線引きを条件付きで示します。
緩めてよい処理の条件|再生成できるデータと再送で復旧できる更新
緩めてよいのは、失われたことに後から気づけて、かつ作り直せる更新だけです。該当するのは次の3つ。
- ETLやバッチの一括ロード(再実行すれば同じ結果になる)
- 集計テーブルやマテリアライズドビューの再構築(元表から再生成できる)
- アクセスログの蓄積(送信側に再送があり、数百ミリ秒の欠落が結果を変えない)
synchronous_commitはセッション単位でも設定できるため、バッチ処理のセッションだけSET LOCAL synchronous_commit = offとするのが実務的です。インスタンス全体で緩める必要はありません。
緩めてはいけない処理|入金記録と在庫引当で1秒分を失う場合の代償
反対に、条件を問わず同期コミットのままにすべき処理もあります。入金・決済・出金の記録、在庫の引当と払出、採番、外部通知と対になる状態更新。共通するのは「消えたことに気づけない」性質です。
決済代行から成功応答を受け取り、自社DBへ入金記録を書き、その直後にサーバーが落ちたとします。synchronous_commit=offなら、入金記録だけが消えた状態で復旧します。決済は成立しているのに、システム上は未入金。この不整合を検知できるのは顧客からの問い合わせか月末の突合作業です。在庫引当も同様で、引当済みの記録が消えれば同じ在庫を二重に売ります。得られるのはコミット数百マイクロ秒の短縮、失うのは金銭的な整合性。交換条件として成立していません。
fsync=offを使う場面|初期ロードと再構築できる検証環境だけ
fsync=offはsynchronous_commit=offと危険度が桁違いです。データファイルの整合性そのものを保証しなくなるため、電源断でデータベースが復旧不能になり得ます。失うのは直近のコミットではなくクラスタ全体です。
それでも使う場面は2つに限られます。初期データロード専用に立てた一時インスタンスで、壊れたらinitdbからやり直せるとき。もう1つはCIで毎回作り直す検証環境です。本番系はもちろん、本番データのコピーを置いたステージング環境でも設定してはいけません。ロード完了後にonへ戻す確認までを運用に含めて、初めて許容できる設定です。
よくある質問
WALの設計と運用で問い合わせの多い5点です。
ライトアヘッドログとトランザクションログは同じものですか?
ほぼ同義として使われますが、指す範囲が製品で違います。トランザクションログはSQL ServerでのWALの呼び名で、PostgreSQLではWAL、Oracleでは REDOログ、MySQL InnoDBではredo logが対応します。注意が要るのはMySQLで、redo logはクラッシュ復旧専用、レプリケーションやCDCが読むバイナリログは別物です。復旧用と複製用のどちらを指しているかは、製品名とセットで確認してください。
WALのファイルが増え続けてディスクが埋まるのはなぜですか?
原因はほぼ2つです。1つはレプリケーションスロットで、消費側が止まったスロットが残るとWALは削除されません。max_slot_wal_keep_sizeの既定は-1(無制限保持)のため、放置すれば空き容量がなくなるまで積み上がります。もう1つはアーカイブの失敗。pg_replication_slotsで非アクティブなスロットを探し、pg_stat_archiverで失敗回数を確認すれば切り分けられます。
synchronous_commitをoffにするとデータは壊れますか?
データベースが壊れることはありません。offでも書き込み順序の規則は守られるため、復旧後は「どこかの時点までの整合が取れた状態」になります。失われるのは、コミット応答を返したのにfsyncが済んでいなかった直近ぶんの更新だけ。再実行できる処理なら許容でき、入金や在庫のように再実行できない処理では許容できません。
WALのサイズを減らすにはどこから手を付ければよいですか?
最初にpg_stat_walのwal_fpiを確認してください。全ページイメージがWAL量の大半なら、checkpoint_timeoutとmax_wal_sizeを引き上げて間隔を延ばすのが効きます。チェックポイント直後の最初の更新だけが全ページを書くため、間隔が延びれば回数が減るからです。それでも足りなければwal_compressionを有効にします。wal_levelをminimalへ落とす手は、複製もPITRも捨てるため本番系では選べません。
MySQLのbinlogとredo logはどちらがWALにあたりますか?
ログ先行書き込みの規則を担うのはredo logです。クラッシュ復旧はredo logから行われ、binlogは使われません。binlogはレプリケーションとポイントインタイムリカバリのための論理的な変更記録で、ストレージエンジンの上の層にあります。両方を有効にしている場合、InnoDBはコミット時に二相コミットで整合を取ります。PostgreSQLが1本のWALで復旧と複製を賄うのに対し、MySQLは役割ごとに2本持つ構成です。
関連記事
- トランザクションとは?ACID・分離レベル・commit/rollbackを実装目線で解説【2026年版】:ACIDと分離レベルの全体像
- MVCCとは?多版型同時実行制御の仕組みと過去版の回収を実装目線で解説:UNDOフェーズを持たない代償になる過去版の回収
- データベースレプリケーションとは?同期・非同期の違いとレプリカ遅延の設計を実装者目線で解説【2026年版】:WALをどこまで待つかを決める設計
- CDC(Change Data Capture)とは?3方式の仕組み・実装設定と採用判断を実装者目線で解説:論理デコードを含む3方式の比較
- Debeziumとは?コネクタ設定と3つの配置形態を実装視点で解説【2026年版】:スロットを消費する側の設定