原文出處:https://bohutang.me/2020/08/18/clickhouse-and-friends-merge-tree-wal/git
最後更新: 2020-09-18github
數據庫系統爲了提升寫入性能,會把數據先寫到內存,等「攢」到必定程度後再回寫到磁盤,好比 MySQL 的 buffer pool 機制。數據庫
由於數據先寫到內存,爲了數據的安全性,咱們須要一個 Write-Ahead Log (WAL) 來保證內存數據的安全性。安全
今天咱們來看看 ClickHouse 新增的 MergeTreeWriteAheadLog (https://github.com/ClickHouse/ClickHouse/pull/8290) 模塊,它到底解決了什麼問題。性能
高頻寫問題
對於 ClickHouse MergeTree 引擎,每次寫入(即便1條數據)都會在磁盤生成一個分區目錄(part),等着 merge 線程合併。優化
若是有多個客戶端,每一個客戶端寫入的數據量較少、次數較頻繁的狀況下,就會引起 DB::Exception: Too many parts
錯誤。spa
這樣就對客戶端有必定的要求,好比須要作 batch 寫入。線程
或者,寫入到 Buffer 引擎,定時的刷回 MergeTree,缺點是在宕機時可能會丟失數據。code
MergeTree WAL
1. 默認模式
咱們先看看在沒有 WAL 狀況下,MergeTree 是如何寫入的:內存
每次寫入 MergeTree 都會直接在磁盤上建立分區目錄,並生成分區數據,這種模式其實就是 WAL + 數據的融合。
很顯然,這種模式不適合頻繁寫操做的狀況,不然會生成很是多的分區目錄和文件,引起 Too many parts
錯誤。
2. WAL模式
設置SETTINGS: min_rows_for_compact_part=2
,分別執行2條寫 SQL,數據會先寫到 wal.bin 文件:
當知足 min_rows_for_compact_part=2
後,merger 線程觸發合併操做,生成 1_1_2_1
分區,也就是完成了 wal.bin 裏的 1_1_1_0
和 1_2_2_0
兩個分區的合併操做。當咱們執行第三條 SQL 寫入:
insert into default.mt(a,b,c) values(1,3,3)
數據塊(分區)會繼續追加到 wal.bin 尾部:
此時,3 條數據分佈在兩個地方:分區 1_1_2_1
, wal.bin 裏的 1_3_3_0
。
這樣就有一個問題:當咱們執行查詢的時候,數據是怎麼合併的呢?
MergeTree 使用全局結構 data_parts_indexes
維護分區信息,當服務啓動的時候,MergeTreeData::loadDataParts
方法:
1. data_parts_indexes.insert(1_1_2_1) 2. 讀取 wal.bin,經過 getActiveContainingPart 判斷分區是否已經merge到磁盤:1_1_1_0 已經存在, 1_2_2_0 已經存在,data_parts_indexes.insert(1_3_3_0) 3. data_parts_indexes:{1_1_2_1,1_3_3_0}
這樣,它老是能維護全局的分區信息。
總結
WAL 功能在 PR#8290 (https://github.com/ClickHouse/ClickHouse/pull/8290) 實現,master 分支已經默認開啓。
MergeTree 經過 WAL 來保護客戶端的高頻、少許寫機制,減小服務端目錄和文件數量,讓客戶端操做盡量簡單、高效。
全文完。
Enjoy ClickHouse :)
葉老師的「MySQL核心優化」大課已升級到MySQL 8.0,掃碼開啓MySQL 8.0修行之旅吧