存儲設計——如何優化 ClickHouse 索引(一)

Keypoint

  1. ClickHouse 索引與其他 RDMS 區別
  2. 稀疏主鍵索引及其構建
  3. ClickHouse 索引最佳實踐

ClickHouse 的索引設計

  Whole data:     [---------------------------------------------]
  CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
  Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
  Marks:           |      |      |      |      |      |      |      |      |      |      |
                  a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
  Marks numbers:   0      1      2      3      4      5      6      7      8      9      10

從文件目錄看 ClickHouse 存儲設計

/var/lib/clickhouse
- DataBase
  - Table
    - Parts all/Partition_key xxxx (分區鍵影響parts 數量)
      - checksum.txt (例如因爲同步導致all broken parts to remove錯誤)
      - columns.txt (列及對應格式的數據)
      - count.txt (當前數據塊條數)
      - default_compression_codec.txt(壓縮格式,默認 LZ4)
      - primary.idx 主鍵索引 (可以與 ORDER BY 不同)
      - Column.bin(數據文件,壓縮後可能是 1:6,2:7,3:4....,在 Compact 模式下,只有一個 bin)
      - Column.mk2 (好像都一樣大小,像是 Mark的縮寫?)

從存儲文件夾中可以看到,分級大概是
表 -> Parts -> 主鍵索引(idx)、各個列數據文件(bin)、列數據對應 Mark(mk2) 及其他元數據文件。

bin 文件受主鍵排序及 ORDER BY 鍵以及插入順序所影響。

相同數據受低基數及高基數主鍵排序影響,如下所示,是數據相同但主鍵不同的表的相同 part,高基數排列的表,除 URL 列外,其他兩個列的數據文件大小均比低基數排列的表要大。但實際查詢的時候是否高基數會更快呢?打個問號。

# 高基數排列
drwxr-x--- 13 root       root             416 Nov  8 01:25 .
drwxr-x---  7 clickhouse clickhouse       224 Nov  8 01:34 ..
-rw-r-----  1 root       root             349 Nov  8 01:25 checksums.txt
-rw-r-----  1 root       root              82 Nov  8 01:25 columns.txt
-rw-r-----  1 root       root               7 Nov  8 01:25 count.txt
-rw-r-----  1 root       root              10 Nov  8 01:25 default_compression_codec.txt
-rw-r-----  1 root       root          269624 Nov  8 01:25 IsRobot.bin
-rw-r-----  1 root       root           21624 Nov  8 01:25 IsRobot.mrk2
-rw-r-----  1 root       root           75638 Nov  8 01:25 primary.idx
-rw-r-----  1 root       root       120469184 Nov  8 01:25 URL.bin
-rw-r-----  1 root       root           21624 Nov  8 01:25 URL.mrk2
-rw-r-----  1 root       root         9964518 Nov  8 01:25 UserID.bin
-rw-r-----  1 root       root           21624 Nov  8 01:25 UserID.mrk2


# 低基數排列
drwxr-x--- 13 root       root             416 Nov  8 01:34 .
drwxr-x---  8 clickhouse clickhouse       256 Nov  8 01:45 ..
-rw-r-----  1 root       root             348 Nov  8 01:34 checksums.txt
-rw-r-----  1 root       root              82 Nov  8 01:34 columns.txt
-rw-r-----  1 root       root               7 Nov  8 01:34 count.txt
-rw-r-----  1 root       root              10 Nov  8 01:34 default_compression_codec.txt
-rw-r-----  1 root       root           32717 Nov  8 01:34 IsRobot.bin
-rw-r-----  1 root       root           21528 Nov  8 01:34 IsRobot.mrk2
-rw-r-----  1 root       root           77519 Nov  8 01:34 primary.idx
-rw-r-----  1 root       root       158361024 Nov  8 01:34 URL.bin
-rw-r-----  1 root       root           21528 Nov  8 01:34 URL.mrk2
-rw-r-----  1 root       root          785254 Nov  8 01:34 UserID.bin
-rw-r-----  1 root       root           21528 Nov  8 01:34 UserID.mrk2

隱含的 Granule —— 併發的最小讀取單位

mk2 是一眼看不出內容的文件,不同列的文件大小(幾乎)相同。

在創建表時經常看到一個設置, granule=8192,但在存儲文件中似乎沒有與之相關的內容。這個 Granule 實際上就與 mk2 綁定,而 idx 大小也和它掛鉤

ClickHouse 藉由二分索引對主鍵索引數據塊進行搜索,跳過不需要的數據組,這個組就是隱含的 Granule。

默認一個 Granule 由 8192 行數據組成。每 8192 行就有一個主鍵索引作爲數據入口。

爲了把主鍵完整地放入主內存,要求主鍵必須足夠小,因此有時候會獨立定義一個主鍵和 ORDER BY 鍵(主鍵必須是 ORDER BY 鍵的前綴)。

一個 887 萬的數據主鍵索引只有 1083 個索引值。MySQL 則必須將主鍵全部索引,當然,好處是它可以藉助 B 樹索引到某條記錄,而在併發讀取數據時, ClickHouse 則至少需要讀 8190 行。

除最後一列外,每個 Granule 的主鍵索引皆爲該組合列的最小值,使之單調遞增,但未必指向具體的行。如下圖所示,UserID 與 URL 構成的主鍵並不在同一行。

-- Granule0, Mark 0
240.923,goal://metry=10000467796a411...
-- Granule1, Mark1
4073,710,goal://dream...

標記主鍵 idx——ClickHouse 的排除法

假設先按 UserID 再按 URL 排序,這表示 UserID 這一列的數據是 100% 按字母順序(lexicographical Ordering)單調遞增的,240.923 就是所有 UserID 數據中最小的

但 URL 作爲次一級,只能保證在該 Granule 裏是最小的,例如 Granule 0裏的 goal://metry=10000467796a411,其他 Granule 不能保證。

但是是否意味着只能在同一個 Granule 中做排除呢?也不是,如果 Granule0-3 都是相同 UserID,那麼同樣可以確認 goal://metry=10000467796a411 是這三個塊裏面最小的,查詢小於這個值時,三個皆可排除

這裏你可能看到了優化的曙光,後文將詳談。

Mk2——數據的“指針”

-- Column.mk2 --
-- 壓縮後的數據塊位置(bin)
-- 解壓後的數據塊位置
block_offset,granule_offset

標記文件是實際數據的“指針”,分別指向解壓前後的具體數據塊位置,前面提到 Granule 是最小併發讀取單位,這裏可以更明確這一概念:

  • Stage 1:ClickHouse 通過主鍵索引排除 Granule
  • Stage 2:未被排除的 Granule 根據編號在 mk2 中找到對應的數據塊解壓獲取數據,由於數據是壓縮的,解壓本身似乎暗含了按塊讀取的必然性。

mk2 的設計也符合列式數據庫的設計方式,方便刪除單列(主鍵除外)而不需要修改索引,需要讀取特定列的數據再讀以減少內存佔用。

而同時也說明一個常見的查詢“偷懶”習慣 SELECT * 危害之大:
越多的列被選中,則需要打開的文件解壓的數據越多,如果是一個數據量大的列(例如響應體)勢必需要消耗更多的內存和 CPU。

下圖是查詢某用戶的 Top 10 URL 訪問的數據讀取圖

SELECT URL, count(URL) AS Count
FROM hits_UserID_URL
WHERE UserID = 749927693
GROUP BY URL
ORDER BY Count DESC
LIMIT 10;

Ref: Block In ClickHouse

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章