資料湖框架選型很糾結?一文了解Apache Hudi核心優勢

leesf發表於2021-01-01

英文原文:https://hudi.apache.org/blog/hudi-indexing-mechanisms/

Apache Hudi使用索引來定位更刪操作所在的檔案組。對於Copy-On-Write表,索引能加快更刪的操作,因為避免了通過連線整個資料集來決定哪些檔案需要重寫。對於Merge-On-Read表,這個設計,對於任意給定的基檔案,能限定要與其合併的記錄數量。具體地,一個給定的基檔案只需要和其所包含的記錄的更新合併。相比之下,沒有索引的設計(比如Apache Hive ACID),可能會導致需要把所有基檔案與所有更刪操作合併。

從高角度看,索引把一個記錄的鍵加一個可選的分割槽路徑對映到儲存上的檔案組ID(更多細節參考這裡)。在寫入過程中,我們查詢這個對映然後把更刪操作導向基檔案附帶的日誌檔案(MOR表),或者導向最新的需要被合併的基檔案(COW表)。索引也使得Hudi可以根據記錄鍵來規定一些唯一性的限制。

記錄更新(黃色塊)和基檔案(白色塊)合併消耗的對比

目前Hudi已經支援了幾種不同的索引機制,並且在工具庫中不斷地完善和增加更多機制,在本文餘下的篇幅中將根據我們的經驗介紹幾種不同作業場景下索引機制的選擇。我們也會穿插一些對已有限制、即將開展的工作、以及優化和權衡方面的評論。

Hudi的索引型別

目前Hudi支援以下幾種索引型別。

  • 布隆索引(預設):使用以記錄的鍵生成的布隆過濾器,也可以用記錄鍵對可能對應的檔案進行剪枝操作。
  • 簡單索引:對更刪的記錄和儲存上的表裡提取的鍵進行輕量級的連線。
  • HBase索引:使用外部的Apache HBase表來管理索引對映。

寫入器可以通過hoodie.index.type來設定以上的型別。此外,自定義的索引實現可以通過hoodie.index.class來配置。對Apache Spark寫入器需提供SparkHoodieIndex的子類。

另一個需要了解的關鍵點是區分全域性索引和非全域性索引。布隆索引簡單索引都有一個全域性選項,分別是hoodie.index.type=GLOBAL_BLOOMhoodie.index.type=GLOBAL_SIMPLEHBase索引本質上就是全域性索引。

  • 全域性索引:全域性索引在全表的所有分割槽範圍下強制要求鍵的唯一性,也就是確保對給定的鍵有且只有一個對應的記錄。全域性索引提供了更強的保證,而更刪的消耗隨著表的大小增加而增加(O(表的大小)),但仍可能對小表適用。
  • 非全域性索引:這個預設的索引實現只在一個分割槽裡強制要求了這樣的限制。由此可見,非全域性索引依靠寫入器為同一個記錄的更刪提供一致的分割槽路徑,但由此同時大幅提高了效率,因為索引查詢複雜度成了O(更刪的記錄數量)且可以很好地應對寫入量的擴充套件。

由於資料可能有著不同的體量、速度和讀取規律,不同索引會適用於不同的作業場景。接下來讓我們分析幾個不同的場景來討論如何選擇適合的Hudi索引。

作業場景:對事實表的延遲更新

許多公司會在NoSQL資料儲存中存放大量的交易資料。例如共享出行的行程表、股票買賣記錄的表、和電商的訂單表。這些表通常一直在增長,且大部分的更新隨機發生在較新的記錄上,而對舊記錄有著長尾分佈型的更新。這通常是源於交易關閉或者資料更正的延遲性。換句話說,大部分更新會發生在最新的幾個分割槽上而小部分會在舊的分割槽。

典型的事實表的更新樣式。

對於這樣的作業模式,布隆索引就能表現地很好,因為查詢索引可以靠設定得當的布隆過濾器來剪枝很多資料檔案。另外,如果生成的鍵可以以某種順序排列,參與比較的檔案數會進一步通過範圍剪枝而減少。Hudi用所有檔案的鍵域來構造區間樹,這樣能來高效地依據輸入的更刪記錄的鍵域來排除不匹配的檔案。

