寫前日誌(write-ahead log,WAL)
/ WAL = wawl /
假設你必須更新三個不同的地方,而這次更新只有在三者一起改變時才正確,然而當機可能在任何兩者之間把你攔下。安全的做法是先把完整的計畫安全地寫在一個地方,再去碰那三者中的任何一個。這樣如果你做到一半當機,你可以把計畫讀回來,要麼把它做完、要麼確知它從未開始。那條「行動前先把計畫寫下」的規則就是寫前日誌,它是檔案系統日誌與資料庫持久性兩者底下的引擎。
WAL 的定義性規則就在它的名字裡:描述某變更的日誌條目必須在該變更被就地套用到主資料「之前」抵達穩定儲存。具體而言,你把一筆紀錄(常是新值,即一個 redo 日誌)附加到日誌、強制它寫到磁碟,然後才更新真正的資料結構。日誌是循序寫入的——附加在尾端——這即使在旋轉磁碟上也快,因為避開了隨機尋道。當機後重啟時,復原向前掃描日誌:已完成的交易(帶提交標記者)被重新套用(重做)以使主資料更新到最新,未完成的則被忽略或回復。同樣的概念以 ARIES 出現在資料庫裡、以日誌出現在 ext4 裡、以 PostgreSQL 的 WAL 檔案出現。
為何重要:WAL 正是系統如何在「一次只能寫一個區塊、且隨時可能斷電」的硬體上取得原子的、持久的、全有或全無更新的方式。誠實的代價是:那個持久性承諾完全建立在「對日誌的那一次強制寫入真的落在穩定媒體上」——如果日誌寫入被緩衝在斷電即失的揮發性磁碟快取裡,保證就蒸發了。這正是為何正確的 WAL 實作會發出真正的快取沖刷或使用 FUA(強制單元存取)寫入,以及為何一台會謊報、忽略沖刷的磁碟機可能悄悄地破壞整套機制。
規則: 日誌紀錄落磁碟 早於 就地更新 附加 LOG:「設區塊 941 = X」;對日誌做 fsync/FUA <- 持久化的計畫 然後 套用:把 X 寫到主區域的區塊 941 復原掃描日誌:重做已提交的紀錄、忽略其餘
日誌紀錄在就地變更之前被強制寫到磁碟;復原會重做已提交的紀錄。
WAL 的持久性保證強不過它背後的沖刷。如果那次強制的日誌寫入落在一個斷電即抹除的揮發性磁碟快取裡——或落在一台忽略快取沖刷指令的磁碟機裡——全有或全無的承諾就被悄悄作廢。正確的 WAL 會沖刷或使用 FUA,且只信任能挺過斷電的東西。