從 Hudi 0.10.0版本開始,我們很高興推出在數據庫領域中稱為 Z-Order和 Hilbert 空間填充曲線的高級數據布局優化技術的支持。
1. 背景
Amazon EMR 團隊最近發表了一篇很不錯的文章[1]展示了對數據進行聚簇[2]是如何提高查詢性能的,為了更好地了解發生了什麼以及它與空間填充曲線的關係,讓我們仔細研究該文章的設置。
文章中比較了 2 個 Apache Hudi 表(均來自Amazon Reviews 數據集[3]):
•未聚簇的 amazon_reviews 表(即數據尚未按任何特定鍵重新排序)•amazon_reviews_clustered 聚簇表。當數據被聚簇後,數據按字典順序排列(這裡我們將這種排序稱為線性排序),排序列為star_rating、total_votes兩列(見下圖)
為了展示查詢性能的改進,對這兩個表執行以下查詢:
這裡要指出的重要考慮因素是查詢指定了排序的兩個列(star_rating 和 total_votes)。但不幸的是這是線性/詞典排序的一個關鍵限制,如果添加更多列,排序的價值會會隨之減少。
從上圖可以看到,對於按字典順序排列的 3 元組整數,只有第一列能夠對所有具有相同值的記錄具有關鍵的局部性屬性:例如所有記錄都具有以「開頭的值」 1"、"2"、"3"(在第一列中)很好地聚簇在一起。但是如果嘗試在第三列中查找所有值為"5"的值,會發現這些值現在分散在所有地方,根本沒有局部性,過濾效果很差。
提高查詢性能的關鍵因素是局部性:它使查詢能夠顯着減少搜索空間和需要掃描、解析等的文件數量。
但是這是否意味着如果我們按表排序的列的第一個(或更準確地說是前綴)以外的任何內容進行過濾,我們的查詢就註定要進行全面掃描?不完全是,局部性也是空間填充曲線在枚舉多維空間時啟用的屬性(我們表中的記錄可以表示為 N 維空間中的點,其中 N 是我們表中的列數)
那麼它是如何工作的?我們以 Z 曲線為例:擬合二維平面的 Z 階曲線如下所示:
可以看到按照路徑,不是簡單地先按一個坐標 ("x") 排序,然後再按另一個坐標排序,它實際上是在對它們進行排序,就好像這些坐標的位已交織成單個值一樣:
在線性排序的情況下局部性僅使用第一列相比,該方法的局部性使用到所有列。
以類似的方式,希爾伯特曲線允許將 N 維空間中的點(我們表中的行)映射到一維曲線上,基本上對它們進行排序,同時仍然保留局部性的關鍵屬性,在此處[4]閱讀有關希爾伯特曲線的更多詳細信息,到目前為止我們的實驗表明,使用希爾伯特曲線對數據進行排序會有更好的聚簇和性能結果。
現在讓我們來看看它的實際效果!
2. 設置
我們將再次使用Amazon Reviews 數據集[5],但這次我們將使用 Hudi 按product_id、customer_id列元組進行 Z-Order排序,而不是聚簇或線性排序。
數據集不需要特別的準備,可以直接從 S3 中以 Parquet 格式下載並將其直接用作 Spark 將其攝取到 Hudi 表。
啟動spark-shell
./bin/spark-shell--master'local[4]'--driver-memory8G--executor-memory8G\--jars../../packaging/hudi-spark-bundle/target/hudi-spark3-bundle_2.12-0.10.0.jar\--packagesorg.apache.spark:spark-avro_2.12:2.4.4\--conf'spark.serializer=org.apache.spark.serializer.KryoSerializer'
導入Hudi表
importorg.apache.hadoop.fs.{FileStatus,Path}importscala.collection.JavaConversions._importorg.apache.spark.sql.SaveMode._importorg.apache.hudi.{DataSourceReadOptions,DataSourceWriteOptions}importorg.apache.hudi.DataSourceWriteOptions._importorg.apache.hudi.common.fs.FSUtilsimportorg.apache.hudi.common.table.HoodieTableMetaClientimportorg.apache.hudi.common.util.ClusteringUtilsimportorg.apache.hudi.config.HoodieClusteringConfigimportorg.apache.hudi.config.HoodieWriteConfig._importorg.apache.spark.sql.DataFrameimportjava.util.stream.CollectorsvallayoutOptStrategy="z-order";//OR"hilbert"valinputPath=s"file:///${System.getProperty("user.home")}/datasets/amazon_reviews_parquet"valtableName=s"amazon_reviews_${layoutOptStrategy}"valoutputPath=s"file:///tmp/hudi/$tableName"defsafeTableName(s:String)=s.replace('-','_')valcommonOpts=Map("hoodie.compact.inline"->"false","hoodie.bulk_insert.shuffle.parallelism"->"10")//////////////////////////////////////////////////////////////////WritingtoHudi////////////////////////////////////////////////////////////////valdf=spark.read.parquet(inputPath)df.write.format("hudi").option(DataSourceWriteOptions.TABLE_TYPE.key(),COW_TABLE_TYPE_OPT_VAL).option("hoodie.table.name",tableName).option(PRECOMBINE_FIELD.key(),"review_id").option(RECORDKEY_FIELD.key(),"review_id").option(DataSourceWriteOptions.PARTITIONPATH_FIELD.key(),"product_category").option("hoodie.clustering.inline","true").option("hoodie.clustering.inline.max.commits","1")//NOTE:Smallfilelimitisintentionallykept_ABOVE_targetfile-sizemaxthresholdforClustering,//toforcere-clustering.option("hoodie.clustering.plan.strategy.small.file.limit",String.valueOf(1024*1024*1024))//1Gb.option("hoodie.clustering.plan.strategy.target.file.max.bytes",String.valueOf(128*1024*1024))//128Mb//NOTE:We'reincreasingcaponnumberoffile-groupsproducedaspartoftheClusteringruntobeabletoaccommodateforthe//wholedataset(~33Gb).option("hoodie.clustering.plan.strategy.max.num.groups",String.valueOf(4096)).option(HoodieClusteringConfig.LAYOUT_OPTIMIZE_ENABLE.key,"true").option(HoodieClusteringConfig.LAYOUT_OPTIMIZE_STRATEGY.key,layoutOptStrategy).option(HoodieClusteringConfig.PLAN_STRATEGY_SORT_COLUMNS.key,"product_id,customer_id").option(DataSourceWriteOptions.OPERATION.key(),DataSourceWriteOptions.BULK_INSERT_OPERATION_OPT_VAL).option(BULK_INSERT_SORT_MODE.key(),"NONE").options(commonOpts).mode(ErrorIfExists)3. 測試
每個單獨的測試請在單獨的 spark-shell 中運行,以避免緩存影響測試結果。
//////////////////////////////////////////////////////////////////Reading/////////////////////////////////////////////////////////////////TempTablew/DataSkippingDISABLEDvalreadDf:DataFrame=spark.read.option(DataSourceReadOptions.ENABLE_DATA_SKIPPING.key(),"false").format("hudi").load(outputPath)valrawSnapshotTableName=safeTableName(s"${tableName}_sql_snapshot")readDf.createOrReplaceTempView(rawSnapshotTableName)//TempTablew/DataSkippingENABLEDvalreadDfSkip:DataFrame=spark.read.option(DataSourceReadOptions.ENABLE_DATA_SKIPPING.key(),"true").format("hudi").load(outputPath)valdataSkippingSnapshotTableName=safeTableName(s"${tableName}_sql_snapshot_skipping")readDfSkip.createOrReplaceTempView(dataSkippingSnapshotTableName)//Query1:Totalvotesbyproduct_category,for6monthsdefrunQuery1(tableName:String)={//Query1:Totalvotesbyproduct_category,for6monthsspark.sql(s"SELECTsum(total_votes),product_categoryFROM$tableNameWHEREreview_date>'2013-12-15'ANDreview_date<'2014-06-01'GROUPBYproduct_category").show()}//Query2:Averagestarratingbyproduct_id,forsomeproductdefrunQuery2(tableName:String)={spark.sql(s"SELECTavg(star_rating),product_idFROM$tableNameWHEREproduct_idin('B0184XC75U')GROUPBYproduct_id").show()}//Query3:Countnumberofreviewsbycustomer_idforsome5customersdefrunQuery3(tableName:String)={spark.sql(s"SELECTcount(*)asnum_reviews,customer_idFROM$tableNameWHEREcustomer_idin('53096570','10046284','53096576','10000196','21700145')GROUPBYcustomer_id").show()}////Query1:Isa"wide"queryandhenceit'sexpectedtotouchalotoffiles//scala>runQuery1(rawSnapshotTableName)+----------------+--------------------+|sum(total_votes)|product_category|+----------------+--------------------+|1050944|PC||867794|Kitchen||1167489|Home||927531|Wireless||6861|Video||39602|Digital_Video_Games||954924|Digital_Video_Dow...||81876|Luggage||320536|Video_Games||817679|Sports||11451|Mobile_Electronics||228739|Home_Entertainment||3769269|Digital_Ebook_Pur...||252273|Baby||735042|Apparel||49101|Major_Appliances||484732|Grocery||285682|Tools||459980|Electronics||454258|Outdoors|+----------------+--------------------+onlyshowingtop20rowsscala>runQuery1(dataSkippingSnapshotTableName)+----------------+--------------------+|sum(total_votes)|product_category|+----------------+--------------------+|1050944|PC||867794|Kitchen||1167489|Home||927531|Wireless||6861|Video||39602|Digital_Video_Games||954924|Digital_Video_Dow...||81876|Luggage||320536|Video_Games||817679|Sports||11451|Mobile_Electronics||228739|Home_Entertainment||3769269|Digital_Ebook_Pur...||252273|Baby||735042|Apparel||49101|Major_Appliances||484732|Grocery||285682|Tools||459980|Electronics||454258|Outdoors|+----------------+--------------------+onlyshowingtop20rows////Query2:Isa"pointwise"queryandhenceit'sexpectedthatdata-skippingshouldsubstantiallyreducenumber//offilesscanned(ascomparedtoBaseline)////NOTE:ThatLinearOrdering(ascomparedtoSpace-curvebasedon)willhavesimilareffectonperformancereducing//total#ofParquetfilesscanned,sincewe'requeryingontheprefixoftheorderingkey//scala>runQuery2(rawSnapshotTableName)+----------------+----------+|avg(star_rating)|product_id|+----------------+----------+|1.0|B0184XC75U|+----------------+----------+scala>runQuery2(dataSkippingSnapshotTableName)+----------------+----------+|avg(star_rating)|product_id|+----------------+----------+|1.0|B0184XC75U|+----------------+----------+////Query3:SimilartoQ2,isa"pointwise"query,butqueryingotherpartoftheordering-key(product_id,customer_id)//andhenceit'sexpectedthatdata-skippingshouldsubstantiallyreducenumberoffilesscanned(ascomparedtoBaseline,LinearOrdering).////NOTE:ThatLinearOrdering(ascomparedtoSpace-curvebasedon)will_NOT_havesimilareffectonperformancereducing//total#ofParquetfilesscanned,sincewe'reNOTqueryingontheprefixoftheorderingkey//scala>runQuery3(rawSnapshotTableName)+-----------+-----------+|num_reviews|customer_id|+-----------+-----------+|50|53096570||3|53096576||25|10046284||1|10000196||14|21700145|+-----------+-----------+scala>runQuery3(dataSkippingSnapshotTableName)+-----------+-----------+|num_reviews|customer_id|+-----------+-----------+|50|53096570||3|53096576||25|10046284||1|10000196||14|21700145|+-----------+-----------+4. 結果
我們總結了以下的測試結果
可以看到多列線性排序對於按列(Q2、Q3)以外的列進行過濾的查詢不是很有效,這與空間填充曲線(Z-order 和 Hilbert)形成了非常明顯的對比,後者將查詢時間加快多達3倍。值得注意的是性能提升在很大程度上取決於基礎數據和查詢,在我們內部數據的基準測試中,能夠實現超過11倍的查詢性能改進!
5. 總結
Apache Hudi v0.10 為開源帶來了新的布局優化功能 Z-order 和 Hilbert。使用這些行業領先的布局優化技術可以為用戶查詢帶來顯着的性能提升和成本節約!
推薦閱讀
1. 來自Apache Hudi PMC Chair的新年大禮包,請注意查收!(附帶2021年精選文章集合)
2. 基於Apache Hudi + Flink的億級數據入湖實踐
3. 基於Apache Hudi構建智能湖倉實踐(附亞馬遜工程師代碼)
4. OnZoom基於Apache Hudi的流批一體架構實踐
5. 移動雲基於Apache Hudi湖倉一體的探索與實踐
引用鏈接
[1]文章:https://aws.amazon.com/blogs/big-data/new-features-from-apache-hudi-0-7-0-and-0-8-0-available-on-amazon-emr/[2]聚簇:https://hudi.apache.org/docs/clustering[3]Amazon Reviews 數據集:https://s3.amazonaws.com/amazon-reviews-pds/tsv/index.txt[4]此處:https://drum.lib.umd.edu/handle/1903/804[5]Amazon Reviews 數據集:https://s3.amazonaws.com/amazon-reviews-pds/readme.html