天天看點

「資料庫」開源列式資料庫 ClickHouse 深度揭秘

作者:架構思考
ClickHouse 是近年來備受關注的開源列式資料庫,主要用于資料分析(OLAP)領域。目前國内社群火熱,各個大廠紛紛跟進大規模使用。歡迎閱讀~

一、引言

ClickHouse是近年來備受關注的開源列式資料庫,主要用于資料分析(OLAP)領域。目前國内社群火熱,各個大廠紛紛跟進大規模使用:

  • 今日頭條 内部用ClickHouse來做使用者行為分析,内部一共幾千個ClickHouse節點,單叢集最大1200節點,總資料量幾十PB,日增原始資料300TB左右。
  • 騰訊内部用ClickHouse做遊戲資料分析,并且為之建立了一整套監控運維體系。
  • 攜程内部從18年7月份開始接入試用,目前80%的業務都跑在ClickHouse上。每天資料增量十多億,近百萬次查詢請求。
  • 快手内部也在使用ClickHouse,存儲總量大約10PB, 每天新增200TB, 90%查詢小于3S。

在國外,Yandex内部有數百節點用于做使用者點選行為分析,CloudFlare、Spotify等頭部公司也在使用。

特别值得一提的是:國内雲計算的上司廠商 阿裡雲 率先推出了自己的ClickHouse托管産品,産品首頁位址為 雲資料庫ClickHouse,可以點選連結申請參加免費公測,一睹為快!

在社群方面,github star數目增速驚人。

「資料庫」開源列式資料庫 ClickHouse 深度揭秘

在DB-engines排名上,如下圖中紅色曲線所示。ClickHouse開源時間雖短,但是增勢迅猛。

「資料庫」開源列式資料庫 ClickHouse 深度揭秘

為何ClickHouse獲得了如此廣泛的關注,得到了社群的青睐,也得到了諸多大廠的應用呢? 本文嘗試從技術視角進行回答。

二、OLAP場景的特點

2.1 讀多于寫

不同于事務處理(OLTP)的場景,比如電商場景中加購物車、下單、支付等需要在原地進行大量insert、update、delete操作,資料分析(OLAP)場景通常是将資料批量導入後,進行任意次元的靈活探索、BI工具洞察、報表制作等。資料一次性寫入後,分析師需要嘗試從各個角度對資料做挖掘、分析,直到發現其中的商業價值、業務變化趨勢等資訊。這是一個需要反複試錯、不斷調整、持續優化的過程,其中資料的讀取次數遠多于寫入次數。這就要求底層資料庫為這個特點做專門設計,而不是盲目采用傳統資料庫的技術架構。

2.2 大寬表,讀大量行但是少量列,結果集較小

在OLAP場景中,通常存在一張或是幾張多列的大寬表,列數高達數百甚至數千列。對資料分析處理時,選擇其中的少數幾列作為次元列、其他少數幾列作為名額列,然後對全表或某一個較大範圍内的資料做聚合計算。這個過程會掃描大量的行資料,但是隻用到了其中的少數列。而聚合計算的結果集相比于動辄數十億的原始資料,也明顯小得多。

2.3 資料批量寫入,且資料不更新或少更新

OLTP類業務對于延時(Latency)要求更高,要避免讓客戶等待造成業務損失;而OLAP類業務,由于資料量非常大,通常更加關注寫入吞吐(Throughput),要求海量資料能夠盡快導入完成。一旦導入完成,曆史資料往往作為存檔,不會再做更新、删除操作。

2.4 無需事務,資料一緻性要求低

OLAP類業務對于事務需求較少,通常是導入曆史日志資料,或搭配一款事務型資料庫并實時從事務型資料庫中進行資料同步。多數OLAP系統都支援最終一緻性。

2.5 靈活多變,不适合預先模組化

分析場景下,随着業務變化要及時調整分析次元、挖掘方法,以盡快發現資料價值、更新業務名額。而資料倉庫中通常存儲着海量的曆史資料,調整代價十分高昂。預先模組化技術雖然可以在特定場景中加速計算,但是無法滿足業務靈活多變的發展需求,維護成本過高。

三、ClickHouse存儲層

ClickHouse從OLAP場景需求出發,定制開發了一套全新的高效列式存儲引擎,并且實作了資料有序存儲、主鍵索引、稀疏索引、資料Sharding、資料Partitioning、TTL、主備複制等豐富功能。以上功能共同為ClickHouse極速的分析性能奠定了基礎。

3.1 列式存儲

與行存将每一行的資料連續存儲不同,列存将每一列的資料連續存儲。示例圖如下:

「資料庫」開源列式資料庫 ClickHouse 深度揭秘

相比于行式存儲,列式存儲在分析場景下有着許多優良的特性。

1)如前所述,分析場景中往往需要讀大量行但是少數幾個列。在行存模式下,資料按行連續存儲,所有列的資料都存儲在一個block中,不參與計算的列在IO時也要全部讀出,讀取操作被嚴重放大。而列存模式下,隻需要讀取參與計算的列即可,極大的減低了IO cost,加速了查詢。

2)同一列中的資料屬于同一類型,壓縮效果顯著。列存往往有着高達十倍甚至更高的壓縮比,節省了大量的存儲空間,降低了存儲成本。

3)更高的壓縮比意味着更小的data size,從磁盤中讀取相應資料耗時更短。

4)自由的壓縮算法選擇。不同列的資料具有不同的資料類型,适用的壓縮算法也就不盡相同。可以針對不同列類型,選擇最合适的壓縮算法。

5)高壓縮比,意味着同等大小的記憶體能夠存放更多資料,系統cache效果更好。

官方資料顯示,通過使用列存,在某些分析場景下,能夠獲得100倍甚至更高的加速效應。

3.2 資料有序存儲

ClickHouse支援在建表時,指定将資料按照某些列進行sort by。

排序後,保證了相同sort key的資料在磁盤上連續存儲,且有序擺放。在進行等值、範圍查詢時,where條件命中的資料都緊密存儲在一個或若幹個連續的Block中,而不是分散的存儲在任意多個Block, 大幅減少需要IO的block數量。另外,連續IO也能夠充分利用作業系統page cache的預取能力,減少page fault。

3.3 主鍵索引

ClickHouse支援主鍵索引,它将每列資料按照index granularity(預設8192行)進行劃分,每個index granularity的開頭第一行被稱為一個mark行。主鍵索引存儲該mark行對應的primary key的值。

對于where條件中含有primary key的查詢,通過對主鍵索引進行二分查找,能夠直接定位到對應的index granularity,避免了全表掃描進而加速查詢。

但是值得注意的是:ClickHouse的主鍵索引與MySQL等資料庫不同,它并不用于去重,即便primary key相同的行,也可以同時存在于資料庫中。要想實作去重效果,需要結合具體的表引擎ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree實作,我們會在未來的文章系列中再進行詳細解讀。

3.4 稀疏索引

ClickHouse支援對任意列建立任意數量的稀疏索引。其中被索引的value可以是任意的合法SQL Expression,并不僅僅局限于對column value本身進行索引。之是以叫稀疏索引,是因為它本質上是對一個完整index granularity(預設8192行)的統計資訊,并不會具體記錄每一行在檔案中的位置。目前支援的稀疏索引類型包括:

  • minmax: 以index granularity為機關,存儲指定表達式計算後的min、max值;在等值和範圍查詢中能夠幫助快速跳過不滿足要求的塊,減少IO。
  • set(max_rows):以index granularity為機關,存儲指定表達式的distinct value集合,用于快速判斷等值查詢是否命中該塊,減少IO。
  • ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed):将string進行ngram分詞後,建構bloom filter,能夠優化等值、like、in等查詢條件。
  • tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed): 與ngrambf_v1類似,差別是不使用ngram進行分詞,而是通過标點符号進行詞語分割。
  • bloom_filter([false_positive]):對指定列建構bloom filter,用于加速等值、like、in等查詢條件的執行。

3.5 資料Sharding

ClickHouse支援單機模式,也支援分布式叢集模式。在分布式模式下,ClickHouse會将資料分為多個分片,并且分布到不同節點上。不同的分片政策在應對不同的SQL Pattern時,各有優勢。ClickHouse提供了豐富的sharding政策,讓業務可以根據實際需求選用。

1) random随機分片:寫入資料會被随機分發到分布式叢集中的某個節點上。

2) constant固定分片:寫入資料會被分發到固定一個節點上。

3)column value分片:按照某一列的值進行hash分片。

4)自定義表達式分片:指定任意合法表達式,根據表達式被計算後的值進行hash分片。

資料分片,讓ClickHouse可以充分利用整個叢集的大規模并行計算能力,快速傳回查詢結果。

