clickhouse在易企秀數據倉庫項目中已投入使用兩年,主要為內部用戶提供快速查詢和多維分析的能力;希望你在業務當中遇到的性能問題,在這里都能得到解決
Clickhouse堪稱OLAP領域的黑馬,最近發布的幾個版本在多表關聯分析上也有了極大的性能提升,尤其是還引入了MaterializeMySQL Database Engine做到了實時對齊業務線mysql中的數據。
表優化
數據類型
建表時能用數值型或日期時間型表示的字段,就不要用字符串——全String類型在以Hive為中心的數倉建設中常見,但CK環境不應受此影響。
雖然clickhouse底層將DateTime存儲為時間戳Long類型,但不建議直接存儲Long類型,因為DateTime不需要經過函數轉換處理,執行效率高、可讀性好。
官方已經指出Nullable類型幾乎總是會拖累性能,因為存儲Nullable列時需要創建一個額外的文件來存儲NULL的標記,并且Nullable列無法被索引。因此除非極特殊情況,應直接使用字段默認值表示空,或者自行指定一個在業務中無意義的值(例如用-1表示沒有商品ID)。
數值類型分組最快,在新版本中ck會對string類型進行一次hash映射再分組
分區和索引
- 分區粒度根據業務特點決定,不宜過粗或過細。一般選擇按天分區,也可指定為tuple();以單表1億數據為例,分區大小控制在10-30個為最佳。
PARTITION BY tuple()
- 必須指定索引列,clickhouse中的索引列即排序列,通過order by指定,一般在查詢條件中經常被用來充當篩選條件的屬性被納入進來;可以是單一維度,也可以是組合維度的索引;通常需要滿足高基列在前、查詢頻率大的在前原則;還有基數特別大的不適合做索引列,如用戶表的userid字段;通常篩選后的數據滿足在百萬以內為最佳。
表參數
index_granularity 是用來控制索引粒度的 默認是8192,如非必須不建議調整。
如果表中不是必須保留全量歷史數據,建議指定TTL,可以免去手動過期歷史數據的麻煩。TTL也可以通過ALTER TABLE語句隨時修改。
查詢優化
單表查詢
- 使用prewhere替代where關鍵字;當查詢列明顯多于篩選列時使用prewhere可十倍提升查詢性能
# prewhere 會自動優化執行過濾階段的數據讀取方式,降低io操作
select * from work_basic_model where product='tracker_view' and ( id='eDf8fZky' or code='eDf8fZky' )
#替換where關鍵字
select * from work_basic_model prewhere product='tracker_view' and ( id='eDf8fZky' or code='eDf8fZky' )
- 數據采樣,通過采用運算可極大提升數據分析的性能
SELECT
Title,
count() * 10 AS PageViews
FROM hits_distributed
SAMPLE 0.1 #代表采樣10%的數據,也可以是具體的條數
WHERE
CounterID = 34
GROUP BY Title
ORDER BY PageViews DESC LIMIT 1000
采樣修飾符只有在mergetree engine表中才有效,且在創建表時需要指定采樣策略;
數據量太大時應避免使用select * 操作,查詢的性能會與查詢的字段大小和數量成線性變換;字段越少,消耗的io資源就越少,性能就會越高。
千萬以上數據集進行order by查詢時需要搭配where條件和limit語句一起使用
- 如非必須不要在結果集上構建虛擬列,虛擬列非常消耗資源浪費性能,可以考慮在前端進行處理,或者在表中構造實際字段進行額外存儲。
select id ,pv, uv , pv/uv rate
使用 uniqCombined 替代 distinct 性能可提升10倍以上,uniqCombined 底層采用類似HyperLogLog算法實現,如能接收2%左右的數據誤差,可直接使用這種去重方式提升查詢性能。
對于一些確定的數據模型,可將統計指標通過物化視圖的方式進行構建,這樣可避免數據查詢時重復計算的過程;物化視圖會在有新數據插入時進行更新。
# 通過物化視圖提前預計算用戶下載量
CREATE MATERIALIZED VIEW download_hour_mv
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(hour) ORDER BY (userid, hour)
AS SELECT
toStartOfHour(when) AS hour,
userid,
count() as downloads,
sum(bytes) AS bytes
FROM download WHERE when >= toDateTime('2020-10-01 00:00:00') #設置更新點,該時間點之前的數據可以通過insert into select的方式進行插入
GROUP BY userid, hour
## 或者
CREATE MATERIALIZED VIEW db.table_MV TO db.table_new ## table_new 可以是一張mergetree表
AS SELECT * FROM db.table_old;
# 不建議添加populate關鍵字進行全量更新
多表關聯
- 當多表聯查時,查詢的數據僅從其中一張表出時,可考慮使用IN操作而不是JOIN。
select a.* from a where a.uid in (select uid from b)
# 不要寫成
select a.* from a left join b on a.uid=b.uid
多表Join時要滿足小表在右的原則,右表關聯時被加載到內存中與左表進行比較。
clickhouse在join查詢時不會主動發起謂詞下推的操作,需要每個子查詢提前完成過濾操作;需要注意的是,是否主動執行謂詞下推,對性能影響差別很大【新版本中已不再存在此問題,但是需要注意的是謂詞位置的不同依然有性能的差異】。
將一些需要關聯分析的業務創建成字典表進行join操作,前提是字典表不易太大,因為字典表會常駐內存。
ENGINE = Dictionary(dict_name)
或者
create database db_dic ENGINE = Dictionary
寫入和刪除優化
- 盡量不要執行單條或小批量刪除和插入操作,這樣會產生大量小分區文件,給后臺merge任務帶來巨大壓力。
- 不要一次寫入太多分區,或數據寫入太快,數據寫入太快會導致merge速度跟不上而報錯;一般建議每秒中發起2-3次寫入操作,每次操作寫入2w-5w條數據。
運維相關
配置
配置 | 描述 |
---|---|
background_pool_size | 后臺用來merge進程的大小,默認是16,建議改成cpu個數的2倍 |
log_queries | 默認值為0,修改為1,系統會自動創建system_query_log表,并記錄每次查詢的query信息 |
max_execution_time | 設置單次查詢的最大耗時,單位是秒;默認無限制;需要注意的是客戶端的超時設置會覆蓋該參數 |
max_threads | 設置單個查詢所能使用的最大cpu個數;默認是CPU核數 |
max_memory_usage | 一般按照CPU核心數的2倍去設置最大內存使用 |
max_bytes_before_external_group_by | 一般按照max_memory_usage的一半設置內存,當group使用內存超出閾值后會刷新到磁盤進行 |
存儲
clickhouse不支持設置多數據目錄,為了提升數據io性能,可以掛載虛擬券組,一個券組綁定多塊物理磁盤提升讀寫性能;多數查詢場景SSD盤會比普通機械硬盤快2-3倍。
數據同步
新版clickhouse提供了一個實驗性的功能,那就是我們可以將clickhouse偽裝成mysql的一個備庫去實時對齊mysql中的數據,當mysql庫表數據發生變化時會實時同步到clickhouse中;這樣就省掉了單獨維護實時spark/flink任務讀取kafka數據再存入clickhouse的環節,大大降低了運維成本提升了效率。
CREATE DATABASE ckdb ENGINE = MaterializeMySQL('172.17.0.2:3306', 'ckdb', 'root', '123');
查詢熔斷
為了避免因個別慢查詢引起的服務雪崩問題,除了可以為單個查詢設置超時以外,還可以配置周期熔斷;在一個查詢周期內,如果用戶頻繁進行慢查詢操作超出規定閾值后將無法繼續進行查詢操作: