ClickHouse 存算分離架構(gòu)探索

背景

ClickHouse 作為開源 OLAP 引擎,因其出色的性能表現(xiàn)在大數(shù)據(jù)生態(tài)中得到了廣泛的應(yīng)用。區(qū)別于 Hadoop 生態(tài)組件通常依賴 HDFS 作為底層的數(shù)據(jù)存儲,ClickHouse 使用本地盤來自己管理數(shù)據(jù),官方推薦使用 SSD 作為存儲介質(zhì)來提升性能。但受限于本地盤的容量上限以及 SSD 盤的價格,用戶很難在容量、成本和性能這三者之間找到一個好的平衡。JuiceFS 的某個客戶近期就遇到了這樣的難題,希望將 ClickHouse 中的溫冷數(shù)據(jù)從 SSD 盤遷移到更大容量、更低成本的存儲介質(zhì),更好地支撐業(yè)務(wù)查詢更長時間數(shù)據(jù)的需求。

JuiceFS 是基于對象存儲實現(xiàn)并完全兼容 POSIX 的開源分布式文件系統(tǒng),同時 JuiceFS 的數(shù)據(jù)緩存特性可以智能管理查詢熱點數(shù)據(jù),非常適合作為 ClickHouse 的存儲系統(tǒng),下面將詳細介紹這個方案。

MergeTree 存儲格式簡介

在介紹具體方案之前先簡單了解一下 MergeTree 的存儲格式。MergeTree 是 ClickHouse 最主要使用的存儲引擎,當創(chuàng)建表時可以通過 PARTITION BY 語句指定以某一個或多個字段作為分區(qū)字段,數(shù)據(jù)在磁盤上的目錄結(jié)構(gòu)類似如下形式:

$ ls -l /var/lib/clickhouse/data/<database>/<table>
drwxr-xr-x  2 test  test    64B Mar  8 13:46 202102_1_3_0
drwxr-xr-x  2 test  test    64B Mar  8 13:46 202102_4_6_1
drwxr-xr-x  2 test  test    64B Mar  8 13:46 202103_1_1_0
drwxr-xr-x  2 test  test    64B Mar  8 13:46 202103_4_4_0

202102_1_3_0 為例,202102 是分區(qū)的名稱,1 是最小的數(shù)據(jù)塊編號,3 是最大的數(shù)據(jù)塊編號,0 是 MergeTree 的深度??梢钥吹?202102 這個分區(qū)不止一個目錄,這是因為 ClickHouse 每次在寫入的時候都會生成一個新的目錄,并且一旦寫入以后就不會修改(immutable)。每一個目錄稱作一個「part」,當 part 逐漸變多以后 ClickHouse 會在后臺對多個 part 進行合并(compaction),通常的建議是不要保留過多 part,否則會影響查詢性能。

每個 part 目錄內(nèi)部又由很多大大小小的文件組成,這里面既有數(shù)據(jù),也有一些元信息,一個典型的目錄結(jié)構(gòu)如下所示:

$ ls -l /var/lib/clickhouse/data/<database>/<table>/202102_1_3_0
-rw-r--r--  1 test  test     ?? Mar  8 14:06 ColumnA.bin
-rw-r--r--  1 test  test     ?? Mar  8 14:06 ColumnA.mrk
-rw-r--r--  1 test  test     ?? Mar  8 14:06 ColumnB.bin
-rw-r--r--  1 test  test     ?? Mar  8 14:06 ColumnB.mrk
-rw-r--r--  1 test  test     ?? Mar  8 14:06 checksums.txt
-rw-r--r--  1 test  test     ?? Mar  8 14:06 columns.txt
-rw-r--r--  1 test  test     ?? Mar  8 14:06 count.txt
-rw-r--r--  1 test  test     ?? Mar  8 14:06 minmax_ColumnC.idx
-rw-r--r--  1 test  test     ?? Mar  8 14:06 partition.dat
-rw-r--r--  1 test  test     ?? Mar  8 14:06 primary.idx

其中比較重要的文件有:

primary.idx:這個文件包含的是主鍵信息,但不是當前 part 全部行的主鍵,默認會按照 8192 這個區(qū)間來存儲,也就是每 8192 行存儲一次主鍵。
ColumnA.bin:這是壓縮以后的某一列的數(shù)據(jù),ColumnA 只是這一列的代稱,實際情況會是真實的列名。壓縮是以 block 作為最小單位,每個 block 的大小從 64KiB 到 1MiB 不等。
ColumnA.mrk:這個文件保存的是對應(yīng)的 ColumnA.bin 文件中每個 block 壓縮后和壓縮前的偏移。
partition.dat:這個文件包含的是經(jīng)過分區(qū)表達式計算以后的分區(qū) ID。
minmax_ColumnC.idx:這個文件包含的是分區(qū)字段對應(yīng)的原始數(shù)據(jù)的最小值和最大值。

基于 JuiceFS 的存算分離方案

因為 JuiceFS 完全兼容 POSIX,所以可以把 JuiceFS 掛載的文件系統(tǒng)直接作為 ClickHouse 的磁盤來使用。這種方案下數(shù)據(jù)會直接寫入 JuiceFS,結(jié)合為 ClickHouse 節(jié)點配置的緩存盤,查詢時涉及的熱數(shù)據(jù)會自動緩存在 ClickHouse 節(jié)點本地。整體方案如下圖所示。

ClickHouse 在寫入時會產(chǎn)生大量的小文件,因此如果寫入壓力較大這個方案對寫入和查詢性能都會有一定影響。建議在寫入數(shù)據(jù)時增大寫入緩存,盡量一次寫入更多數(shù)據(jù)來避免這個小文件過多的問題。最簡單的做法是使用 ClickHouse 的 Buffer 表,基本上不需要修改應(yīng)用代碼就可以解決小文件過多的問題,適合當 ClickHouse 宕機時允許少量數(shù)據(jù)丟失的場景。這樣做的好處是存儲和計算完全分離,ClickHouse 節(jié)點完全無狀態(tài),如果節(jié)點故障可以很快恢復(fù),不涉及任何數(shù)據(jù)拷貝。未來可以讓 ClickHouse 感知到底層存儲是共享的,實現(xiàn)自動的無數(shù)據(jù)拷貝遷移。

同時由于 ClickHouse 通常應(yīng)用在實時分析場景,這個場景對于數(shù)據(jù)實時更新的要求比較高,在分析時也需要經(jīng)常性地查詢新數(shù)據(jù)。因此數(shù)據(jù)具有比較明顯的冷熱特征,即一般新數(shù)據(jù)是熱數(shù)據(jù),隨著時間推移歷史數(shù)據(jù)逐漸變?yōu)槔鋽?shù)據(jù)。利用 ClickHouse 的存儲策略(storage policy)來配置多塊磁盤,通過一定條件可以實現(xiàn)自動遷移冷數(shù)據(jù)到 JuiceFS。整體方案如下圖所示。

這個方案中數(shù)據(jù)會先寫入本地磁盤,當滿足一定條件時 ClickHouse 的后臺線程會異步把數(shù)據(jù)從本地磁盤遷移到 JuiceFS 上。和第一個方案一樣,查詢時也會自動緩存熱數(shù)據(jù)。注意圖中為了區(qū)分寫和讀因此畫了兩塊磁盤,實際使用中沒有這個限制,可以使用同一個盤。雖然這個方案不是完全的存儲計算分離,但是可以滿足對寫入性能要求特別高的場景需求,也保留一定的存儲資源彈性伸縮能力。下面會詳細介紹這個方案在 ClickHouse 中如何配置。

ClickHouse 支持配置多塊磁盤用于數(shù)據(jù)存儲,下面是示例的配置文件:

<storage_configuration>
    <disks>
        <jfs>
            <path>/jfs</path>
        </jfs>
    </disks>
</storage_configuration>

上面的 /jfs 目錄即是 JuiceFS 文件系統(tǒng)掛載的路徑。在把以上配置添加到 ClickHouse 的配置文件中,并成功掛載 JuiceFS 文件系統(tǒng)以后,就可以通過 MOVE PARTITION 命令將某個 partition 移動到 JuiceFS 上,例如:

