WeOLAP:微信 OLAP 湖倉新場景優化實踐
ClickHouse 在微信有著廣泛應用,如何保障其自身查詢性能,并能在新場景中結合應用成為了關鍵問題。基于該背景,開發團隊首先針對ClickHouse 的性能問題,開發了相應的性能觀測工具,并在數據查詢、策略實驗等場景針對性進行了湖倉讀取、bitmap 計算等方面的探索優化,最后將 ClickHouse 在 AI 場景進行落地應用,沉淀了融合 OLAP 能力的成熟數據管線。
一、ClickHouse 在微信的應用
1. ClickHouse 在微信的應用
ClickHouse 在微信團隊有著廣泛應用,如實時報表、AB 實驗和實時計算等,通過將 Hadoop 相關生態集成到 ClickHouse 中,性能得到了十倍到百倍的提升,能夠做到萬億級數倉、亞秒級響應和穩定高可用。

ClickHouse 在微信的集群規模有數千臺,Top50 響應時長約 0.34 秒,平均響應時長為 4 秒,查詢量級為每天百萬級。當前的主要版本是基于社區的 22.8,少量版本對應社區 23.3。

2. 新場景應用
過去一年我們探索了 ClickHouse 的一些新的應用場景。在湖倉讀取方面,基于 Iceberg/Hive 進行讀取和湖上數據加工,來緩解數據孤島問題;在實驗新場景上,進行畫像分析、人群圈選,支撐實時可見的在線實驗系統;另外,也與 AI 進行結合,通過成熟的 OLAP 數據管線為近/離線模型推理進行提效。

二、ClickHouse 的性能觀察工具
作為一個用戶,需要感知查詢的資源消耗;作為一個運維同學,需要知道如何優化集群負載;作為一個開發同學,需要快速定位慢查詢的原因。這些都離不開性能觀察工具。ClickHouse 提供了一系列便捷的性能觀測工具,如 Query Log、Query Thread Log、Sampling Query Profiler 和 Flame Graph 等。

首先是最常用的 Query Log 和 Query Thread Log,通過查詢的 query id,可以對這條查詢性能進行觀察分析。我們還可以在代碼中增加自定義的 Profile Event,方便定制一些觀測指標。

第二個是 Sampling Query Profiler 和 ClickHouse Flame Graph,通過可視化的火焰圖能夠直觀地對內存和 CPU 進行分析,在 CH 可以對指定查詢進行 profile,支持的最細粒度為 query 級別。它有一個缺點,會將一個查詢涉及到的多個線程匯聚到一起,導致無法對單個線程的情況進行分析。我們針對這個問題也做了改進優化,使它能支持線程級的單個展示和查詢聚合。

第三個是 Processors Profile Log,它可以幫助我們清晰地看到每個算子的耗時,判斷算子間是否均衡、是否存在傾斜情況,也可以幫助我們看到算子間的依賴關系。

WeOLAP 團隊還自研了性能分析工具 Profile Engine,從事前和事后兩個場景進行優化。在事前對用戶提交的 SQL 結合集群信息和表信息進行分析,并基于索引、分區等給出相應可視化改進建議;在事后基于制定的規則對大查詢和慢查詢進行分析,給出優化建議。通過這個工具,既可以給使用者提出優化建議,也可以幫助使用者平衡集群負載。該工具上線后的使用效果很不錯。

三、湖倉讀取優化
ClickHouse 在湖倉鏈路中既是存儲組件又是計算組件,跨層的存在會導致一些問題:
- ClickHouse 中的數據有孤島化傾向,不能被 Spark、Presto 等引擎查詢。
- 數據冗余,Shared-nothing 帶來昂貴的機器成本。
- 繁瑣的數據 ETL。
我們的改進目標是讓 ClickHouse 作為計算組件,直接讀取湖倉數據。

其中存在一些挑戰:
- ClickHouse 目前只支持單機讀取 Hive。
- ClickHouse 支持讀取 Iceberg,但僅限 S3 存儲。
- Iceberg 沒有 C++ 的 API。
- 現在只支持 Hive/Iceberg 外表,一旦表 schema 變化,需要手動同步 DDL 修改。
- 部分場景的 ORC 讀取性能不佳。

針對上述問題,我們采取了如下優化措施:
- 新增外置 HTTP 協議的 Iceberg API server,使用 Java 繞開 C++ 限制,實現外置 server。
- 通過一致性 hash 分發文件路徑到各節點實現分布式讀取。
- 對元信息和數據文件進行 cache。
- 讀取集群和計算集群分離。

增加外庫實現,避免手動繁瑣的建表和元信息不一致問題。

ClickHouse 在讀取某些 ORC 文件時會很慢,例如示例的 select * 和 select count(1)。

通過火焰圖分析,我們發現 Apache Arrow 庫讀取 ORC 有大量的 memcpy,十分影響性能。我們切換到了 Apache ORC 庫進行讀取,整體性能提升了 0.5 到 1 倍。

在某些場景會出現 IO 浪費,如圖中的 select 一列,在 stripe size 為 4MB 和 64MB 時,對應解壓后的大小相等,但 HDFS 讀取量差異很大。

ReadBuffer 在讀取時很容易 cache 大量我們不需要的數據,幫我們緩存很多不需要的列,造成大量 IO 浪費。此外,在讀取時會先讀 stripe footer,再讀 row data,導致頻繁地 HDFS seek。以上這兩點是造成 IO 浪費的主要原因。

我們采用 IO 預讀機制對 ORC 的讀取性能進行優化。首先,ORC 文件可以提前計算文件中哪些 range 是需要被讀取的,基于此,我們將讀取規則改為當讀命中某個 range 時,按照 range 粒度執行預讀,并將臨近 range 進行合并,減少HDFS seek 次數。

在應用該讀取優化后,性能提升十分明顯,以圖中的讀取 6 列為例,原有的 40 秒查詢縮短至 3.7 秒,提升了 10 倍。

此外,我們還做了 HDFS 優化、元信息優化和資源并發鏈接限制,基于這些優化,在典型場景性能提升了 5 到 10 倍。
四、實驗場景 Bitmap 優化
在命中分析、畫像圈選中可以使用 bitmap 進行查詢加速,將原有的交并補邏輯轉換為位圖操作,相比明細表的聚合或 join 查詢,通常可以取得數倍的性能提升。

ClickHouse 數據按行進行拆分運算,在 bitmap 場景中,不用批數據的行數,即使行數相同,其代表的計算工作量也存在很大差異,造成了數據傾斜,其中某個 pipe 的工作量顯著高于別的 pipe,以至拖慢了整個查詢。

我們的解決方案是在執行引擎新增 repartition 階段,重新進行數據均衡,并將數據分發到所有后續 pipe。在大 bitmap 計算中,數據傾斜場景性能提升約 10%~20%。

我們通過 ClickHouse Flame Graph 對三個線程的執行過程進行分析,發現有兩個執行線程長時間等待,而另一個執行線程耗時在讀取 bitmap,讀取開銷遠大于計算。

ClickHouse 在 mark 級以下沒有任何并行化機制,我們針對性優化成支持行級并行讀取,對于大 bitmap 異步進行反序列化讀取,并減少內存拷貝操作。

另外,我們通過對原有字段編碼進行壓實,既節省了存儲空間,又提升了性能。

新增內核特性可編碼字典 Encode Dictionary,支持單機字典和副本同步字典,支持所有原生 ClickHouse 字典函數,支持 value to key 反查,以及 bitmap to bitmap 編碼。

在經過以上優化后,我們在測試數據集上的性能提升很明顯。在 bitmap32 上,求并集和交集有 10 倍的性能提升,在 bitmap64 上,有百倍的提升。

在實際業務應用上,bitmap64 場景從查不了變為查得快,bitmap32 場景從快到更快,在畫像分析、實驗留存分析和表存儲等方面優化效果都很不錯。

五、ClickHouse with AI
隨著機器學習的興起,圖片或文本通過 embedding 高維向量的方式表達,求解相似度會轉換為計算向量間的距離。在離線加工場景使用 OLAP 有很多優勢,比如可以基于元數據過濾、做一些聚合操作,以及配合 UDF 進行加工等等。此外,我們也在精確距離運算、ANN 索引等方面做了一些探索性的優化。

我們基于 ClickHouse 對整套算法鏈路做了重構,融合 OLAP 成熟數據管線,實現了推理、加工和檢索一體化。當有復用需求時,可以直接修改數據管線中的 SQL 配置或 UDF,從而大大降低了使用成本。

我們還做了向量精確檢索查詢優化,將其封裝為 SQL,對于后續的需求可以方便地進行修改迭代。并且對查詢 SQL 進行了性能優化:

- 通過 SQL 改寫,采用 with 代替 join,減少冗余計算;prefilter 提前過濾不必要元素。
- 使用 ZSTD 壓縮,優化數據結構。
- 加入 repartition 階段,解決線程間數據傾斜問題。

另外,我們還優化了 embedding 計算相關函數,在業務場景中取得了 4 倍的性能提升:
- 我們在內核中新增了一個向量距離計算函數 NormalizedCosineDistance,它可以在歸一化場景下減少整體計算量。
- 同時我們也根據業務場景定制了 embedding vector distance 函數,通過大幅減少計算的過程中的 memcpy,性能有了很大的提升。

以上就是本次分享的內容,謝謝大家。

































