天天看點

HyperLogLog在Presto和ClickHouse中的相容及性能差異

前言

目前HyperLogLog是一種主流的算法,用于估算海量同類型資料的不同值,是以幾乎所有的計算/查詢引擎都有了想關的實作,當然雖然可能其它的優化算法,但算法主體相同,然而不同引擎實作的存儲過程大同小異,如果想要在不同引擎之前共享中間結果,就需要深入了解不同引擎的存儲實作。

Presto是Facebook開源的,完全基于記憶體的并⾏計算,分布式SQL互動式查詢引擎是一種Massively parallel processing (MPP)架構,多個節點管道式執⾏⽀持任意資料源(通過擴充式Connector元件),資料規模GB~PB級。

ClickHouse 是一個真正的列式資料庫管理系統(DBMS)。在 ClickHouse 中,資料始終是按列存儲的,包括矢量(向量或列塊)執行的過程。隻要有可能,操作都是基于矢量進行分派的,而不是單個的值,這被稱為«矢量化查詢執行»,它有利于降低實際的資料處理開銷。

但礙于Presto在實際工作中會出現不穩定性的情況,便推動了Presto HLL計算的遷移,以期更加快速地響應使用者的查詢請求,這便是這篇部落格的背景。

HLL資料共享流程

Presto/Hive上的基數預估的中間結果,實際上是一組格式化的位元組數組,而ClickHouse并不支援直接加載HLL的位元組數組,是以就需要擴充ClickHouse的聚合函數,即開發UDAF(自己寫C++代碼啦,目前CH并不是很友善地編寫UDF/UDAF),完成Presto引擎上計算産生的中間結果的讀取加載過程。至于CH上的記憶體資料存儲格式,依然采用CH本身的實作,以減少工作量。

但需要注意的是,CH即使已經能夠直接加載來自Presto的HLL資料,考慮到Presto和CH在計算HyperLogLog時采用的Hash邏輯不同,一個是小端一個大端,依然不能直接聚合兩個引擎各自産生的中間數組,是以目前的模式是單向的,未來考慮雙向聚合。

簡言之,整個過程就是Presto HLL序列化 => 自定義資料格式 => 寫入CH => 調用CH UDAF聚合,最終完成Presto HLL資料在CH上的聚合過程。

HLL聚合性能比較

Presto

資料總量(條) HLL資料讀取以及merge耗時(機關秒)
100000 4
200000 9
500000 23
1000000 44

ClickHouse

資料總量 HLL資料讀取及Merge耗時(機關秒)
100000 5
200000 18
500000 46
1000000 91

性能差異分析

源表(viprpdm.ads_mer_high_dim_detail_df)靜态統計資訊:

select count(*) from test_table where dt=‘20210111’;

dt分區的資料總量N_total=2675032

select count(*) from test_table where dt=‘20210111’ and length(test_hll) <=2060;

dt分區中位元組數組長度小于等于2060的總量N_2060=2670580

select count(*) from test_table where dt=‘20210111’ and length(test_hll) <= 1024;

dt分區中位元組數組長度小于等于1024的總量N_1024=1112470

其中rate_1024 = N_1024 / N_total = 0.416,大約占總量的一半

由上的統計資訊可以看到,源表中的HLL資料的位元組數組長度,基本上所有的字段值的長度都約為4096個桶的一半,這裡以Dense類型的存儲格式為主說明性能差異的原因。

一般地Dense類型的資料,會使用一個與桶數相同長度的資料,來儲存每一個value,舉例4096個桶,就對應了4096個slot的數組,其中每個slot會存入一個6位長度的數來表示不同值。

應用程式中最本的計算單元是按位元組來處理,而一個位元組需要8位的比特表示,是以Presto為了更能高效地來存儲和處理HyperLogLog計算的中間結果,會将桶中應該存放的6位值對齊到

4位,即VALUE_BITS >> 1,這樣桶數也會降低到原來的一半BUCKET_BITS >> 1,那麼在不考慮溢出的情況下,就隻需要[2048 * 4 / 8 = 1024, 4096 * 6/8]範圍内的存儲就可以完成計算。

由于對6位的值對齊到4位,必然會出現實際值溢出問題,即某個桶的值超過了2^4,Presto内部會建立一個溢出數組,來儲存溢出的值,這樣依然在溢出的場景能夠很好的工作,雖然這樣的做法相對于直接使用一個位元組來表示桶值複雜,但通常情況下溢出的現象不會很頻繁,是以這種設計在更普遍的情況下會帶來存儲和計算的性能提升。

總結

綜上,目前從Presto HLL資料到CK的中間結果的存儲格式,采用一個位元組來存放完整的桶值,是以抛卻必要的頭部資訊外,需要4096*6/8=3072 bytes。

實際上CK本身在自己的HyperLogLog實作中,也是采用了6位表示的存儲格式,但總的數組大小為(4096*6 + 7)/8,是以可能在大部分情況下,HLL計算性優劣如下:

中間轉換格式 < CK存儲格式 < Presto存儲格式

是以,如果想要更好地打通ClickHouse與Presto之前的HyperLogLog資料,在應用層後續重點工作當是完善ClickHouse側加載Presto HLL資料的過程。

參考項目位址:

繼續閱讀