更重要的是,多樣化的分片功能,為業務優化打開了想象空間。比如在hash sharding的情況下,JOIN計算能夠避免資料shuffle,直接在本地進行local join; 支援自定義sharding,可以為不同業務和SQL Pattern定制最适合的分片政策;利用自定義sharding功能,通過設定合理的sharding expression可以解決分片間資料傾斜問題等。

另外,sharding機制使得ClickHouse可以橫向線性拓展,建構大規模分布式叢集,進而具備處理海量資料的能力。

3.6 資料Partitioning

ClickHouse支援PARTITION BY子句,在建表時可以指定按照任意合法表達式進行資料分區操作,比如通過toYYYYMM()将資料按月進行分區、toMonday()将資料按照周幾進行分區、對Enum類型的列直接每種取值作為一個分區等。

資料Partition在ClickHouse中主要有兩方面應用:

  • 在partition key上進行分區裁剪,隻查詢必要的資料。靈活的partition expression設定,使得可以根據SQL Pattern進行分區設定,最大化的貼合業務特點。
  • 對partition進行TTL管理,淘汰過期的分區資料。

3.7 資料TTL

在分析場景中,資料的價值随着時間流逝而不斷降低,多數業務出于成本考慮隻會保留最近幾個月的資料,ClickHouse通過TTL提供了資料生命周期管理的能力。

ClickHouse支援幾種不同粒度的TTL:

1) 列級别TTL:當一列中的部分資料過期後,會被替換成預設值;當全列資料都過期後,會删除該列。

2)行級别TTL:當某一行過期後,會直接删除該行。

3)分區級别TTL:當分區過期後,會直接删除該分區。

3.8 高吞吐寫入能力

ClickHouse采用類LSM Tree的結構,資料寫入後定期在背景Compaction。通過類LSM tree的結構,ClickHouse在資料導入時全部是順序append寫,寫入後資料段不可更改,在背景compaction時也是多個段merge sort後順序寫回磁盤。順序寫的特性,充分利用了磁盤的吞吐能力,即便在HDD上也有着優異的寫入性能。

官方公開benchmark測試顯示能夠達到50MB-200MB/s的寫入吞吐能力,按照每行100Byte估算,大約相當于50W-200W條/s的寫入速度。

3.9 有限支援delete、update

在分析場景中,删除、更新操作并不是核心需求。ClickHouse沒有直接支援delete、update操作,而是變相支援了mutation操作,文法為alter table delete where filter_expr, alter table update col=val where filter_expr。

目前主要限制為删除、更新操作為異步操作,需要背景compation之後才能生效。

3.10 主備同步

ClickHouse通過主備複制提供了高可用能力,主備架構下支援無縫更新等運維操作。而且相比于其他系統它的實作有着自己的特色:

1)預設配置下,任何副本都處于active模式,可以對外提供查詢服務;

2)可以任意配置副本個數,副本數量可以從0個到任意多個;

3)不同shard可以配置不提供副本個數,用于解決單個shard的查詢熱點問題;

四、ClickHouse計算層

ClickHouse在計算層做了非常細緻的工作,竭盡所能榨幹硬體能力,提升查詢速度。它實作了單機多核并行、分布式計算、向量化執行與SIMD指令、代碼生成等多種重要技術。

4.1 多核并行

ClickHouse将資料劃分為多個partition,每個partition再進一步劃分為多個index granularity,然後通過多個CPU核心分别處理其中的一部分來實作并行資料處理。

在這種設計下,單條Query就能利用整機所有CPU。極緻的并行處理能力,極大的降低了查詢延時。

4.2 分布式計算

除了優秀的單機并行處理能力,ClickHouse還提供了可線性拓展的分布式計算能力。ClickHouse會自動将查詢拆解為多個task下發到叢集中,然後進行多機并行處理,最後把結果彙聚到一起。

在存在多副本的情況下,ClickHouse提供了多種query下發政策:

  • 随機下發:在多個replica中随機選擇一個;
  • 最近hostname原則:選擇與目前下發機器最相近的hostname節點,進行query下發。在特定的網絡拓撲下,可以降低網絡延時。而且能夠確定query下發到固定的replica機器,充分利用系統cache。
  • in order:按照特定順序逐個嘗試下發,目前一個replica不可用時,順延到下一個replica。
  • first or random:在In Order模式下,當第一個replica不可用時,所有workload都會積壓到第二個Replica,導緻負載不均衡。first or random解決了這個問題:當第一個replica不可用時,随機選擇一個其他replica,進而保證其餘replica間負載均衡。另外在跨region複制場景下,通過設定第一個replica為本region内的副本,可以顯著降低網絡延時。