ALTER TABLE test MOVE PARTITION 'xxx' TO DISK 'jfs';

當然這種手動移動的方式只是用于測試,ClickHouse 支持通過配置存儲策略的方式來將數(shù)據(jù)自動從某個磁盤移動到另一個磁盤。下面是示例的配置文件:

<storage_configuration>
    <disks>
        <jfs>
            <path>/jfs</path>
        </jfs>
    </disks>
    <policies>
        <hot_and_cold>
            <volumes>
                <hot>
                    <disk>default</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                </hot>
                <cold>
                    <disk>jfs</disk>
                </cold>
            </volumes>
            <move_factor>0.1</move_factor>
        </hot_and_cold>
    </policies>
</storage_configuration>

上面的配置文件中有一個名為 hot_and_cold 的存儲策略,其中定義了兩個 volume,名為 hot 的 volume 是默認的 SSD 盤,名為 cold 的 volume 即是上一步 disks 中定義的 JuiceFS 盤。這些 volume 在配置文件中的順序很重要,數(shù)據(jù)會首先存儲到第一個 volume 中,而 max_data_part_size_bytes 這個配置表示當數(shù)據(jù) part 超過指定的大小時(示例中是 1GiB)自動從當前 volume 移動到下一個 volume,也就是把數(shù)據(jù)從 SSD 盤移動到 JuiceFS。最后的 move_factor 配置表示當 SSD 盤的磁盤容量超過 90% 時也會觸發(fā)數(shù)據(jù)移動到 JuiceFS。

最后在創(chuàng)建表時需要顯式指定要用到的存儲策略:

CREATE TABLE test (
  ...
) ENGINE = MergeTree
...
SETTINGS storage_policy = 'hot_and_cold';

當滿足數(shù)據(jù)移動的條件時,ClickHouse 就會啟動后臺線程去執(zhí)行移動數(shù)據(jù)的操作,默認會有 8 個線程同時工作,這個線程數(shù)量可以通過 background_move_pool_size配置調(diào)整。

除了配置存儲策略以外,還可以在創(chuàng)建表時通過 TTL 將超過一段時間的數(shù)據(jù)移動到 JuiceFS 上,例如:

CREATE TABLE test (
  d DateTime,
  ...
) ENGINE = MergeTree
...
TTL d + INTERVAL 1 DAY TO DISK 'jfs'
SETTINGS storage_policy = 'hot_and_cold';

上面的例子是將超過 1 天的數(shù)據(jù)移動到 JuiceFS 上,結(jié)合存儲策略一起可以非常靈活地管理數(shù)據(jù)的生命周期。

寫入性能測試

采用冷熱數(shù)據(jù)分離方案以后數(shù)據(jù)并不會直接寫入 JuiceFS,而是先寫入 SSD 盤,再通過后臺線程異步遷移到 JuiceFS 上。但是我們希望直接評估不同存儲介質(zhì)在寫數(shù)據(jù)的場景有多大的性能差異,因此這里在測試寫入性能時沒有配置冷熱數(shù)據(jù)分離的存儲策略,而是讓 ClickHouse 直接寫入不同的存儲介質(zhì)。

具體測試方法是將真實業(yè)務(wù)中的某一張 ClickHouse 表作為數(shù)據(jù)源,然后使用 INSERT INTO 語句批量插入千萬級行數(shù)的數(shù)據(jù),比較直接寫入 SSD 盤、JuiceFS 以及對象存儲的吞吐。最終的測試結(jié)果如下圖:

以 SSD 盤作為基準,可以看到 JuiceFS 的寫入性能與 SSD 盤有 30% 左右的性能差距,但是相比對象存儲有 11 倍的性能提升。這里 JuiceFS 的測試中開啟了 writeback 選項,這是因為 ClickHouse 在寫入時每個 part 會產(chǎn)生大量的小文件(KiB 級),客戶端采用異步寫入的方式能明顯提升性能,同時大量的小文件對于查詢性能也會造成一定影響。

