WAL と障害復旧 ── データを絶対に失わない仕組み
このレイヤーの役割:クラッシュしてもコミット済みデータを失わない。レプリケーションの基盤。RDB の「保険」。

この章で何ができるようになるか:「WAL がなぜ必要か」「チェックポイントとは何か」「WAL の書き込みがボトルネックになるケース」を説明でき、関連パラメータを適切に設定できるようになる。
なぜ WAL が必要か
WAL なしの場合:
1. メモリ上のバッファを変更
2. バッファをディスクに書き出す(ランダムI/O)
3. → 書き出し途中でクラッシュ → データが中途半端な状態
4. → 復旧不可能
WAL ありの場合:
1. 「何を変更するか」を WAL に書く(シーケンシャルI/O)
2. WAL を fsync(ディスクに確実に書き出す)
3. メモリ上のバッファを変更
4. → クラッシュしても WAL から変更を再適用(redo)できる
WAL の書き込みはシーケンシャルI/O → ランダムI/O より遥かに速い
WAL レコードの構造
WAL レコードの内容:
- LSN(Log Sequence Number): WAL 内の位置(単調増加)
- Transaction ID
- 操作の種類(INSERT / UPDATE / DELETE / COMMIT / ...)
- 対象のテーブルとページ
- 変更前と変更後のデータ(Full Page Image の場合はページ全体)
-- 現在の WAL 位置(LSN)を確認
SELECT pg_current_wal_lsn();
-- → 0/1A3E5F00
-- WAL の書き込み速度を確認
SELECT
pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') / (1024*1024) AS wal_written_mb;
WAL の書き込みフロー
1. トランザクション内の各操作で WAL レコードを生成
2. WAL レコードを WAL バッファ(メモリ)に書き込む
3. COMMIT 時:
a. WAL バッファをディスクに書き出す(write)
b. fsync を実行(ディスクの書き込みバッファをフラッシュ)
c. → ここで初めてコミットが「確定」する
4. クライアントにコミット成功を返す
synchronous_commit の設定
-- デフォルト: on(WAL の fsync を待つ)
SET synchronous_commit = on;
-- → 最も安全。クラッシュしてもデータを失わない。
-- → fsync の待ち時間がレイテンシに加算される。
SET synchronous_commit = off;
-- → fsync を待たずにコミット成功を返す
-- → レイテンシが大幅に短縮(数ms → 数百μs)
-- → クラッシュ時に直近のコミット(最大 wal_writer_delay 分)を失う可能性
-- → 用途: ログ書き込みなど、多少のデータロスが許容される場合
チェックポイント:WAL と実データの同期点
WAL だけが増え続けると、障害復旧時に大量の WAL を再適用する必要がある。チェックポイントで定期的にメモリの変更をディスクに反映し、古い WAL を不要にする。
チェックポイントの動作:
1. 全ての Dirty Page(メモリ上で変更されたページ)をディスクに書き出す
2. チェックポイントの LSN を記録
3. この LSN より前の WAL はもう不要
→ 復旧時はチェックポイントの LSN から再生すればよい
チェックポイントのトリガー:
- checkpoint_timeout(デフォルト 5分)ごと
- max_wal_size(デフォルト 1GB)を超えたとき
- 手動: CHECKPOINT コマンド
- pg_start_backup() 実行時
-- チェックポイント関連のパラメータ
SHOW checkpoint_timeout; -- 5min(チェックポイントの最大間隔)
SHOW max_wal_size; -- 1GB(この量の WAL が生成されたらチェックポイント)
SHOW checkpoint_completion_target; -- 0.9(チェックポイントの書き出しを timeout の 90% に分散)
checkpoint_completion_target の意味
checkpoint_completion_target = 0.9, checkpoint_timeout = 5min の場合:
チェックポイントの書き出しを 5min × 0.9 = 4.5分 かけて行う
→ I/O を分散してスパイクを防ぐ
checkpoint_completion_target = 0.1 の場合:
30秒で一気に書き出す
→ I/O スパイクが発生し、通常のクエリが遅くなる
Full Page Writes
チェックポイント後の最初のページ変更時に、ページ全体を WAL に書き込む。
なぜ必要か:
OS は 4KB 単位で書き込む。PostgreSQL は 8KB ページ。
ページ書き出しの途中でクラッシュすると、ページの前半は新データ、後半は旧データ
という「Torn Page」が発生する。
Full Page Write があれば、WAL からページ全体を復元できる。
コスト:
チェックポイント直後は WAL の書き込み量が一時的に増大
(ページ全体 8KB × 変更されたページ数)
SHOW full_page_writes; -- on(デフォルト。変更すべきではない)
障害復旧(Crash Recovery)
PostgreSQL 起動時の復旧フロー:
1. pg_control ファイルから最後のチェックポイントの LSN を読む
2. そのチェックポイント以降の WAL レコードを順番に再適用(redo)
3. コミットされていないトランザクションの変更を取り消す(undo は不要※)
4. 復旧完了
※ PostgreSQL は未コミットのデータを可視性判定で除外する
(t_xmin のトランザクションがコミットされていなければ見えない)
→ 明示的な undo 処理は不要
WAL のチューニングポイント
-- WAL バッファサイズ(メモリ上の WAL バッファ)
SHOW wal_buffers; -- -1(自動: shared_buffers の 1/32, max 64MB)
-- WAL セグメントサイズ(1ファイルのサイズ)
-- デフォルト 16MB。大量書き込みなら 64MB〜256MB に増やす
-- WAL の圧縮
SET wal_compression = on; -- WAL レコードを圧縮(CPU ↑, I/O ↓)
-- Full Page Writes が多い場合に特に効果的
-- WAL の書き込み先を別ディスクに分離
-- → WAL とデータの I/O が競合しない
-- → シンボリックリンクで pg_wal を別マウントポイントに向ける
Point-in-Time Recovery(PITR)
WAL を保存しておけば、任意の時点にデータベースを復元できる。
1. ベースバックアップ(pg_basebackup)を定期的に取得
2. WAL を連続的にアーカイブ(archive_command)
復元:
1. ベースバックアップをリストア
2. アーカイブされた WAL を再適用
3. recovery_target_time で「2026-04-08 15:30:00 まで」と指定
→ その時点のデータベース状態に復元
-- postgresql.conf
archive_mode = on
archive_command = 'cp %p /backup/wal_archive/%f'
-- recovery.conf(復元時)
restore_command = 'cp /backup/wal_archive/%f %p'
recovery_target_time = '2026-04-08 15:30:00'
まとめ
| 概念 | 役割 | 設定ポイント |
|---|---|---|
| WAL | 変更を先にログに書く → クラッシュ耐性 | wal_buffers, wal_compression |
| fsync | WAL をディスクに確実に書き出す | synchronous_commit |
| チェックポイント | Dirty Page をディスクに反映 → 古い WAL を不要にする | checkpoint_timeout, max_wal_size |
| Full Page Writes | Torn Page を防ぐ | full_page_writes = on(変更不可) |
| PITR | 任意の時点に復元 | archive_mode, archive_command |