什麼是 WAL
WAL(Write Ahead Log)預寫日誌,是資料庫系統中常見的一種手段,用於保證資料操作的原子性和永續性。
在電腦科學中,預寫式日誌(Write-ahead logging,縮寫 WAL)是關聯式資料庫系統中用於提供原子性和永續性(ACID 屬性中的兩個)的一系列技術。在使用 WAL 的系統中,所有的修改在提交之前都要先寫入 log 檔案中。
log 檔案中通常包括 redo 和 undo 資訊。這樣做的目的可以通過一個例子來說明。假設一個程式在執行某些操作的過程中機器掉電了。在重新啟動時,程式可能需要知道當時執行的操作是成功了還是部分成功或者是失敗了。如果使用了 WAL,程式就可以檢查 log 檔案,並對突然掉電時計劃執行的操作內容跟實際上執行的操作內容進行比較。在這個比較的基礎上,程式就可以決定是撤銷已做的操作還是繼續完成已做的操作,或者是保持原樣。
WAL 允許用 in-place 方式更新資料庫。另一種用來實現原子更新的方法是 shadow paging,它並不是 in-place 方式。用 in-place 方式做更新的主要優點是減少索引和塊列表的修改。ARIES 是 WAL 系列技術常用的演算法。在檔案系統中,WAL 通常稱為 journaling。PostgreSQL 也是用 WAL 來提供 point-in-time 恢復和資料庫複製特性。
備份
我們想一想,如果想保證對一個資料的操作可以恢復。可以怎麼做?你不用去想資料庫是怎麼實現的,也不用想太高深。其實這是一個很簡單的問題,我們常常在處理這種問題。最簡單的方法其實就是備份一份資料:當我需要對一條資料做更新操作前,先將這條資料備份在一個地方,然後去更新,如果更新失敗,可以從備份資料中回寫回來。這樣就可以保證事務的回滾,就可以保證資料操作的原子性了。其實 SQLite 引入 WAL 之前就是通過這種方式來實現原子事務,稱之為 rollback journal, rollback journal 機制的原理是:在修改資料庫檔案中的資料之前,先將修改所在分頁中的資料備份在另外一個地方,然後才將修改寫入到資料庫檔案中;如果事務失敗,則將備份資料拷貝回來,撤銷修改;如果事務成功,則刪除備份資料,提交修改。
WAL
再繼續上面的問題?如何做到資料的可恢復(原子性)和提交成功的資料被持久化到磁碟(永續性)?另一種機制就是WAL,WAL 機制的原理也很簡單:修改並不直接寫入到資料庫檔案中,而是寫入到另外一個稱為 WAL 的檔案中;如果事務失敗,WAL 中的記錄會被忽略,撤銷修改;如果事務成功,它將在隨後的某個時間被寫回到資料庫檔案中,提交修改。
WAL 的優點
- 讀和寫可以完全地併發執行,不會互相阻塞(但是寫之間仍然不能併發)。
- WAL 在大多數情況下,擁有更好的效能(因為無需每次寫入時都要寫兩個檔案)。
- 磁碟 I/O 行為更容易被預測。
- 使用更少的 fsync()操作,減少系統脆弱的問題。
提升效能
我們都知道,資料庫的最大效能挑戰就是磁碟的讀寫,許多先輩在提供資料儲存效能上絞盡腦汁,提出和實驗了一套又一套方法。其實所有方案最終總結出來就三種:隨機讀寫改順序讀寫、緩衝單條讀寫改批量讀寫、單執行緒讀寫改併發讀寫。WAL 其實也是這兩種思路的一種實現,一方面 WAL 中記錄事務的更新內容,通過 WAL 將隨機的髒頁寫入變成順序的日誌刷盤,另一方面,WAL 通過 buffer 的方式改單條磁碟刷入為緩衝批量刷盤,再者從 WAL 資料到最終資料的同步過程中可以採用併發同步的方式。這樣極大提升資料庫寫入效能,因此,WAL 的寫入能力決定了資料庫整體效能的上限,尤其是在高併發時。
checkpoint
上面講到,使用 WAL 的資料庫系統不會再每新增一條 WAL 日誌就將其刷入資料庫檔案中,一般積累一定的量然後批量寫入,通常使用頁為單位,這是磁碟的寫入單位。 同步 WAL 檔案和資料庫檔案的行為被稱為 checkpoint(檢查點),一般在 WAL 檔案積累到一定頁數修改的時候;當然,有些系統也可以手動執行 checkpoint。執行 checkpoint 之後,WAL 檔案可以被清空,這樣可以保證 WAL 檔案不會因為太大而效能下降。
有些資料庫系統讀取請求也可以使用 WAL,通過讀取 WAL 最新日誌就可以獲取到資料的最新狀態。
具體實現
常見的資料庫一般都會用到 WAL 機制,只是不同的系統說法和實現可能有所差異。mysql、sqlite、postgresql、etcd、hbase、zookeeper、elasticsearch 等等都有自己的實現。
mysql
mysql 的 WAL,大家可能都比較熟悉。mysql 通過 redo、undo 日誌實現 WAL。redo log 稱為重做日誌,每當有操作時,在資料變更之前將操作寫入 redo log,這樣當發生掉電之類的情況時系統可以在重啟後繼續操作。undo log 稱為撤銷日誌,當一些變更執行到一半無法完成時,可以根據撤銷日誌恢復到變更之間的狀態。mysql 中用 redo log 來在系統 Crash 重啟之類的情況時修復資料(事務的永續性),而 undo log 來保證事務的原子性。
zookeeper
和大多數分散式系統一樣,ZooKeeper 也有 WAL(Write-Ahead-Log),對於每一個更新操作,ZooKeeper 都會先寫 WAL, 然後再對記憶體中的資料做更新,然後向 Client 通知更新結果。另外,ZooKeeper 還會定期將記憶體中的目錄樹進行 Snapshot,落地到磁碟上。這麼做的主要目的,一當然是資料的持久化,二是加快重啟之後的恢復速度,如果全部通過 Replay WAL 的形式恢復的話,會比較慢。
elasticsearch
如果沒有用 fsync
把資料從檔案系統快取刷(flush)到硬碟,elasticsearch 不能保證資料在斷電甚至是程式正常退出之後依然存在。為了保證可靠性,需要確保資料變化被持久化到磁碟。
在動態更新索引時,elasticsearch 說一次完整的提交會將段刷到磁碟,並寫入一個包含所有段列表的提交點。Elasticsearch 在啟動或重新開啟一個索引的過程中使用這個提交點來判斷哪些段隸屬於當前分片。
即使通過每秒重新整理(refresh)實現了近實時搜尋,elasticsearch 仍然需要經常進行完整提交來確保能從失敗中恢復。但在兩次提交之間發生變化的文件怎麼辦?
Elasticsearch 增加了一個 translog ,或者叫事務日誌,在每一次對 Elasticsearch 進行操作時均進行了日誌記錄。
etcd
用過 etcd 的同學可能會發現,etcd 的資料目錄下有兩個子目錄wal
和snap
。它們的作用就是實現 WAL 機制用的。
wal
: 存放預寫式日誌,最大的作用是記錄了整個資料變化的全部歷程。在 etcd 中,所有資料的修改在提交前,都要先寫入到 WAL 中。
snap
: 存放快照資料,etcd 防止 WAL 檔案過多而設定的快照,儲存 etcd 資料狀態。
WAL 機制使得 etcd 具備了以下兩個功能:
- 故障快速恢復: 當你的資料遭到破壞時,就可以通過執行所有 WAL 中記錄的修改操作,快速從最原始的資料恢復到資料損壞前的狀態。
- 資料回滾(undo)/重做(redo):因為所有的修改操作都被記錄在 WAL 中,需要回滾或重做,只需要方向或正向執行日誌中的操作即可
hbase
hbase 實現 WAL 的方法將 HLog,hbase 的 RegionServer 會將資料儲存在記憶體中(MemStore),直到滿足一定條件,將其 flush 到磁碟上。這樣可以避免建立很多小檔案。記憶體儲存是不穩定的,HBase 也是使用 WAL 來解決這個問題:每次更新操作都會寫日誌,並且寫日誌和更新操作在一個事務中。
推薦系列
Mysql 大表問題和解決
Mysql 主鍵問題
列式儲存
時間序列資料庫(TSDB)初識與選擇
十分鐘瞭解 Apache Druid
Apache Druid 底層儲存設計
Apache Druid 的叢集設計與工作流程
想了解更多資料儲存相關知識,請關注我的公眾號。