ClickHouse各種MergeTree的關係與作用
- 2020 年 3 月 26 日
- 筆記
在ClickHouse的整個體系裡面,MergeTree表引擎絕對是一等公民,使用ClickHouse就是在使用MergeTree,這種說法一點也不為過。
眾所周知,MergeTree表引擎是一個家族系列,目前整個系列一共包含了14種不同類型的MergeTree,可謂是功能豐富了吧?
但凡事都有兩面性,功能豐富的同時也無疑讓很多朋友犯了難。
這麼多表引擎,它們之間是什麼關係?
我們到底應該使用哪一種表引擎?
今天我就用這篇文章,嘗試回答上述兩個高頻問題。
老師常教導我們要訓練結構化思維,通過抽象、歸納等辦法來分析一個事物,有時候會起到事半功倍的效果。
這麼多表引擎,它們之間是什麼關係?
我們可以使用兩種關係,來理解整個MergeTree系列:
- 繼承關係
首先,為了便於理解,可以使用繼承關係來看待MergeTree。通過最基礎的MergeTree表引擎,向下派生出6個變種表引擎,如下圖所示

在ClickHouse底層具體的實現方法中,上述7種表引擎的區別主要體現在Merge合併的邏輯部分。如下圖所示,是我簡化後的對象關係:

可以看到,在具體的實現邏輯部分,7種MergeTree共用一個主體,在觸發Merge動作時,調用了各自獨有的合併邏輯。
而除開MergeTree之外的其他6個變種表引擎,它們的Merge合併邏輯,全部是建立在MergeTree基礎之上的,如下圖所示:

它們均繼承於MergeTree的 MergingSortedBlockInputStream。
MergingSortedBlockInputStream 的主要作用,是按照ORDER BY的規則保持新分區數據的有序性。
而其他6種變種MergeTree的合併邏輯,則是在有序的基礎之上 "各有所長",要麼是將排序後相鄰的重複數據消除、亦或是將它們累加匯總。
所以,從繼承關係的角度來理解,我們不僅明白了這7種MergeTree的關係,也進一步明確了一個事實,它們的主要區別在Merge部分的邏輯,所以特殊功能只會在Merge合併時才會觸發。
- 組合關係
剛才已經介紹了7種MergeTree的關係,餘下的7種是ReplicatedMergeTree系列。
ReplicatedMergeTree與普通的MergeTree又有什麼區別呢? 我們接著看下面這張圖:

圖中的虛線框部分是MergeTree的能力邊界,而ReplicatedMergeTree在它的基礎之上增加了分散式協同的能力。
藉助ZooKeeper的消息日誌廣播,實現了副本實例之間的數據同步功能。
ReplicatedMergeTree系列可以用組合關係來理解,如下圖所示:

當我們為7種MergeTree加上Replicated前綴後,又能組合出7種新的表引擎,這些ReplicatedMergeTree擁有副本協同的能力。
我們到底應該使用哪一種表引擎?
現在回答第二個問題,按照使用的場景劃分,可以將上述14種表引擎大致分成以下6類應用場景:
- 默認情況
在沒有特殊要求的場合,使用基礎的MergeTree表引擎即可,它不僅擁有高效的性能,也提供了所有MergeTree共有的基礎功能,包括列存、數據分區、分區索引、一級索引、二級索引、TTL、多路徑存儲等等。

與此同時,它也定義了整個MergeTree家族的基調,例如:
ORDER BY 決定了每個分區中數據的排序規則;
PRIMARY KEY 決定了一級索引(primary.idx);
ORDER BY 可以指代PRIMARY KEY, 通常只用聲明ORDER BY 即可。
接下來將要介紹的其他表引擎,除開ReplicatedMergeTree系列外,都是在Merge合併動作時添加了各自獨有的邏輯。
- 數據去重
通過剛才的說明,大家應該明白,MergeTree的主鍵(PRIMARY KEY)只是用來生成一級索引(primary.idx)的,並沒有唯一性約束這樣的語義。
一些朋友在使用MergeTree的時候,用傳統資料庫的思維來理解MergeTree就會出現問題。
如果業務上不允許數據重複,遇到這類場景就可以使用ReplacingMergeTree,如下圖所示:

