書名: ClickHouse性能之巔:從架構設計解讀性能之謎作者名: 陳峰本章字數: 7975字更新時間: 2023-02-23 16:29:15
Chapter 1
第1章 數據倉庫的核心技術
現代工程界普遍認為,數據庫系統可以在廣義上分為聯機事務處理(OnLine Transaction Process, OLTP)和聯機分析處理(OnLine Analyze Process, OLAP)兩種面向不同領域的數據庫,其中OLAP數據庫也被稱為數據倉庫(簡稱數倉)。從產品上看,有專門面向OLTP的數據庫,例如MySQL、PostgreSQL、Oracle等,也有專門面向OLAP的數據庫,例如Hive、Greenplum、HBase、ClickHouse等。還有一種嘗試統一這兩類數據庫的HATP(Hybird Analyze Transaction Process)系統,例如TiDB、OceanBase等。
表1-1將OLAP和OLTP進行對比。近年來,隨著技術的發展,OLAP和OLTP之間的界限也逐漸模糊,幾年前OLAP并不支持事務,近幾年已經出現了一些支持簡單事務的OLAP引擎,ClickHouse也將簡單的事務支持列入Roadmap。另外,隨著分布式技術的發展,部分OLTP能夠處理TB級的數據,甚至一些廠商推出的HATP數據庫,直接打破了兩者的界限。
表1-1 OLAP和OLTP的對比

OLAP和OLTP在功能上越來越趨于一致,使得在有些場景下OLAP和OLTP可以相互取代,這是否意味著原有分類方法失效了呢?是否未來就不再需要數倉或者不再需要事務數據庫呢?ClickHouse的極致性能優化能否推動OLAP和OLTP的融合?回答這些問題需要厘清OLAP和OLTP分類的本質。
本章將從OLAP和OLTP的本質出發,向讀者揭示OLAP和OLTP分類的關鍵因素,從而引出ClickHouse的核心理念及優化方向。
1.1 OLAP和OLTP的本質區別
OLTP在進行數據庫設計時使用實體-關系模型(Entity-Relationship Model, ER模型)。在ER模型的建模過程中有一個非常重要的規范化過程。規范化的目的在于通過一系列手段使得數據庫設計符合數據規范化(Normal Form, NF)的原則。簡單地說,規范化是將數據表從低范式變成高范式。一般情況下,在OLTP中通常將數據規范化為第三范式(3NF)。
1.1.1 數據三范式
在規范化的過程中經常使用范式的概念,在數據庫理論中有6種范式,下面挑選3種常用的范式進行簡單介紹以方便讀者理解后續內容。
1.第一范式
第一范式指表中的每個屬性都不可分割,滿足條件即滿足第一范式。表1-2展示了一個不滿足第一范式的例子,由于本例中的標簽字還可以細分為性別、年齡、是否為VIP用戶等多個屬性,因此不滿足第一范式。
表1-2 不滿足第一范式的用戶標簽表

2.第二范式
第二范式建立在第一范式的基礎上,當表中的所有屬性都被主鍵的所有部分唯一確定,即為滿足第二范式。表1-3展示了一個不滿足第二范式的例子,本例中用戶ID和標簽ID組成了主鍵,標簽名稱下的兩個屬性只依賴于標簽ID,用戶所在地只依賴于用戶ID,都不依賴由用戶ID和標簽ID組成的主鍵,從而不滿足第二范式。刪除標簽名稱和用戶所在地即可使得表格滿足第二范式。
表1-3 不滿足第二范式的用戶標簽表

3.第三范式
第三范式建立在第二范式的基礎上,當表中的屬性不依賴除主鍵外的其他屬性,即為滿足第三范式。回顧表1-3,來源名稱是不滿足第三范式的,這是因為來源名稱依賴于來源ID。如果想滿足第三范式,需要將來源ID刪除。表1-3經過規范化的合格數據表如表1-4、表1-5所示。
表1-4 合格的用戶標簽表

表1-5 合格的用戶信息表