4.3 向量化執行與SIMD

ClickHouse不僅将資料按列存儲,而且按列進行計算。傳統OLTP資料庫通常采用按行計算,原因是事務進行中以點查為主,SQL計算量小,實作這些技術的收益不夠明顯。但是在分析場景下,單個SQL所涉及計算量可能極大,将每行作為一個基本單元進行處理會帶來嚴重的性能損耗:

1)對每一行資料都要調用相應的函數,函數調用開銷占比高;

2)存儲層按列存儲資料,在記憶體中也按列組織,但是計算層按行處理,無法充分利用CPU cache的預讀能力,造成CPU Cache miss嚴重;

3)按行處理,無法利用高效的SIMD指令;

ClickHouse實作了向量執行引擎(Vectorized execution engine),對記憶體中的列式資料,一個batch調用一次SIMD指令(而非每一行調用一次),不僅減少了函數調用次數、降低了cache miss,而且可以充分發揮SIMD指令的并行能力,大幅縮短了計算耗時。向量執行引擎,通常能夠帶來數倍的性能提升。

4.4 動态代碼生成Runtime Codegen

在經典的資料庫實作中,通常對表達式計算采用火山模型,也即将查詢轉換成一個個operator,比如HashJoin、Scan、IndexScan、Aggregation等。為了連接配接不同算子,operator之間采用統一的接口,比如open/next/close。在每個算子内部都實作了父類的這些虛函數,在分析場景中單條SQL要處理資料通常高達數億行,虛函數的調用開銷不再可以忽略不計。另外,在每個算子内部都要考慮多種變量,比如列類型、列的size、列的個數等,存在着大量的if-else分支判斷導緻CPU分支預測失效。

ClickHouse實作了Expression級别的runtime codegen,動态地根據目前SQL直接生成代碼,然後編譯執行。如下圖例子所示,對于Expression直接生成代碼,不僅消除了大量的虛函數調用(即圖中多個function pointer的調用),而且由于在運作時表達式的參數類型、個數等都是已知的,也消除了不必要的if-else分支判斷。

「資料庫」開源列式資料庫 ClickHouse 深度揭秘

4.5 近似計算

近似計算以損失一定結果精度為代價,極大地提升查詢性能。在海量資料進行中,近似計算價值更加明顯。

ClickHouse實作了多種近似計算功能:

  • 近似估算distinct values、中位數,分位數等多種聚合函數;
  • 建表DDL支援SAMPLE BY子句,支援對于資料進行抽樣處理;

4.6 複雜資料類型支援

ClickHouse還提供了array、json、tuple、set等複合資料類型,支援業務schema的靈活變更。

五、結語

近年來ClickHouse發展趨勢迅猛,社群和大廠都紛紛跟進使用。本文嘗試從OLAP場景的需求出發,介紹了ClickHouse存儲層、計算層的主要設計。ClickHouse實作了大多數目前主流的資料分析技術,具有明顯的技術優勢:

  • 提供了極緻的查詢性能:開源公開benchmark顯示比傳統方法快100~1000倍,提供50MB~200MB/s的高吞吐實時導入能力)
  • 以極低的成本存儲海量資料: 借助于精心設計的列存、高效的資料壓縮算法,提供高達10倍的壓縮比,大幅提升單機資料存儲和計算能力,大幅降低使用成本,是建構海量資料倉庫的絕佳方案。
  • 簡單靈活又不失強大:提供完善SQL支援,上手十分簡單;提供json、map、array等靈活資料類型适配業務快速變化;同時支援近似計算、機率資料結構等應對海量資料處理。

相比于開源社群的其他幾項分析型技術,如Druid、Presto、Impala、Kylin、ElasticSearch等,ClickHouse更是一整套完善的解決方案,它自包含了存儲和計算能力(無需額外依賴其他存儲元件),完全自主實作了高可用,而且支援完整的SQL文法包括JOIN等,技術上有着明顯優勢。相比于hadoop體系,以資料庫的方式來做大資料處理更加簡單易用,學習成本低且靈活度高。目前社群仍舊在迅猛發展中,相信後續會有越來越多好用的功能出現。

文章來源:https://developer.aliyun.com/article/739804