天天看點

Clickhouse MergeTree引擎原理[讀書筆記]

文章目錄

      • 概述
      • 建立&使用
      • 檔案結構
        • 目錄結構
        • 分區
          • 分區目錄結構
          • 分區合并的過程
        • 索引
          • 一級索引
          • 二級索引
        • 壓縮資料塊
          • 檔案塊的結構
          • Block合并的判斷依據
        • 資料标記
      • 執行過程
        • 寫入過程
        • 查詢過程
      • MergeTree派生引擎
          • 單副本派生引擎
          • 多副本派生引擎
      • 其他引擎

概述

Clickhouse作為一個資料庫,主要用于管理資料的結構化存儲,優化查詢等操作。

對于引擎的知識點,需要搞明白:
  • 建立方式,使用方式
  • 資料存儲結構
  • 資料查詢原理

ClickHouse擁有非常龐大的表引擎體系,主要包括6大類:MergeTree家族、外部存儲、記憶體、檔案、接口和其他。其中由于合并樹包括ALTER相關操作、主鍵索引、資料分區、資料副本和資料采樣等完整特性,是Clickhouse的核心内容,本文主要梳理MergeTree引擎及其派生引擎的知識點。

MergeTree顧名思義:合并樹,通過合并完成查詢、索引、分區等相關操作。

建立&使用

MergeTree在寫入一批資料時,資料總會以資料片段的形式寫入磁盤,且資料片段不可修改。屬于相同分區的資料片段會定期被合成一個新的片段。這種資料片段往複合并的特點,也正是合并樹名稱的由來。

建表語句:

CREATE TABLE [IF NOT EXISTS] [db_name.]table_name (
    name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],
    省略...) 