為了高效地把記錄鍵和布隆過濾器進行比對,即儘量減少過濾器的讀取和均衡執行器間的工作量,Hudi快取了輸入記錄並使用了自定義分割槽器和統計規律來解決資料的偏斜。有時,如果布隆過濾器的偽正率過高,查詢會增加資料的打亂操作。Hudi支援動態布隆過濾器(設定hoodie.bloom.index.filter.type=DYNAMIC_V0)。它可以根據檔案裡存放的記錄數量來調整大小從而達到設定的偽正率。

在不久的將來,我們計劃引入一個更快的布隆索引機制。該機制會在Hudi內部的後設資料表中跟蹤記錄布隆過濾器和取值範圍從而進行快速的點查詢。這可以避免因從基檔案中讀取布隆過濾器和取值範圍而導致的查詢的侷限性。(總體設計請參考RFC-15

作業場景:對事件表的去重

事件流無處不在。從Apache Kafka或其他類似的訊息匯流排發出的事件數通常是事實表大小的10-100倍。事件通常把時間(到達時間、處理時間)作為首類處理物件,比如物聯網的事件流、點選流資料、廣告曝光數等等。由於這些大部分都是僅追加的資料,插入和更新只存在於最新的幾個分割槽中。由於重複事件可能發生在整個資料管道的任一節點,在存放到資料湖前去重是一個常見的需求。

上圖演示了事件表的更新分佈情況。

總的來說,低消耗去重一個非常挑戰的工作。儘管甚至可以用一個鍵值儲存來實現去重(即HBase索引),但索引儲存的消耗會隨著事件數增長而線性增長以至於變得不可行。事實上,有範圍剪枝功能的布隆索引是最佳的解決方案。我們可以利用作為首類處理物件的時間來構造由事件時間戳和事件id(event_ts+event_id)組成的鍵,這樣插入的記錄就有了單調增長的鍵。這會在最新的幾個分割槽裡大幅提高剪枝檔案的效益。

作業場景:對維度表的隨機更刪

這種型別的表通常包含高緯度的資料和資料連結,比如使用者資料、商家資訊等。這些都是高保真度的表。它們的更新量通常很小但所接觸的分割槽和資料檔案會很多,範圍涉及從舊到新的整個資料集。有時因為沒有很好的分割槽條件,這些表也會不分割槽。

上圖演示了維度表的更新分佈情況。

正如之前提到的,如果範圍比較不能剪枝許多檔案的話,那麼布隆索引並不能帶來很好的效益。在這樣一個隨機寫入的作業場景下,更新操作通常會觸及表裡大多數檔案從而導致布隆過濾器依據輸入的更新對所有檔案標明真正(true positive)。最終會導致,即使採用了範圍比較,也還是檢查了所有檔案。使用簡單索引對此場景更合適,因為它不採用提前的剪枝操作,而是直接和所有檔案的所需欄位連線。如果額外的運維成本可以接受的話,也可以採用HBase索引,其對這些表能提供更加優越的查詢效率。

當使用全域性索引時,使用者也可以考慮通過設定hoodie.bloom.index.update.partition.path=true或者hoodie.simple.index.update.partition.path=true(Global Bloom)hoodie.hbase.index.update.partition.path=true或者hoodie.hbase.index.update.partition.path=true 或者來處理分割槽路徑需要更新的情況;例如對於以所在城市分割槽的使用者表,會有使用者遷至另一座城市的情況。這些表也非常適合採用Merge-On-Read表型。

將來我們計劃在Hudi內實現記錄層的索引機制,以此提高索引查詢效率,同時也將省去維護額外系統(比如HBase)的開銷。

總結

若沒有索引功能,Hudi就不可能在超大擴充套件規模上實現更刪操作。希望這篇文章為目前的索引機制提供了足夠的背景知識和對不同權衡取捨的解釋。

以下是一些頗具意義的相關工作:

  • 基於Apache Flink並建立在RocksDB狀態儲存上的索引機制將帶來真正意義上的資料湖流式插入更新。
  • 全新的後設資料索引將基於Hudi後設資料全面翻新現有的布隆索引機制。
  • 記錄層的索引實現,用另一個Hudi表作為二級索引。

在接下來的開發中,專案組會對這個領域保持積極的投入。我們始終期待更多貢獻者的加入以及推進路線圖中的專案。如果有意參與,歡迎與我們的社群聯絡

相關文章