在了解了直接寫入不同介質(zhì)的性能以后,接下來測試冷熱數(shù)據(jù)分離方案的寫入性能。經(jīng)過實際業(yè)務(wù)測試,基于 JuiceFS 的冷熱數(shù)據(jù)分離方案表現(xiàn)穩(wěn)定,因為新數(shù)據(jù)都是直接寫入 SSD 盤,因此寫入性能與上面測試中的 SSD 盤性能相當。SSD 盤上的數(shù)據(jù)可以很快遷移到 JuiceFS 上,在 JuiceFS 上對數(shù)據(jù) part 進行合并也都是沒有問題的。

查詢性能測試

查詢性能測試使用真實業(yè)務(wù)中的數(shù)據(jù),并選取幾個典型的查詢場景進行測試。其中 q1-q4 是掃描全表的查詢,q5-q7 是命中主鍵索引的查詢。測試結(jié)果如下圖:

可以看到 JuiceFS 與 SSD 盤的查詢性能基本相當,平均差異在 6% 左右,但是對象存儲相比 SSD 盤有 1.4 至 30 倍的性能下降。得益于 JuiceFS 高性能的元數(shù)據(jù)操作以及本地緩存特性,可以自動將查詢請求需要的熱數(shù)據(jù)緩存在 ClickHouse 節(jié)點本地,大幅提升了 ClickHouse 的查詢性能。需要注意的是以上測試中對象存儲是通過 ClickHouse 的 S3 磁盤類型進行訪問,這種方式只有數(shù)據(jù)是存儲在對象存儲上,元數(shù)據(jù)還是在本地磁盤。如果通過類似 S3FS 的方式把對象存儲掛載到本地,性能會有進一步的下降。

在完成基礎(chǔ)的查詢性能測試以后,接下來測試冷熱數(shù)據(jù)分離方案下的查詢性能。區(qū)別于前面的測試,當采用冷熱數(shù)據(jù)分離方案時,并不是所有數(shù)據(jù)都在 JuiceFS 中,數(shù)據(jù)會優(yōu)先寫入 SSD 盤。

首先選取一個固定的查詢時間范圍,評估 JuiceFS 緩存對性能的影響,測試結(jié)果如下圖:

跟固定時間范圍的查詢一樣,從第二次查詢開始因為緩存的建立帶來了 78% 左右的性能提升。不同的地方在于第四次查詢因為涉及到查詢新寫入或者合并后的數(shù)據(jù),而 JuiceFS 目前不會在寫入時緩存大文件,會對查詢性能造成一定影響,之后會提供參數(shù)允許緩存寫入數(shù)據(jù)來改善新數(shù)據(jù)的查詢性能。

總結(jié)

通過 ClickHouse 的存儲策略可以很簡單地將 SSD 和 JuiceFS 結(jié)合使用,實現(xiàn)性能與成本的兩全方案。從寫入和查詢性能測試的結(jié)果上來看 JuiceFS 完全可以滿足 ClickHouse 的使用場景,用戶不必再擔心容量問題,在增加少量成本的情況下輕松應(yīng)對未來幾倍的數(shù)據(jù)增長需求。JuiceFS 目前已經(jīng)支持超過 20 家公有云的對象存儲,結(jié)合完全兼容 POSIX 的特性,不需要改動 ClickHouse 任何一行代碼就可以輕松接入云上的對象存儲。

展望

在當前越來越強調(diào)云原生的環(huán)境下,存儲計算分離已經(jīng)是大勢所趨。ClickHouse 2021 年的 roadmap 上已經(jīng)明確把存儲計算分離作為了主要目標,雖然目前 ClickHouse 已經(jīng)支持把數(shù)據(jù)存儲到 S3 上,但這個實現(xiàn)還比較粗糙。未來 JuiceFS 也會與 ClickHouse 社區(qū)緊密合作共同探索存算分離的方向,讓 ClickHouse 更好地識別和支持共享存儲,實現(xiàn)集群伸縮時不需要做任何數(shù)據(jù)拷貝。

其他:
Elasticsearch 存儲成本省 60%,稿定科技干貨分享

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容