ENGINE = MergeTree()
[PARTITION BY expr]
[ORDER BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[SETTINGS name=value, 省略...
           
  • Partition by :分區鍵
  • ORDER BY:排序鍵,組合鍵
  • PRIMARY KEY:主鍵,

    注:主鍵必須是排序鍵的子集

  • SAMPLE BY:抽樣表達式
抽樣表達式需要配合SAMPLE子查詢使用,這項功能對于選取抽樣資料十分有用。

檔案結構

MergeTree是由特殊設計的檔案存儲方式實作的,核心知識點主要包括:

  • 分區
  • 索引
  • 資料壓縮塊
    • 壓縮算法
    • 檔案結構
  • 資料标記

其中,分區作為檔案夾劃分的依據,每個分區獨立分開存儲。每個分區一個獨立的索引檔案,真實資料按列進行存儲,并以塊為機關進行壓縮存儲。資料标記記錄索引記錄和檔案塊的比對關系。

通過上述一套組合拳,在每次發起查詢請求時,能夠最大限度的剔除無效資料,隻讀取有用的資料塊,這也是Clickhouse讀取高效的關鍵。

目錄結構

Clickhouse的原始資料結構:

${clickhouse_data}/db.name/table.name/{分區路徑}/{各類資料}

分區

分區目錄結構

資料是以分區目錄的形式進行組織的,每個分區目錄下包含索引、資料塊等檔案。完整的存儲結構如下:

Clickhouse MergeTree引擎原理[讀書筆記]

一張資料表的完整實體結構分為3個層級,依次是資料表目錄(庫/表)、分區目錄及各分區下具體的資料檔案。接下來就逐一介紹它們的作用。

  • partition:分區目錄,餘下各類資料檔案(primary.idx、[Column].mrk、[Column].bin等)都是以分區目錄的形式被組織存放的,相同分區資料會被合并到同一個分區目錄;
  • checksums.txt:校驗檔案,使用二進制格式存儲。它儲存了餘下各類檔案(primary.idx、count.txt等)的size大小及size的哈希值,用于快速校驗檔案的完整性和正确性。
  • columns.txt:列資訊檔案,使用明文格式存儲。用于儲存此資料分區下的列字段資訊。
  • count.txt:計數檔案,使用明文格式存儲。用于記錄目前資料分區目錄下資料的總行數。
  • primary.idx:一級索引檔案,使用二進制格式存儲。用于存放 稀疏索引 ,借助稀疏索引,在資料查詢的時能夠排除主鍵條件範圍之外的資料檔案;
  • [Column].bin:資料檔案,使用壓縮格式存儲,預設為LZ4壓縮格式,每一個列字段都擁有獨立的.bin資料檔案,并以列字段名稱命名;
  • [Column].mrk:列字段資料标記檔案,使用二進制格式存儲。标記檔案中儲存了.bin檔案中資料的偏移量資訊。标記檔案與稀疏索引對齊,又與.bin檔案一一對應,是以MergeTree通過标記檔案建立了primary.idx稀疏索引與.bin資料檔案之間的映射關系;
  • partition.dat與minmax[Column].idx:如果使用了分區鍵,例如PARTITION BY EventTime,則會額外生成partition.dat與minmax索引檔案,它們均使用二進制格式存儲。partition.dat用于儲存目前分區下分區表達式最終生成的值;而minmax索引用于記錄目前分區下分區字段對應原始資料的最小和最大值;
  • skp_idx_[Column].idx與skp_idx_[Column].mrk:如果在建表語句中聲明了二級索引,則會額外生成相應的二級索引與标記檔案,它們同樣也使用二進制存儲。二級索引在ClickHouse中又稱跳數索引。

**注:**如果不使用分區鍵,即不使用PARTITION BY聲明任何分區表達式,則分區ID預設取名為all,所有的資料都會被寫入這個all分區。

還是有分區概念,隻不過預設為一個分區。

分區目錄名:PartitionID_MinBlockNum_MaxBlockNum_Level

PartitionID:分區值

MinBlockNum和MaxBlockNum:顧名思義,最小資料塊編号與最大資料塊編号。

計數n在單張MergeTree資料表内全局累加,n從1開始,每當新建立一個分區目錄時,計數n就會累積加1。對于一個新的分區目錄而言,MinBlockNum與MaxBlockNum取值一樣,同等于n。

Level:目前合并的層級,分區合并過的次數,也就是“版本号”。

例如:

201905_1_2_0

分區合并的過程

伴随着每一批資料的寫入(一次INSERT語句),MergeTree都會生成一批新的分區目錄。即便不同批次寫入的資料屬于相同分區,也會生成不同的分區目錄。

在之後的某個時刻(寫入後的10~15分鐘,也可以手動執行optimize查詢語句),ClickHouse會通過背景任務再将屬于相同分區的多個目錄合并成一個新的目錄。已經存在的舊分區目錄并不會立即被删除,而是在之後的某個時刻通過背景任務被删除(預設8分鐘)。但是舊的分區目錄已不再是激活狀态(active=0),是以在資料查詢時,它們會被自動過濾掉。

手動觸發合并操作:

合并後的分區目錄:

PartitionID_合并前最小塊号_合并前最大塊号_(原有最大Level+1)

例如:201905_1_2_1和201905_5_5_0合并後:201905_1_5_2

分區名稱變化如下圖所示:

Clickhouse MergeTree引擎原理[讀書筆記]

索引

一級索引

主鍵去重原理:

索引主鍵必須是

Order By

排序字段的子集。

按照Key鍵進行排序,相同的Key鍵隻保留最新一條記錄。預設情況下主鍵(PRIMARYKEY)與排序鍵相同。

MergeTree的主鍵使用PRIMARY KEY定義,待主鍵定義之後,MergeTree會依據index_granularity間隔(預設8192行),為資料表生成一級索引并儲存至primary.idx檔案内,索引資料按照PRIMARY KEY排序。

先排序,再按照指定行進行切分,索引檔案單獨存放。
二級索引

參考一級索引,多個索引字段比對的key值存為一個索引。

MarkRange與索引編号對應,使用start和end兩個屬性表示其區間範圍。通過與start及end對應的索引編号的取值,即能夠得到它所對應的數值區間。而數值區間表示了此MarkRange包含的資料範圍。

壓縮資料塊

核心概念:

  • 壓縮算法
  • 檔案結構
  1. 資料是經過壓縮的,目前支援LZ4、ZSTD、Multiple和Delta幾種算法,預設使用LZ4算法;
  2. 資料會事先依照ORDER BY的聲明排序;
  3. 資料是以壓縮資料塊的形式被組織并寫入.bin檔案中的。
檔案塊的結構

一個壓縮資料塊由頭資訊和壓縮資料兩部分組成。頭資訊固定使用9位位元組表示,具體由1個UInt8(1位元組)整型和2個UInt32(4位元組)整型組成,分别代表使用的壓縮算法類型、壓縮後的資料大小和壓縮前的資料大小。

例如:0x821200065536

0x82是壓縮算法:LZ4的編碼

12000:壓縮後位元組大小

65536:壓縮前位元組大小

Block合并的判斷依據
  • 每次取8192行,size<64KB,等下一批次資料累加(還是8192行);
  • 64KB<=size<=1MB,直接生成一個壓縮塊;
  • size> 1MB,首先按照1MB大小截斷并生成下一個壓縮資料塊,剩餘資料繼續依照上述規則執行。

資料标記

資料标記(.mrk)檔案主要用于比對索引檔案和資料塊檔案。

一行标記資料使用一個元組表示,元組内包含兩個整型數值的偏移量資訊。它們分别表示在此段資料區間内,在對應的.bin壓縮檔案中,壓縮資料塊的起始偏移量;以及将該資料壓縮塊解壓後,其未壓縮資料的起始偏移量。

執行過程

盤點完資料的存儲方式,本節梳理下MergeTree資料在寫入和查詢過程中如何快速定位到資料位置,并且讀取資料,完成計算過程。

寫入過程

  1. 生成分區目錄,伴随着每一批資料的寫入,都會生成一個新的分區目錄。在後續的某一時刻,屬于相同分區的目錄會依照規則合并到一起;
  2. 按照index_granularity索引粒度,會分别生成primary.idx一級索引(如果聲明了二級索引,還會建立二級索引檔案);
  3. 生成每一個列字段的.mrk資料标記;
  4. 生成每一個列字段的.bin壓縮資料檔案(二進制)。

索引和标記區間是對齊的,而标記與壓縮塊則根據區間資料大小的不同,會生成多對一、一對一和一對多三種關系。對應關系依據如下:

  • 多對一:size < 64KB
  • 一對一:64KB<=size<=1MB
  • 一對多:size>1MB

查詢過程

資料查詢的本質,可以看作一個不斷減小資料範圍的過程。在最理想的情況下,MergeTree首先可以依次借助分區索引、一級索引和二級索引,将資料掃描範圍縮至最小。然後再借助資料标記,将需要解壓與計算的資料範圍縮至最小。

MergeTree派生引擎

整個MergeTree家族中,除了基礎表引擎MergeTree之外,還包括MergeTree的派生引擎。

單副本派生引擎

常用的表引擎還有

ReplacingMergeTree、SummingMergeTree、AggregatingMergeTree、CollapsingMergeTree和VersionedCollapsingMergeTree

等。每一種合并樹的變種,在繼承了基礎MergeTree的能力之後,又增加了獨有的特性。子引擎包含MergeTree的壓縮資料塊,列式存儲,資料标記等特性。

它們的所有特殊邏輯,都是在觸發合并的過程中被激活的。

例如:

  • ReplacingMergeTree:合并時,相同key值隻保留一個;
  • SummingMergeTree:相同Key值,自動做Sum求和操作;

派生繼承關系如下圖:

Clickhouse MergeTree引擎原理[讀書筆記]
多副本派生引擎

ReplicatedMergeTree系列:如果給合并樹系列的表引擎加上Replicated字首,又會得到一組支援資料副本的表引擎,例如ReplicatedMergeTree。主要作用是實作表的自動資料副本屬性,和分布式表、資料副本等概念結合使用。和各類MergeTree的派生引擎組合關系如下圖所示:

Clickhouse MergeTree引擎原理[讀書筆記]

其他引擎

其他類型的引擎包括Kafka,HDFS、Mysql等,暫時不在本次讨論範圍。

注:本部落格是《Clickhouse原了解析與應用實踐》MergeTree引擎章節筆記整理。

繼續閱讀