4.第零范式
不滿足第一范式的所有情況都被稱為第零范式。表1-2所示的是其中一種情況。數據庫理論中并沒有對第零范式進行嚴格的定義,由于作者在本書寫作過程中會經常使用第零范式的模型設計,因此在本書中,如果沒有特別說明,第零范式特指存在Map或數組結構的一類表。這類“第零范式”的表設計具備一定的實際意義,在作者的工作中,經常會用到這類設計。靈活應用這類第零范式,可能會收獲意想不到的效果。
1.1.2 規范化的意義
一般要求在設計業務數據表時,至少設計到滿足第三范式,避免出現數據冗余。從表1-3中不難發現,標簽名稱和來源名稱是冗余的。冗余不僅增加了數據量,更重要的是,冗余會影響數據庫事務,降低數據庫事務性能。
表1-6展示了一個不合格的表設計,請讀者關注最后兩列,很明顯這是不滿足第三范式的一種設計。表中的最后一列“需要權限”用于設置數據權限,表中的數據意味著第一行和第三行需要admin權限才能查看。一般情況下沒有問題,隨著業務的變化,需要將授權級別從“2 -非公開”改為admin和manager都有權限查看。對于這種需求,如果使用表1-5的設計,就需要進行全表掃描,將數據表中所有授權級別為“2-非公開”的數據全部修改,這會嚴重降低數據庫性能。
表1-6 影響事務性能的不合格表結構

