前言
經(jīng)過一個(gè)月的調(diào)研和快速試錯(cuò),我們的ClickHouse集群已經(jīng)正式投入生產(chǎn)環(huán)境喇聊,在此過程中總結(jié)出了部分有用的經(jīng)驗(yàn),現(xiàn)記錄如下蹦狂∈睦椋看官可去粗取精,按照自己項(xiàng)目中的實(shí)際情況采納之凯楔。(版本為19.16.14.65)
因?yàn)槲覀円隒lickHouse的時(shí)間并不算長(zhǎng)窜骄,還有很多要探索的,因此不敢妄稱“最佳實(shí)踐”摆屯,還是叫做“更佳實(shí)踐”比較好吧邻遏。
表相關(guān)事項(xiàng)
數(shù)據(jù)類型
- 建表時(shí)能用數(shù)值型或日期時(shí)間型表示的字段,就不要用字符串——全String類型在以Hive為中心的數(shù)倉(cāng)建設(shè)中常見虐骑,但CK環(huán)境不應(yīng)受此影響准验。
- 直接用DateTime表示時(shí)間列,而不是用整形的時(shí)間戳廷没。因?yàn)镃K中DateTime的底層就是時(shí)間戳沟娱,效率高,可讀性好腕柜,且轉(zhuǎn)換函數(shù)豐富济似。
- 官方已經(jīng)指出Nullable類型幾乎總是會(huì)拖累性能,因?yàn)榇鎯?chǔ)Nullable列時(shí)需要?jiǎng)?chuàng)建一個(gè)額外的文件來存儲(chǔ)
NULL
的標(biāo)記盏缤,并且Nullable列無法被索引砰蠢。因此除非極特殊情況,應(yīng)直接使用字段默認(rèn)值表示空唉铜,或者自行指定一個(gè)在業(yè)務(wù)中無意義的值(例如用-1表示沒有商品ID)台舱。
分區(qū)和索引
- 事實(shí)表必須分區(qū),分區(qū)粒度根據(jù)業(yè)務(wù)特點(diǎn)決定,不宜過粗或過細(xì)竞惋。我們當(dāng)前都是按天分區(qū)柜去,按小時(shí)、周拆宛、月分區(qū)也比較常見(系統(tǒng)表中的query_log嗓奢、trace_log表默認(rèn)就是按月分區(qū)的)。
- 必須指定索引列浑厚,在絕大多數(shù)查詢的
WHERE
語(yǔ)句中都會(huì)用到的列適合作為索引股耽。CK的索引非MySQL的B樹索引,而是類似Kafka log風(fēng)格的稀疏索引钳幅,故不用考慮最左原則物蝙,但是建議日期列和區(qū)分度較低的列在前,區(qū)分度較高的列在后敢艰。
訂正:根據(jù)稀疏索引的規(guī)律诬乞,建議查詢中更經(jīng)常用做查詢條件(WHERE謂詞)的列在前,較不經(jīng)常用做查詢條件的列在后钠导。如果有兩列在WHERE謂詞中出現(xiàn)的頻率大致相同丽惭,則基數(shù)較大的列(即區(qū)分度較高的列)在前,基數(shù)較小的列(區(qū)分度較低的列)在后辈双。另外责掏,基數(shù)特別大的列(如訂單ID等)不建議直接用作索引。
- 表的索引粒度
index_granularity
不建議調(diào)整湃望,保持默認(rèn)值8192即可换衬。
表參數(shù)
- 生產(chǎn)環(huán)境中提供線上服務(wù)的表均采用復(fù)制表與分布式表相結(jié)合,即Replicated*MergeTree+Distributed引擎证芭。分布式表的表名為本地表名加上
_all
后綴瞳浦。 - 如果表中不是必須保留全量歷史數(shù)據(jù),建議指定TTL废士,可以免去手動(dòng)過期歷史數(shù)據(jù)的麻煩叫潦。TTL也可以通過
ALTER TABLE
語(yǔ)句隨時(shí)修改。 - 建議指定
use_minimalistic_part_header_in_zookeeper = 1
設(shè)置項(xiàng)官硝,能夠顯著壓縮表元數(shù)據(jù)在ZooKeeper中的存儲(chǔ)矗蕊。該項(xiàng)也可以寫入config.xml中的<merge_tree>一節(jié)。
查詢相關(guān)事項(xiàng)
單表查詢
- 所有應(yīng)用層查詢禁止
SELECT *
氢架。 - 查詢分區(qū)表必須指定分區(qū)(所謂partition pruning)傻咖,不能全表查詢。
- 大規(guī)模數(shù)據(jù)集上的ORDER BY要加LIMIT限制岖研。
- 結(jié)果集上的簡(jiǎn)單運(yùn)算(例如
SELECT pv, uv, pv / uv as ratio...
中的ratio)可以在前端展示時(shí)再進(jìn)行卿操,減少SQL中不必要的虛擬列。 - 業(yè)務(wù)場(chǎng)景非強(qiáng)制要求100%準(zhǔn)確的基數(shù)計(jì)量,應(yīng)該用uniq()函數(shù)而不是uniqExact()函數(shù)或DISTINCT關(guān)鍵字害淤。uniq()底層采用HyperLogLog實(shí)現(xiàn)扇雕,能夠以低于1%的精度損失換來極大的性能提升。
- 能夠重用的模式化查詢(如固定刷新的BI報(bào)表窥摄、熱力圖等)一定要做成物化視圖镶奉,并在物化視圖上查詢出結(jié)果,可以避免大量的重復(fù)計(jì)算溪王。關(guān)于其用法腮鞍,可參見之前寫過的《物化視圖簡(jiǎn)介與ClickHouse中的應(yīng)用示例》值骇。
多表查詢
- 當(dāng)兩表關(guān)聯(lián)查詢只需要從左表出結(jié)果時(shí)莹菱,建議用IN而不是JOIN,即寫成
SELECT ... FROM left_table WHERE join_key IN (SELECT ... FROM right_table)
的風(fēng)格吱瘩。 - 不管是LEFT道伟、RIGHT還是INNER JOIN操作,小表都必須放在右側(cè)使碾。因?yàn)镃K默認(rèn)在大多數(shù)情況下都用hash join算法蜜徽,左表固定為probe table,右表固定為build table且被廣播票摇。
- CK的查詢優(yōu)化器比較弱拘鞋,JOIN操作的謂詞不會(huì)下推,因此一定要先做完過濾矢门、聚合等操作盆色,再在結(jié)果集上做JOIN。這點(diǎn)與我們寫其他平臺(tái)SQL語(yǔ)句的習(xí)慣很不同祟剔,初期尤其需要注意隔躲。
- 兩張分布式表上的IN和JOIN之前必須加上GLOBAL關(guān)鍵字际长。如果不加GLOBAL關(guān)鍵字的話详瑞,每個(gè)節(jié)點(diǎn)都會(huì)單獨(dú)發(fā)起一次對(duì)右表的查詢均驶,而右表又是分布式表多柑,就導(dǎo)致右表一共會(huì)被查詢N2次(N是該分布式表的shard數(shù)量)搪泳,這就是所謂的查詢放大夷磕,會(huì)帶來不小的overhead帮匾。加上GLOBAL關(guān)鍵字之后查描,右表只會(huì)在接收查詢請(qǐng)求的那個(gè)節(jié)點(diǎn)查詢一次耗溜,并將其分發(fā)到其他節(jié)點(diǎn)上买置。
負(fù)載均衡
對(duì)于循環(huán)復(fù)制拓?fù)涞募海樵兎植际奖淼呢?fù)載均衡策略(即load_balancing
)設(shè)為first_or_random是最優(yōu)的强霎,能夠充分利用機(jī)器page cache的同時(shí)忿项,在有replica失敗時(shí)也能盡量保證負(fù)載平均分配。詳情可見這個(gè)issue。
寫入相關(guān)事項(xiàng)
- 寫入分布式表的底表轩触,而不直接寫分布式表寞酿。在之前的《ClickHouse復(fù)制表、分布式表機(jī)制與使用方法》一文中已有說明脱柱。
- 不要做小批量零碎的寫入伐弹,每批次至少千條級(jí)別,避免給merge造成太大壓力榨为。
- 不要同時(shí)寫入太多個(gè)分區(qū)惨好,或者寫入過快(官方給出的閾值為1秒1次),容易因?yàn)閙erge的速度跟不上parts生成的速度而報(bào)出"too many parts"的錯(cuò)誤随闺。如果正常情況下還會(huì)出現(xiàn)此錯(cuò)誤日川,建議在CPU資源允許的情況下適當(dāng)調(diào)大后臺(tái)任務(wù)線程數(shù)
background_pool_size
,默認(rèn)值為16矩乐。
運(yùn)維相關(guān)事項(xiàng)
CPU
CK的“快”與其對(duì)CPU的積極利用密不可分龄句,所以CPU的單核性能和多核性能都要盡量好一點(diǎn),16核32線程左右且?guī)л^高的睿頻比較合適散罕。CK設(shè)置中的max_threads
參數(shù)控制單個(gè)查詢所能利用的CPU線程數(shù)分歇,默認(rèn)與本機(jī)CPU的物理核心數(shù)相同,如果服務(wù)器是CK獨(dú)占的欧漱,那么就不用改职抡,否則就改小些。
在監(jiān)控集群時(shí)误甚,CPU指標(biāo)也是最重要的缚甩。實(shí)測(cè)當(dāng)單個(gè)CK Server節(jié)點(diǎn)的CPU使用率超過70%時(shí),服務(wù)就不太穩(wěn)定了靶草。
內(nèi)存
官方文檔建議單機(jī)物理內(nèi)存128G左右蹄胰。實(shí)測(cè)CK在我們的應(yīng)用場(chǎng)景下內(nèi)存占用并不激進(jìn),每線程對(duì)應(yīng)1G內(nèi)存非常綽綽有余奕翔,即max_threads
設(shè)為20的話裕寨,max_memory_usage
參數(shù)設(shè)為20G(懶得打辣么多0了)。為了不干擾系統(tǒng)的正常運(yùn)行派继,也應(yīng)配置所有查詢能利用的最大內(nèi)存參數(shù)max_memory_usage_for_all_queries
宾袜,取物理內(nèi)存的80%左右即可。
另外驾窟,CK在執(zhí)行GROUP BY聚合邏輯的過程中很有可能超出內(nèi)存限制庆猫,因此也建議設(shè)置max_bytes_before_external_group_by
參數(shù)。在內(nèi)存占用超出此閾值之后绅络,就會(huì)spill到磁盤繼續(xù)操作月培,且性能沒有降低特別多嘁字。官方建議將它設(shè)置為max_memory_usage
的一半。
存儲(chǔ)
CK不太挑存儲(chǔ)介質(zhì)杉畜,普通7200rpm SATA HDD都可以用纪蜒,也可以配置磁盤陣列,建議RAID10或者RAID6此叠。但是如果為了快速響應(yīng)纯续,或者多數(shù)查詢的數(shù)據(jù)量都很大,還是建議上SSD(我們就是如此)灭袁。另外猬错,CK還支持基于配置文件的多盤存儲(chǔ)、冷熱數(shù)據(jù)分離和存儲(chǔ)策略(storage policy)設(shè)置茸歧,在特定場(chǎng)景下可能會(huì)很有用倦炒。我們未實(shí)操過,不多講了举娩。
ZooKeeper
千萬(wàn)要調(diào)教好ZooKeeper集群析校,一旦ZK不可用构罗,復(fù)制表和分布式表就不可用了铜涉。ZK的數(shù)據(jù)量基本上與CK的數(shù)據(jù)量成正相關(guān),所以一定要配置自動(dòng)清理:
autopurge.purgeInterval = 1
autopurge.snapRetainCount = 5
另外遂唧,ZK的log文件和snapshot文件建議分不同的盤存儲(chǔ)芙代,盡量減少follower從leader同步的磁盤壓力,且余量必須要留足盖彭,畢竟硬盤的成本不算高纹烹。
The End
上文中還涉及到一些比較重要的知識(shí)點(diǎn),如MergeTree索引的結(jié)構(gòu)召边,JOIN語(yǔ)句的執(zhí)行過程铺呵,CK與ZK的交互等等,今后有時(shí)間會(huì)分別寫文章詳細(xì)講解隧熙。
618之前事情一直都會(huì)比較多片挂,希望一切順利。今天先這樣吧贞盯。
民那晚安晚安音念。