基于 Apache Hudi 極致查詢優化的探索實踐( 二 )


(2)高性能FileList
在查詢超大規模數據集時,FileList是不可避免的操作,在 HDFS 上該操作耗時還可以接受,一旦涉及到對象存儲,大規模 FileList 效率極其低下 , Hudi 引入 MDT 將文件信息直接保存在下來 , 從而避免了大規模FileList 。

基于 Apache Hudi 極致查詢優化的探索實踐

文章插圖
Presto 與 Hudi的集成HetuEngine(Presto)作為數據湖對外出口引擎,其查詢 Hudi 能力至關重要 。對接這塊我們主要針對點查和復雜查詢做了不同的優化,下文著重介紹點查場景 。在和 Hudi 集成之前首先要解決如下問題
  1. 如何集成 Hudi,在 Hive Connector 直接魔改,還是使用獨立的 Hudi Connector?
  2. 支持哪些索引做 DataSkipping?
  3. DataSkipping 在 Coordinator 側做還是在 Worker 端做?
問題1: 經過探討我們決定使用 Hudi Connector承載本次優化 。當前社區的 Connector 還略優不足,缺失一些優化包括統計信息、Runtime Filter、Filter不能下推等導致 TPC-DS 性能不是很理想 , 我們在本次優化中重點優化了這塊,后續相關優化會推給社區 。
問題2: 內部 HetuEngine 其實已經支持 Bitmap 和二級索引,本次重點集成了 MDT 的 Column statistics和 BloomFilter 能力,利用 Presto下推的 Filter 直接裁剪文件 。
問題3: 關于這個問題我們做了測試,對于 column 統計信息來說,總體數據量并不大,1w 個文件統計信息大約幾M,加載到 Coordinator 內存完全沒有問題,因此選擇在 Coordinator 側直接做過濾 。
基于 Apache Hudi 極致查詢優化的探索實踐

文章插圖
對于 BloomFilter、Bitmap 就完全不一樣了,測試結果表明 1.4T 數據產生了 1G 多的 BloomFilter 索引 , 把這些索引加載到 Coordinator 顯然不現實 。我們知道 Hudi MDT 的 BloomFilter 實際是存在 HFile里,HFile點查十分高效,因此我們將 DataSkipping 下壓到 Worker 端 , 每個 Task 點查 HFile 查出自己的 BloomFilter 信息做過濾 。
基于 Apache Hudi 極致查詢優化的探索實踐

文章插圖
點查場景測試測試數據我們采用和 ClickHouse 一樣的SSB數據集進行測試,數據規模1.5T,120億條數據 。
$ ./dbgen -s 2000 -T c$ ./dbgen -s 2000 -T l$ ./dbgen -s 2000 -T p$ ./dbgen -s 2000 -T s測試環境1CN+3WN Container 170GB , 136GB JVM heap, 95GB Max Query Memory,40vcore
數據處理利用 Hudi 自帶的 Hilbert 算法直接預處理數據后寫入目標表,這里 Hilbert 算法指定 S_CITY,C_CITY,P_BRAND, LO_DISCOUNT作為排序列 。
SpaceCurveSortingHelper.orderDataFrameBySamplingValues(df.withColumn("year", expr("year((LO_ORDERDATE))")), LayoutOptimizationStrategy.HILBERT, Seq("S_CITY", "C_CITY", "P_BRAND", "LO_DISCOUNT"), 9000).registerTempTable("hilbert")spark.sql("insert into lineorder_flat_parquet_hilbert select * from hilbert")測試結果使用冷啟動方式,降低 Presto 緩存對性能的影響 。
SSB Query
基于 Apache Hudi 極致查詢優化的探索實踐

文章插圖
文件讀取量
基于 Apache Hudi 極致查詢優化的探索實踐

文章插圖
  1. 對于所有 SQL 我們可以看到 2x - 11x 的性能提升,FileSkipping 效果更加明顯過濾掉的文件有 2x - 200x 的提升 。
  2. 即使沒有 MDT ,Presto 強大的 Rowgroup 級別過濾,配合 Hilbert 數據布局優化也可以很好地提升查詢性能 。
  3. SSB模型掃描的列數據都比較少,實際場景中如果掃描多個列 Presto + MDT+ Hilbert 的性能可以達到 30x 以上 。
  4. 測試中同樣發現了MDT的不足,120億數據產生的MDT表有接近50M,加載到內存里面需要一定耗時,后續考慮給MDT配置緩存盤加快讀取效率 。
關于 BloomFilter 的測試,由于 Hudi 只支持對主鍵構建 BloomFilter,因此我們構造了1000w 數據集做測試
spark.sql( """ |create table prestoc( |c1 int, |c11 int, |c12 int, |c2 string, |c3 decimal(38, 10), |c4 timestamp, |c5 int, |c6 date, |c7 binary, |c8 int |) using hudi |tblproperties ( |primaryKey = 'c1', |preCombineField = 'c11', |hoodie.upsert.shuffle.parallelism = 8, |hoodie.table.keygenerator.class = 'org.apache.hudi.keygen.SimpleKeyGenerator', |hoodie.metadata.enable = "true", |hoodie.metadata.index.column.stats.enable = "true", |hoodie.metadata.index.column.stats.file.group.count = "2", |hoodie.metadata.index.column.stats.column.list = 'c1,c2', |hoodie.metadata.index.bloom.filter.enable = "true", |hoodie.metadata.index.bloom.filter.column.list = 'c1', |hoodie.enable.data.skipping = "true", |hoodie.cleaner.policy.failed.writes = "LAZY", |hoodie.clean.automatic = "false", |hoodie.metadata.compact.max.delta.commits = "1" |) | |""".stripMargin)

推薦閱讀