數據庫規范化的意義在于通過規范化降低冗余,提高數據庫事務性能。正是基于這個考慮,在數據庫表設計中,會要求將對數據表進行規范化。
1.1.3 規范化的局限
任何架構在有優勢的同時,也會有局限。規范化的數據表也同樣適用。規范化的數據表能夠降低冗余,進而提高事務性能。同時,規范化的數據表無法支撐分析。
以表1-3~表1-5為例,表1-4和表1-5為表1-3進行規范化后的合格用戶標簽表。如果需要按照用戶所在城市來統計年齡分布,是無法單獨使用表1-4完成的。必須對表1-4和表1-5進行連接(Join)操作,得到新表才能用于分析。而在大多數數據庫系統中,Join操作的過程相對于查詢來說是比較慢的。
1.1.4 數倉建模的本質
通過1.1.1~1.1.3節的分析,我們可以得出一個推論:高范式的表適合事務處理,低范式的表適合分析處理。從中我們可以得出數倉建模的本質:逆規范化。數倉建模在本質上就是一個逆規范化的過程,將來自原始業務數據庫的規范化數據還原為低范式的數據,用于快速分析。
在實際建模過程中,數倉經常提到的寬表本質上就是一個低范式的表。寬表將所有相關聯的列全部整合到一張表中,用于后續的分析操作,這樣做的好處就是所有相關信息都在這張寬表中,理論上在進行分析時就不需要進行Join操作了,可以直接進行相關的分析,提高了分析速度。這樣做的缺點就是數據冗余,難以支持事務能力。
大部分數據倉庫都是基于低范式數據集進行優化的,讀者在使用OLAP引擎時一定要時刻記住這一點,避免將OLTP數據庫中的原始高范式數據直接用于OLAP分析,否則分析效果可能會很差。正確的做法是通過逆規范化的過程將高范式數據集還原為低范式數據集,再由OLAP進行分析。
1.1.5 OLAP和OLTP的底層數據模型
OLAP和OLTP的本質區別在于底層數據模型不同。OLAP更適合使用低范式的數據表,而OLTP則更適合使用高范式的數據表。盡管它們之間的功能越來越相似,只要其底層數據模型不同,那么它們之間的區別就永遠存在,結構決定功能。
ClickHouse是一個面向OLAP的數倉,很多的優化都是面向低范式數據模型的,并沒有對高范式數據模型進行很好的優化。甚至在有些場景下,ClickHouse的Join能力會成為整個系統的瓶頸。
ClickHouse更適合處理低范式數據集,特別是第零范式的數據集。ClickHouse對第零范式的數據集進行了比較多的優化,在ClickHouse中靈活使用第零范式會給性能帶來非常大的提升。
1.1.6 維度建模
在使用OLAP進行數據分析時,需要對原始數據進行維度建模,之后再進行分析。維度建模理論中,基于事實表和維度表構建數據倉庫。在實際操作中,一般會使用ODS(Operational Data Store,運營數據存儲)層、DW(Data Warehouse,數據倉庫)層、ADS(Application Data Service,應用數據服務)層三級結構。
1.ODS層
ODS層一般作為業務數據庫的鏡像。在項目中,數倉工程師通常通過數據抽取工具(例如Sqoop、DataX等)將業務庫的數據復制到數倉的ODS層,供后續建模使用。ODS層的數據結構和業務數據庫保持一致,建立ODS層的原因在于,通過復制一份數據到ODS層,可以避免建模過程直接訪問業務數據庫,避免對業務數據庫帶來影響,影響線上業務。
2.DW層
將數據導入ODS層后,即可對ODS層的數據進行清洗、建模,生成DW層的數據。生成DW層數據的本質即本章提到的逆規范化的過程。由于ODS層中的數據本質上是業務數據庫的副本,因此這些數據是高范式的,不適合進行OLAP分析。這也導致了在進行OLAP分析前需要將高范式的數據通過一些手段逆規范化為低范式的數據。低范式的數據作為DW層的數據,對外提供分析服務。
在進行數據逆規范化時,可能會產生一些中間結果,這些中間結果也可以存儲于DW層,因此DW層有時會再次進行細分,劃分成DWD(Data Warehouse Detail,數據倉庫明細)層、DWM(Data Warehouse Middle,數據倉庫中間)層、DWS(Data Warehouse Service,數據倉庫服務)層3個更細分的層次。
ODS層的數據通過清洗后存儲到DWD層,DWD層的本質是一個去除了臟數據的高質量低范式數據層。DWD層的數據通過聚合形成寬表并保存到DWM層中。DWM層已經是低范式的數據層了,可以用于OLAP分析。在某些場景中,可以對DWM層的數據進行業務聚合,以支持更復雜的業務,此時需要將生成的數據保存到DWS層中。
并不是所有場景都需要包含這3個細分的DW層。DW層的本質就是對高范式的數據進行逆規范化,生成低范式數據。讀者只需要把握住這個核心,在實際的維度建模過程中,根據業務的實際需求進行建模,不需要在所有的場景下機械地遵循DWD層、DWM層、DWS層的三層架構。
本書會多次用到DW層的概念,如無特殊說明,DW層均指經過了逆規范化后的結果,而不是逆規范化的過程。
3.ADS層
ADS層保存供業務使用的數據,雖然DW層的數據可用于OLAP分析,但分析過程通常比較慢,無法支撐實時的業務需求,需要引入ADS層作為緩存,向上支撐業務。同樣的,ADS層也不是必須的,需要根據業務實際情況來選擇,ClickHouse的高性能計算引擎可以在一定程度上取代ADS層。
ADS層數據本質是面向業務的、高度業務化的數據,可以認為是基于DW層數據分析的結果,很多情況下是指標、標簽的計算結果。本書后續提到的ADS,如無特殊說明,均指基于DW層分析后的業務化的結果。
1.2 典型大數據數倉技術及其核心思路
科學技術是不會突變的,ClickHouse也不是憑空冒出來的,而是在早年大數據數倉系統的基礎上進行改良得到的。本節介紹ClickHouse誕生之前的幾個知名數倉技術及其核心思路,從而引出ClickHouse的優化方向。
1.2.1 Hive
Hive是一個基于HDFS(Hadoop Distributed File System, Hadoop分布式文件系統)的數倉系統,可以將存儲在HDFS之上的文件進行結構化解析,并向用戶提供使用SQL操作Hadoop的能力。由于Hive構建在HDFS和Spark/MapReduce的系統之上,使得Hive具備了理論上近乎無限的大數據處理能力,因此Hive迅速在業界占據主導地位,在其巔峰時期,Hive+SparkSQL就是大數據系統的標配。Hive使用的是中間件思路,構建在Hadoop的組件之上。
Hive本質上是一個元數據系統,由于自身沒有存儲和計算引擎,因此Hive無法單獨使用,必須配合HDFS+Spark/MapReduce,才能完成整個數倉的功能。甚至Hive的元數據存儲都借助了MySQL等關系型數據庫。這樣的架構設計使得Hive成為事實上的Hadoop中間件系統,在大數據早期階段具有非常重要的意義。
□降低了Hadoop的技術門檻。
□提高了Hadoop的使用效率。
□投入產出比高。
隨著技術的進步,Hadoop自身的問題越來越突出,使得Hive成為整個系統的瓶頸,這時候Hive的一些問題也隨之暴露,主要體現在以下幾點。
□安裝運維困難:安裝Hive需要同時安裝許多組件,易出現單點故障。
□受限于底層HDFS:如小文件問題、不支持更新等。
□受限于底層計算引擎:計算效率太低、延遲高,無法支撐即席查詢等。
在ClickHouse技術尚未出現的年代,使用Hive需要同時配備非常龐大的大數據平臺,這使得傳統煙囪式數據開發模型由于成本原因,無法在大數據時代的早期廣泛應用,因此出現了數據中臺的概念。建設數據中臺的一個重要原因是技術不成熟使得大數據應用的成本非常高,企業沒有建設多個大數據平臺的動力,需要盡可能降低建設和維護成本,這就需要將所有數據整合到“大中臺小前臺”的架構中,并由此誕生了一大批提供數據中臺服務的公司,極大地推動了產業的發展和技術的應用。
煙囪式的數據開發模式在數據中臺時代被視為洪水猛獸,應該絕對避免。當然這個觀點在數據中臺時代是正確的。隨著大數據技術的成熟,使用全新技術的煙囪式數據開發模式其實也有著數據中臺無法比擬的優勢——成本低、研發快、架構靈活。這更體現出了計算機科學中的一個經典理論——沒有銀彈。作為架構師,應該理性、平等地分析每個架構的優點和缺點,找到最合適的架構,而不是期望找到正確的架構。
1.2.2 HBase
和Hive類似,HBase也是建立在HDFS之上的NoSQL數據庫。和Hive不同的是,HBase實現了自己的存儲引擎,避開了HDFS低性能的缺陷,獲得了非常高的吞吐能力。憑借遠比Hive高的性能,在大數據時代早期,部分場景也會將HBase當成數據倉庫來使用。
HBase采用重新設計的存儲引擎解決了一些數倉的技術瓶頸。HBase的存儲引擎是面向NoSQL設計的,由于無法完整地實現SQL的能力,因此除了少部分不需要強大SQL能力的場景之外,HBase的使用場景非常有限。HBase的出現也有著非常重要的意義。
□證明了基于低效的HDFS也能設計出相對高性能的系統。
□彌補了Hive無法支持實時場景的缺陷。
□填補了大數據系統即席查詢的空白,擴充了大數據的應用場景。
1.2.3 Kylin
Kylin是一個多維OLAP數倉。Hive的查詢延遲很高,尤其在復雜的多維分析中格外明顯,難以滿足某些實時場景下的查詢需求。HBase雖然可以解決一部分場景下的高延遲問題,但因為不支持SQL特性,所以無法支持復雜的多維分析。
Kylin就是在這樣的背景下應運而生的。Kylin的基本原理是既然復雜的多維分析查詢速度慢,那就提前計算好結果并保存到HBase中,即可在需要時快速得出結果。Kylin采用將復雜計算前置的思想,降低了復雜計算的延遲。從本質上看,可以認為Kylin也是一個大數據中間件。當然,Kylin也面臨著如下挑戰。
□維度爆炸:Kylin是將計算前置,由于很難事先預測需要組合的維度,因此只能進行窮舉。這種窮舉的方式面臨著維度爆炸的風險。
□數據實時性弱:由于Kylin存在預計算過程,新的數據必須經過預計算才能被檢索到,因此Kylin只是解決了實時查詢的問題,沒有解決數據無法實時響應的缺陷。
□資源浪費:由于預計算的結果并不一定會被使用,因此可能存在資源浪費的情況。
□指標逃逸:如果所需數據未被預計算,需要重新使用Spark/MapReduce進行計算。
1.2.4 其他數倉
1.2.1~1.2.3節向讀者介紹了3種常用的數據倉庫及其核心思路,這3種數倉分別采用了3種不同的設計思路來實現大數據的數據倉庫。而其他的數倉,大多也是采用其中的一種或幾種思路,例如Greenplum采用的是與Hive類似的中間件思路,只不過其底層數據庫是PostgreSQL而不是Hive的Hadoop。而本書的主角ClickHouse則是采用類似HBase的思路,以極限單機性能為目標,重新設計了存儲引擎和計算引擎。對于這3種數倉的架構會在第6章進行詳細介紹和對比。
1.3 傳統數倉的缺陷
1.2節介紹了3種典型數倉技術的核心思路,本節將對這些傳統數倉的缺陷進行總結。
1.3.1 效率低
傳統的數倉大多構建在Hadoop之上,這為傳統的數倉帶來了近乎無限的橫向擴展能力,同時也造成了效率低的問題。效率低主要體現在以下幾個方面。
□部署效率低:在部署Hive、HBase、Kylin之前,必須部署Hadoop集群。和傳統數據庫相比,這個部署效率是非常低效的。
□運維效率低:Hive、HBase、Kylin基于Hadoop, Hadoop生態會帶來一個非常嚴重的單點故障問題,即Hadoop體系中任何一個組件出現問題,都可能引起整個系統不可用。使用傳統的數倉對運維的要求非常高。
□計算效率低:主要體現在Hive和Kylin上,這兩個數倉沒有自己的存儲引擎和計算引擎,只能依靠堆機器實現復雜查詢,而無法從數據本身入手。直到在大數據時代后期,一些以數據快速查詢為目標而特殊設計的數據存儲格式成為標準,這個現象才有所改觀。而HBase的優化核心就是重新設計的存儲引擎,使得HBase可以對數據本身進行查詢速度的優化。
1.3.2 延遲高
構建在Hadoop之上的數倉引擎,除了效率低的缺點之外,還面臨著高延遲的挑戰。高延遲主要體現在以下兩個方面。
□查詢延遲高:使用Hive作為數倉,受限于HDFS的性能瓶頸,Hive的查詢速度比較慢,難以支撐低延遲場景,無法應用在實時計算的場景中。
□寫入數據延遲高:同樣受限于HDFS,Hive的數據寫入延遲也很高,這意味著數據無法實時寫入Hive,從而無法支撐實時分析場景。
1.3.3 成本高
傳統的數倉數倉引擎還會帶來成本高的挑戰,主要體現在以下幾個方面。
□部署成本高:由于Hadoop的計算邏輯是通過堆計算資源的方式來攤銷復雜查詢的時間,因此如果需要達到一個比較理想的性能,必須要求集群中節點的數量達到一定的規模,否則因為計算效率低,單機很容易成為性能瓶頸。這導致了Hive等基于Hadoop的數倉部署成本高的缺陷。
□運維成本高:集群服務器達到一定規模后,運維成本會指數級增長。同時,由于Hadoop中組件太多,任何一個組件失效都有可能導致整個服務不可用,因此運維團隊必須包含所有組件的運維人員,這也極大提高了運維團隊的人力成本。
□存儲成本高:Hadoop的HDFS為了避免集群中因服務器故障導致的不可用情況,默認使用三副本策略存儲數據,即數據會保存3份。這會極大提高存儲成本。即使是新一代的Hadoop采用糾刪碼技術降低了副本數量,也因使用場景有限,只適合在冷數據存儲中使用,對于經常需要查詢的熱數據,并不適合采用該方案。
□決策成本高:傳統的大數據部署成本高,導致企業在做決策時面臨比較大的決策成本,一方面是前期投入太大,短期內看不到效果,長期效果也很難說清楚;另一方面是即使企業下定決心來建設數倉,昂貴的基礎設施和缺乏專業技術人員也會導致需要很長的建設周期,太長的建設周期又會帶來很多不可預知的變數,最終影響企業的決策。
1.4 ClickHouse查詢性能的優勢
1.3節向讀者說明了傳統數倉存在的一些缺陷,本節將為讀者介紹ClickHouse是如何通過精妙的存儲引擎和計算引擎來解決這些問題的。
1.4.1 向量化引擎
在存儲引擎的設計上,ClickHouse采用了基于列存儲的存儲結構。列存儲在很多場景中極大地降低了數據分析過程中讀取的數據量,圖1-1展示了列存儲相比于行存儲減少數據量的原理。顯然,在寬表場景下,由于行存儲在抽取某些列時必須讀取該行的所有列,因此讀取了大量無效的數據(圖1-1中行存方案中未加▲的深色方塊數據為無效的不參與計算的列),從而降低了讀取效率。

圖1-1 列存儲和行存儲的對比
在計算引擎的設計上,ClickHouse首次使用了向量化計算引擎。向量化計算引擎的計算原理如圖1-2所示,借助CPU提供的SIMD(Single Instruction Multiple Data,單指令多數據流)技術,可以充分發揮現代計算機體系架構的優勢,最大限度地壓榨單機性能。

圖1-2 向量化計算引擎計算原理示意圖
而ClickHouse對單機性能的壓榨,使得其可以在單機部署的情況下處理大量數據。在實際使用中,基本上百億以下的數據表,都可以使用單機解決。這種程度的單機處理能力已經可以滿足非常多的企業層面的需要,也在很大程度上解決了傳統大數據數倉的效率低和成本高的問題。
1.4.2 高效的數據壓縮
列存儲為ClickHouse帶來的另一個非常明顯的優勢就是大幅提高了數據壓縮空間。列存儲是將同一列的數據存儲在連續的空間中,相比于行存儲,列存儲在連續的空間上更有規律。而規律的存儲,帶來了更大的壓縮率,從而大幅減少壓縮后的數據大小,極大減少了磁盤的I/O時間。
筆者在實際項目中,基本都能做到8:1的壓縮比,即8TB的數據只需要1TB的存儲空間。這在提高計算效率的同時也降低了存儲成本。相比于Hadoop的三副本策略,存儲成本大幅降低。
讀者可能會存在一個疑問:Hadoop的三副本能保證數據不丟失,而ClickHouse的存儲是無法保證數據不丟失的,那么二者是否不能放在一起比較?這個疑問是有一定道理的,二者的應用場景不同,面臨的問題也不同。Hadoop如果需要發揮能力,必須有一個龐大的集群來攤銷低效率需要的額外處理時間,這意味著集群中任何一臺機器出現故障,都有可能導致集群不可用,從概率學上看,假設一臺機器的故障率是1%,那么100臺機器中有一臺出現故障的概率已經接近100%了。由此可見,在一個龐大的Hadoop集群中是必須考慮機器故障問題的。
而ClickHouse則不同,ClickHouse在設計時傾向于榨干單機性能,在很多場景下用單機解決問題。這種設計使得單機ClickHouse出現故障的概率只有1%,可以在一定程度上忽略機器故障。當然,具體場景需要讀者依據業務需求進行分析,如果確實需要保證數據不丟失,可以使用RAID(Redundant Arrays of Independent Disks,磁盤陣列)在物理層面提供保障,也可以使用ClickHouse提供的復制表從軟件層面解決該問題。總之,ClickHouse提供了比較靈活的機制。
1.4.3 高效的I/O優化
超高的壓縮率為ClickHouse帶來了更低的數據存儲成本和更低的I/O時間,同時也帶來了計算時的額外開銷——解壓。數據壓縮后存儲到磁盤上,意味著壓縮的數據被讀取后無法直接獲取內容,也就無法參與分析和計算,必須經過解壓還原原始數據,才可以參與分析和計算。那么如何最大限度地減少壓縮時間,甚至在數據被程序讀取前就過濾掉不相關的數據,成為具備壓縮能力的存儲引擎的一大挑戰。
ClickHouse通過基于LSM(Log-Structured Merge,日志結構合并)技術的稀疏索引來應對這個挑戰。通過LSM技術,保證數據寫入磁盤前就已經按照要求做好了排序,這意味著數倉中非常常見的范圍查詢場景可以節省大量的I/O時間,從而提升查詢速度。
1.5 本章小結
本章首先向讀者介紹了產生OLAP和OLTP兩種形態數據庫的底層原因及兩者的區別,并解釋了為何OLAP數據庫針對大寬表進行優化。然后介紹了傳統的大數據數倉技術的內容及思路,并為讀者分析了這些傳統技術帶來的缺陷。最后介紹了ClickHouse的一些優化手段。