WAL 預寫式日誌:PostgreSQL 資料安全的守護者 | PostgreSQL
WAL(Write-Ahead Logging) 是 PostgreSQL 實現資料持久性(Durability)的核心機制。其根本原則是:在修改資料頁之前,必須先將變更記錄寫入 WAL。只要 WAL 記錄持久化到磁碟,即使發生 Crash,已提交的 Transaction 也能在重啟後完整恢復。WAL 同時也是 PostgreSQL 複寫與備份的技術基石。
核心原則:隨機 I/O 變順序 I/O
傳統做法是每次 COMMIT 後立刻將所有 dirty page 寫回磁碟。問題在於資料頁分散在磁碟各處,每次 COMMIT 都要進行大量隨機 I/O(Random I/O),效能極差。
WAL 的解決方案是將隨機 I/O 轉換為順序 I/O:
傳統做法(Force Policy):
COMMIT → 寫多個散落的資料頁 → 大量 Random I/O → 效能瓶頸
Page A (Offset: 4MB)
Page B (Offset: 156MB)
Page C (Offset: 892MB)
WAL 方式:
COMMIT → 只需順序追加 WAL 記錄 → Sequential I/O → 高效能
WAL segment: 追加到末尾
資料頁: 非同步延遲寫回(由 bgwriter/checkpointer 負責)
WAL 的兩大保證:
- Durability(持久性):COMMIT 時只需要 WAL flush 到磁碟,不需要資料頁立即寫回
- Atomicity(原子性):Crash Recovery 時重放 WAL,確保 Transaction 要麼完整重放、要麼完全不重放
LSN(Log Sequence Number)
LSN 是 WAL 記錄的全域唯一位置標識,是一個 64-bit 整數,表示在 WAL 串流中的位元組偏移量。
LSN 格式:高 32 位 / 低 32 位(以 16 進位顯示)
例如:0/1A2B3C4D
我們可以查詢當前的 LSN 狀態:
-- 查看當前 WAL 已寫入的 LSN
SELECT pg_current_wal_lsn();
-- 輸出:0/1A2B3C4D
-- 查看當前 WAL 已 flush 到磁碟的 LSN
SELECT pg_current_wal_flush_lsn();
-- 查看當前 WAL 檔案名稱
SELECT pg_walfile_name(pg_current_wal_lsn());
-- 輸出:000000010000000000000001
-- 計算兩個 LSN 之間的 WAL 大小(bytes)
SELECT pg_wal_lsn_diff('0/2000000', '0/1A2B3C4D');
在備援伺服器上,還有兩個重要的 LSN 函數:
-- 備援伺服器接收到的最新 LSN
SELECT pg_last_wal_receive_lsn();
-- 備援伺服器重放到的最新 LSN
SELECT pg_last_wal_replay_lsn();
WAL Segment 檔案結構
WAL 以固定大小的 Segment 檔案儲存,預設每個 Segment 為 16MB,位於 $PGDATA/pg_wal/ 目錄:
pg_wal/ 目錄結構:
├── 000000010000000000000001 (Segment 1, Timeline 1)
├── 000000010000000000000002 (Segment 2, Timeline 1)
├── 000000010000000000000003 (Segment 3, Timeline 1)
└── archive_status/
├── 000000010000000000000001.done (已歸檔)
└── 000000010000000000000002.ready (等待歸檔)
檔名格式(24 個 16 進位字元):
┌────────┬────────────────┬────────────────┐
│ 8 位 │ 8 位 │ 8 位 │
│Timeline│ Log Segment │ Segment Num │
└────────┴────────────────┴────────────────┘
每個 Segment 內部由 8KB 的 Page 組成,每個 WAL Record 包含:
WAL Record 結構:
┌────────────┬────────────┬───────────────┬─────────────┐
│ xl_tot_len │ xl_xid │ xl_prev │ xl_info │
│ (記錄總長) │ (TX ID) │ (前一條 LSN) │ (操作類型) │
├────────────┴────────────┴───────────────┴─────────────┤
│ data (變更內容,可能包含 Full Page Image) │
└────────────────────────────────────────────────────────┘
Checkpoint 機制
Checkpoint 是 WAL 與資料頁同步的關鍵機制——它確保在某個時間點之前的所有變更已寫入磁碟。
Checkpoint 執行流程:
- 記錄 checkpoint_lsn:獲取當前 LSN 作為 Checkpoint 點
- 寫回所有 dirty buffers:Checkpointer 掃描 Shared Buffer Pool,將所有標記為 dirty 的 Page 寫回磁碟(最耗時的步驟)
- 寫入 CHECKPOINT record 到 WAL:記錄 checkpoint_lsn 等資訊
- 更新 pg_control 檔案:記錄最新 Checkpoint 的位置,此 LSN 之前的 WAL 理論上可以刪除
Checkpoint 觸發條件
| 條件 | 說明 |
|---|---|
checkpoint_timeout 到期 | 預設 5 分鐘 |
WAL 大小超過 max_wal_size | 預設 1GB |
手動執行 CHECKPOINT 命令 | 需要超級使用者權限 |
| PostgreSQL 正常關閉 | smart/fast shutdown |
Checkpoint 與 Crash Recovery 的關係
時間軸:
Checkpoint A Checkpoint B Crash 點
│ │ │
────●────────────────●──────────────✗
Crash Recovery 只需從最近的 Checkpoint(B)重放 WAL 到 Crash 點。
→ Checkpoint 越頻繁,Recovery 時間越短
→ 但正常執行期間 I/O 壓力越大
full_page_writes:防止 Torn Page
磁碟通常以 512B 或 4096B 為單位寫入,但 PostgreSQL 頁面為 8KB。如果寫入頁面時發生 Crash(只寫了一半),頁面會損壞(Torn Page)。
full_page_writes 的解決方案:
Checkpoint 後第一次修改某個 Page 時,WAL 記錄包含該 Page 的完整映像(FPI)。
後續對同一 Page 的修改,在下一個 Checkpoint 之前只記錄增量變更。
Checkpoint 修改 P1 修改 P2 修改 P1 Checkpoint
│ │ │ │ │
──────●────────────●────────●────────●──────────●
│ │
└─ WAL 包含 P1 └─ 只記錄增量
完整映像(FPI) (已有 FPI 保護)
代價是 WAL 體積增大,但生產環境務必保持 full_page_writes = on。
WAL Level
WAL 記錄的詳細程度由 wal_level 參數控制:
| Level | 功能 | 用途 |
|---|---|---|
minimal | 最基本的恢復資訊 | 單機、不需備份 |
replica(預設) | 支援 Streaming Replication 與 PITR | 大多數生產環境 |
logical | 支援 Logical Replication 與 logical decoding | 跨版本複寫、CDC |
-- 查看當前 WAL Level
SHOW wal_level;
-- 輸出:replica
Crash Recovery 流程
當 PostgreSQL 啟動時發現上次並非正常關閉,會進入 Recovery 模式:
讀取 pg_control 檔案
│
▼
判斷 state 欄位
│
├─ 正常關閉 → 直接啟動
│
└─ 非正常關閉 → 進入 Recovery 模式
│
▼
找到最近的 Checkpoint
(從 pg_control 讀取 checkpoint_lsn)
│
▼
從 checkpoint_lsn 開始順序重放 WAL 記錄
│
▼
重放到 WAL 結尾
│
▼
建立新 Checkpoint,更新 pg_control
│
▼
開放連線,正常運作
監控 WAL 統計
-- PG14+ WAL 全域統計
SELECT
wal_records, -- 產生的 WAL 記錄總數
wal_fpi, -- Full Page Image 的數量
pg_size_pretty(wal_bytes) AS wal_size, -- WAL 總大小
wal_buffers_full, -- 因 buffer 滿而等待的次數
wal_write, -- WAL 寫入次數
wal_sync -- WAL fsync 次數
FROM pg_stat_wal;
-- 監控 Checkpoint 頻率
SELECT
checkpoints_timed, -- 因 timeout 觸發的 checkpoint 數
checkpoints_req, -- 因 WAL 大小觸發的 checkpoint 數
buffers_checkpoint, -- checkpoint 寫回的 buffer 數
buffers_clean, -- bgwriter 寫回的 buffer 數
buffers_backend -- backend 直接寫回的 buffer 數(應盡量少)
FROM pg_stat_bgwriter;
使用 pg_waldump 解析 WAL
pg_waldump 是分析 WAL 內容的強大工具:
# 解析指定 WAL segment 的記錄
pg_waldump $PGDATA/pg_wal/000000010000000000000001
# 只顯示特定 resource manager 的記錄
pg_waldump --rmgr=Heap $PGDATA/pg_wal/000000010000000000000001
# 顯示統計摘要
pg_waldump --stats $PGDATA/pg_wal/000000010000000000000001
生產環境建議配置
# postgresql.conf
# WAL 等級:至少 replica
wal_level = replica
# WAL 大小範圍
min_wal_size = 512MB
max_wal_size = 2GB
# Checkpoint 頻率(Recovery 時間 vs I/O 壓力的平衡)
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
# WAL 寫入效能
wal_compression = lz4 # PG15+ 壓縮 Full Page Image
synchronous_commit = on # 生產環境保持 on
wal_buffers = 16MB
儲存配置建議:
pg_wal/目錄放在獨立的快速儲存設備(NVMe SSD)- 確保有足夠空間(至少
max_wal_size的 2 倍) - 啟用 WAL 歸檔以支援 PITR
常見陷阱
Replication Slot 導致 WAL 積累
-- 排查:找出 inactive 的 replication slot
SELECT slot_name, active, restart_lsn
FROM pg_replication_slots
WHERE active = false;
-- 解決:刪除不再使用的 slot
SELECT pg_drop_replication_slot('unused_slot');
checkpoint_completion_target 設太低
設定過低(如 0.5)會導致 Checkpoint I/O 集中在短時間內,產生 I/O 突刺。建議設為 0.9,讓 Checkpoint 的 I/O 均勻分散在整個 Checkpoint 週期中。
WAL 與複寫的關係
WAL 是 PostgreSQL 所有複寫機制的基礎:
- Streaming Replication:Primary 持續串流 WAL 給 Standby
- WAL Archiving:WAL Segment 完成後複製到歸檔位置
- PITR(Point-In-Time Recovery):從備份 + WAL 恢復到任意時間點
- Logical Replication:從 WAL 解碼行級別變更,複製到訂閱者
PG17 新功能:Incremental Backup
PG17 引入 WAL Summarizer,記錄每個 WAL Segment 修改了哪些資料頁,用於實現增量備份:
# 啟用 WAL Summarizer(PG17+)
summarize_wal = on
增量備份只需備份自上次備份以來修改過的資料頁,大幅降低備份時間和空間。
總結
WAL 是 PostgreSQL 資料安全的基石:
- 將隨機 I/O 轉換為順序 I/O,在保障資料安全的同時提升效能
- LSN 是 WAL 的全域座標系,所有複寫與備份操作都以 LSN 為基準
- Checkpoint 是 WAL 與資料頁的同步點,決定了 Crash Recovery 的起點
- full_page_writes 防止 Torn Page,生產環境務必開啟
- WAL 是 Streaming Replication、Logical Replication、PITR 的技術基礎
下一篇,我們將探討 Buffer Management——PostgreSQL 如何用 Clock-Sweep 演算法管理 Shared Buffer Pool。