🎯 什麼情境該想到我

當你想理解(或自己實作)「系統當機/斷電後,資料如何不丟、狀態如何回復一致」時。

⚙️ 怎麼用

核心原則:先寫日誌,再改資料(Write-Ahead)。 只要日誌落盤,就算資料還沒寫完,重啟後也能靠日誌重建。

  1. 每次修改前,先把「要做什麼」順序追加寫入 WAL 並 fsync 落盤。
  2. 提交(commit)以日誌寫入成功為準,實際資料頁可稍後再刷。
  3. 崩潰復原(ARIES 三階段):分析 → 重做(redo 已提交但未落盤的)→ 撤銷(undo 未提交的)。
  4. WAL 也是複製與 CDC 的資料來源(把日誌串流給從庫)。

★ 為什麼「先寫日誌」就能保證不壞

根本問題:磁碟寫入不是原子的。 寫一個 8KB 的頁到一半斷電 → 半新半舊的頁,既不是舊狀態也不是新狀態。

❌ 直接改資料頁
   改到一半斷電 → ★ 資料頁毀損,而且「不知道毀在哪」

✅ WAL
   ① 先「順序追加」寫日誌:「我要把 X 從 A 改成 B」
   ② fsync 讓日誌真的落盤
   ③ 回報 commit 成功                     ← ★ 此刻就安全了
   ④ 資料頁稍後再刷(可以批次、可以延後)

   斷電後重啟 → 讀日誌 → 重做已提交但沒刷完的,撤銷沒提交的

★ 關鍵洞察:WAL 把「隨機寫」變成「順序寫」。
資料頁散落在磁碟各處(隨機寫,慢);日誌只是往檔案尾端追加(順序寫,快得多)。
所以 WAL 不只讓資料安全,還讓寫入變快——這是它能成為所有資料庫標配的真正原因。

★ fsync 才是真正的瓶頸

write()  → 資料只到了「作業系統的 page cache」   ★ 斷電就沒了
fsync()  → 強制刷到「實體儲存」                 ★ 這一步才安全,也是最慢的一步
儲存單次 fsync 大約
機械硬碟~10 ms(受限於轉速)
SSD~0.1–1 ms
有電池保護的 RAID 卡快取★ 極快(斷電時電池保住快取內容)

★ 這解釋了資料庫的一個核心指標:「每秒能提交幾個交易」約等於「每秒能做幾次 fsync」。

★ 群組提交(group commit):把多個並行交易的日誌合併成一次 fsync——
這是資料庫提升寫入吞吐最重要的優化之一,也是「批次寫入比逐筆寫入快幾十倍」的原因。

⚠️ 廉價 SSD 會對 fsync 說謊(回報已落盤但其實還在自己的快取裡),斷電就毀。
生產環境要用有斷電保護(PLP)的企業級 SSD。

★ ARIES 三階段(崩潰復原的標準做法)

① 分析 Analysis:從最後一個 checkpoint 掃到日誌尾端
                 → 找出崩潰時「哪些交易還沒提交」「哪些頁是髒的」
② 重做 Redo    :★ 從 checkpoint 重放「所有」變更(包含未提交的)
                 → 把資料庫還原到「崩潰當下」的狀態
③ 撤銷 Undo    :★ 回捲所有未提交的交易

★ 「為什麼 redo 要包含未提交的」是經典追問:
因為要先把狀態精確還原到崩潰當下,才能正確地執行 undo
(undo 需要知道「當時的值」是什麼)。這叫 repeating history。

Undo 本身也要寫日誌(CLR, compensation log record),否則 undo 到一半又崩潰就無法繼續。

Checkpoint 的作用:標記「這之前的都已經刷到資料頁了」→ 復原只要從 checkpoint 開始,不用從頭重放。
★ checkpoint 太稀疏 → 復原慢;太頻繁 → 平時的 I/O 壓力大。 這是可調的取捨。

★ WAL 的第二個身分:資料的變更串流

WAL 記錄了「每一次變更」,所以它天然就是一個有序的事件流:

用途怎麼用 WAL
複製把 WAL 串流給從庫重放(見 工具-資料複製)
★ CDC(變更資料捕捉)Debezium 讀 PG 的邏輯解碼 / MySQL 的 binlog → 推到 Kafka
時間點復原(PITR)基礎備份 + 重放到指定時間的 WAL
★ 事件溯源概念上同源:狀態是事件重放的結果

★ CDC 讀 WAL 而不是「定時輪詢資料庫」,是一個重要的架構選擇:
輪詢會漏掉中間狀態、有延遲、且對主庫造成負載;
讀 WAL 拿到的是完整、有序、低延遲的變更流。
→ 這也是 工具-Transactional-Outbox-Pattern 的一種替代方案。

同源的其他設計

系統對應
LSM-Treememtable 前的 WAL(見 工具-儲存引擎B-Tree與LSM-Tree)
檔案系統ext4/XFS 的 journal
Kafka★ 整個系統就是一個 WAL(分散式的有序追加日誌)
RedisAOF(append-only file)
etcd / Raft★ 共識演算法的 log 就是 WAL(見 工具-線性一致性與共識)
備忘錄模式檢查點 + 增量,同一個思路

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意 / 什麼時候不適用

  • ★ fsync 的成本決定了寫入吞吐上限。想提高就要靠群組提交或批次交易,
    而不是「加機器」——單一交易的提交延遲是物理限制。
  • ★ synchronous_commit = off 會讓寫入快很多,但斷電會丟掉最近的交易。
    這是一個明確的取捨(適合可重算的資料,不適合金流)。要有意識地設定,不要當成「效能調校」隨手開。
  • WAL 會佔空間且需要管理。PG 的 max_wal_size、歸檔策略設錯 →
    ★ 磁碟被 WAL 塞爆導致資料庫停止寫入(很常見的生產事故)。
  • ★ 複製槽(replication slot)洩漏:從庫掛了但 slot 還在 → 主庫的 WAL 永遠不能回收 → 磁碟爆掉。
    CDC 工具離線時特別容易踩到。要監控 slot 的落後量。
  • 廉價 SSD 對 fsync 說謊(見上)。
  • WAL 保證的是「單機的崩潰安全」,不是「磁碟毀損的安全」——那要靠備份與複製。
  • 邏輯複製與實體複製不同:PG 的實體複製(streaming)要求版本一致;
    邏輯複製(logical decoding)才能跨版本、跨系統——做 CDC 要用邏輯的。

🔗 相關工具