ReplacingMergeTree通過ORDER BY,表示判斷唯一約束的條件。當分區合併之時,根據ORDER BY排序後,相鄰重複的數據會被排除。
由此,可以得出幾點結論:
第一,使用ORDER BY作為特殊判斷標識,而不是PRIMARY KEY。關於這一點網上有一些誤傳,但是如果理解了ORDER BY與PRIMARY KEY的作用,以及合併邏輯之後,都能夠推理出應該是由ORDER BY決定。
ORDER BY的作用, 負責分區內數據排序;
PRIMARY KEY的作用, 負責一級索引生成;
Merge的邏輯, 分區內數據排序後,找到相鄰的數據,做特殊處理。
第二,只有在觸發合併之後,才能觸發特殊邏輯。以去重為例,在沒有合併的時候,還是會出現重複數據。
第三,只對同一分區內的數據有效。以去重為例,只有屬於相同分區的數據才能去重,跨越不同分區的重複數據不能去重。
上述幾點結論,適用於包含ReplacingMergeTree在內的6種MergeTree,所以後面不在贅述。
- 預聚合(數據立方體)
有這麼一類場景,它的查詢主題是非常明確的,也就是說聚合查詢的維度欄位是固定,並且沒有明細數據的查詢需求,這類場合就可以使用SummingMergeTree或是AggregatingMergeTree,如下圖所示:

可以看到,在新分區合併後,在同一分區內,ORDER BY條件相同的數據會進行合併。如此一來,首先表內的數據行實現了有效的減少,其次度量值被預先聚合,進一步減少了後續計算開銷。
聚合類MergeTree通常可以和表引擎協同使用,如下圖所示:

可以將物化視圖設置成聚合類MergeTree,將其作為固定主題的查詢表使用。
值得一提的是,通常只有在使用SummingMergeTree或AggregatingMergeTree的時候,才需要同時設置ORDER BY與PRIMARY KEY。
顯式的設置PRIMARY KEY,是為了將主鍵和排序鍵設置成不同的值,是進一步優化的體現。
例如某個場景的查詢需求如下:
聚合條件,GROUP BY A,B,C
過濾條件,WHERE A
此時,如下設置將會是一種較優的選擇:
GROUP BY A,B,C
PRIMARY KEY A
BTW,如果ORDER BY與PRIMARY KEY不同,PRIMARY KEY必須是ORDER BY的前綴(為了保證分區內數據和主鍵的有序性)。
- 數據更新
數據的更新在ClickHouse中有多種實現手段,例如按照分區Partition重新寫入、使用Mutation的DELETE和UPDATE查詢。
使用CollapsingMergeTree或VersionedCollapsingMergeTree也能實現數據更新,這是一種使用標記位,以增代刪的數據更新方法,如下圖所示:

通過增加一個標誌欄位(例如圖中的sigh欄位),作為數據有效性的判斷依據。
可以看到,在新分區合併後,在同一分區內,ORDER BY條件相同的數據,其標誌值為1和-1的數據行會進行抵消。
下圖是另外一種便於理解的視角,就如同擠壓瓦楞紙一般,數據被抵消了:

CollapsingMergeTree和VersionedCollapsingMergeTree的區別又是什麼呢?
CollapsingMergeTree對數據寫入的順序是敏感的,它要求標誌位需要按照正確的順序排序。例如按照1,-1的寫入順序是正確的; 而如果按照-1,1的錯誤順序寫入,CollapsingMergeTree就無法正確抵消。
試想,如果在一個多執行緒並行的寫入場景,我們是無法保證這種順序寫入的,此時就需要使用VersionedCollapsingMergeTree了。
VersionedCollapsingMergeTree在CollapsingMergeTree基礎之上,額外要求指定一個version欄位,在分區Merge合併時,它會自動將version欄位追加到ORERY BY的末尾,從而保證了標誌位的有序性。
ENGINE = VersionedCollapsingMergeTree(sign,ver)ORDER BY id//等效於ORDER BY id,ver
- 監控集成
GraphiteMergeTree可以與Graphite集成,如果你使用了Graphite作為系統的運行監控系統, 則可以通過GraphiteMergeTree存儲指標數據,加速查詢性能、降低存儲成本。
- 高可用
Replicated* 擁有數據副本的能力,如下圖所示:

結合剛才的5類場景,如果進一步需要高可用的需求,選擇一種MergeTree和Replicated組合即可,例如ReplicatedMergeTree、ReplicatedReplacingMergeTree等等。
讀完這篇文章,你是否對ClickHouse的MergeTree家族有了更深刻的認識呢?如果仍有疑問,歡迎觀看我在騰訊雲做的ClickHouse科普直播,地址如下:
在影片中我也專門介紹了ClickHouse的表引擎部分。