Hudi 原理 | 聊一聊 Apache Hudi 的原理(2)

語言: CN / TW / HK

這周我們會接着上週的話題,繼續聊一聊Hudi的實現原理,主要關注Hudi的核心讀寫邏輯,數據的存儲和處理邏輯,以及一些附屬的功能。正在使用或是考慮使用Hudi的朋友, 請不要錯過 ,因為理解了實現原理以後可以避免很多使用上的坑,也能更好地發揮出Hudi的優勢。

今天我們會講一講Hudi這些功能的實現原理:

  • Merge on Read( MOR表

  • Transactional( 事務

  • Incremental Query( 增量查詢

由於這篇文章會用到上一篇文章中講到的知識,還沒有讀過的朋友,推薦先讀完上一篇文章。

Merge on Read (簡稱MO R表),是Hudi最初開源時尚處於“實驗階段”的新功能,在開源後的0.3.5版本開始才告完成。 現在則是Hudi最常用的表類型。

之所以在COW表之後又增加了一種新的表類型,原因在上一篇文章中也有提到

Merge on Read則是對Copy on Write的優化。優化了什麼呢?主要是寫入性能。

導致COW表寫入慢的原因,是因為 COW表每次在寫入時,會把新寫入的數據和老數據合併以後,再寫成新的文件。 單單是寫入的過程(不包含前期的repartition和tagging過程),就包含至少三個步驟:

  1. 讀取老數據的parquet文件(涉及對parquet文件解碼, 不輕鬆

  2. 將老數據和新數據合併

  3. 將合併後的數據重新寫成parquet文件(又涉及parquet文件編碼, 也不輕鬆

就是有點兒不輕鬆

種種原因導致COW表的寫入速度始終快不起來,限制了其在時效性要求高,寫入量巨大的場景下的應用。(關於parquet文件 不輕鬆 的原因,可以看這篇文章《詳解Parquet文件格式》)

為了解決COW表寫入速度上的瓶頸,Hudi採用了另一種寫入方式: upsert時把變更內容寫入log文件,然後定期合併log文件和base文件。 這樣的好處是避免了寫入時讀取老數據,也就避免了parquet文件 不輕鬆 的編解碼過程,只需要把變更記錄寫入一個文件即可(而且是順序寫入)。顯然是 輕鬆了不少

warehouse
├── .hoodie
├── 20220101
│ ├── fileId1_001.parquet
│ ├── .fileId1_20220312163419285.log
│ └── .fileId1_20220312172212361.log
└── 20220102
├── fileId2_001.parquet
└── .fileId2_20220312163512913.log

典型的MOR表的目錄,注意log文件包含寫入的時間戳

有些朋友這時或許會有疑問,“這樣寫入固然是輕鬆了,但怎麼讀到最新的數據呢?” 是個好問題。 為了解決讀取最新數據的問題,Hudi提供了好幾種機制,但從原理上來説只有兩種:

  1. 讀取數據時,同時從base文件和log文件讀取,並把兩邊的數據合併

  2. 定期地、異步地把log文件的數據合併到base文件(這個過程被稱為compaction)

第一種機制也是Merge on Read這個名字的由來,因為 Hudi的讀取過程是實時地把base數據和log數據合併起來,並返回給用户 。注意這兩種機制不是非此即彼的,而是互為補充。Hudi的默認配置就是同時使用這兩種機制,即: 讀取時merge,同時定期地compact。

在讀取時合併數據,聽起來很影響效率。 事實也是如此 ,因為實時合併的實現方式是把所有log文件讀入內存,放在一個HashMap裏,然後遍歷base文件,把base數據和緩存在內存裏的log數據進行join,最後才得到合併後的結果。難免會影響到讀取效率。

COW影響寫入,MOR影響讀取,那有沒有什麼辦法可以兼顧讀寫,魚與熊掌能不能得兼? 目前來説不能 ,好在Hudi把選擇權留給了用户,讓用户可以根據自身的業務需求,選擇不同的query類型。

對於MOR表,Hudi支持3種query類型,分別是

  1. Snapshot Query

  2. Incremental Query

  3. Read Optimized Query

其中1和3就是為了平衡讀和寫之間的取捨。這兩者的區別是:Snapshot Query和上文所説的一樣,讀取時進行“實時合併”;Read Optimized Query則不同, 只讀取base文件,不讀取log文件 ,因此讀取效率和COW表相同,但讀到的數據可能不是最新的。

官方對兩種query類型的解釋

以上講完了Hudi和upsert相關的主要功能,接下來講講Hudi另一大特色功能: Transactional ,也就是 事務功能

Hudi的事務功能被稱為Timeline,因為Hudi把所有對一張表的操作都保存在一個時間線對象裏面。Hudi官方文檔中對於Timeline功能的介紹稍微有點複雜,不是很清晰。其實從用户角度來看的話,Hudi提供的事務相關能力主要是這些:

特性
原子性 寫入即使失敗,也不會造成數據損壞
隔離性 讀寫分離,寫入不影響讀取,不會讀到寫入中途的數據
回滾 可以回滾變更,把數據恢復到舊版本
時間旅行 可以讀取舊版本的數據(但太老的版本會被清理掉)
存檔 可以長期保存舊版本數據(存檔的版本不會被自動清理)
增量讀取 可以讀取任意兩個版本之間的差分數據

講完了功能清單,接下來就講一講事務的實現原理。內容以 COW表為主,但MOR表也可以由此類推,因為MOR表本質上是對COW表的優化。

這裏沿用上一篇文章中的例子,假設初始我們有5條數據,內容如下

txn_id user_id item_id amount date
1 1 1 2 20220101
2 2 1 1 20220101
3 1 2 3 20220101
4 1 3 1 20220102
5 2 3 2 20220102

實際存儲的目錄結構是這樣的(文件名做了簡化)

warehouse
├── .hoodie
├── 20220101
│ ├── fileId1_001.parquet
│ └── fileId1_002.parquet
├── 20220102
│ └── fileId2_001.parquet
└── 20220103
└── fileId3_001.parquet

的數據保存在fileId1_001和fileId2_001兩個文件裏。

我們稱呼這個版本為v1。 接下來我們寫入3條新的數據,其中1條是更新,2條是新增。

txn_id user_id item_id amount date
3 1 2 5 20220101
6 1 4 1 20220103
7 2 3 2 20220103

寫入後的目錄結構如下

warehouse
├── .hoodie
├── 20220101
│ ├── fileId1_001.parquet
│ └── fileId1_002.parquet
├── 20220102
│ └── fileId2_001.parquet
└── 20220103
└── fileId3_001.parquet

更新的1條數據(txn_id=3)保存在fileId1_002這個文件裏,而新增的2條數據(txn_id=6和txn_id=7)則被保存在fileId3_001。

我們稱呼更新後的版本為v2。

Hudi在這張表的timeline裏(實際存放在.hoodie目錄下)會記錄下v1和v2對應的文件列表。 當client讀取數據時,首先會查看timeline裏最新的commit是哪個,從最新的commit裏獲得對應的文件列表,再去這些文件讀取真正的數據。

v1和v2對應的文件

Hudi通過這種方式實現了多版本隔離的能力。當一個client正在讀取v1的數據時,另一個client可以同時寫入新的數據,新的數據會被寫入新的文件裏,不影響v1用到的數據文件。只有當數據全部寫完以後,v2才會被commit到timeline裏面。後續的client再讀取時,讀到的就是v2的數據。

順帶一提的是,儘管Hudi具備多版本數據管理的能力,但舊版本的數據不會無限制地保留下去。Hudi會在新的commit完成時開始清理舊的數據,默認的策略是“ 清理早於10個commit前的數據 ”。

最後再講講Hudi的另一個特色功能: Incremental Query( 增量查詢 )。 這個功能提供給用户“ 讀取任意兩個commit之間差分數據 的能力。 這個功能也是基於上述的“多版本數據管理”實現的,下面就來講講。

還是以上文的例子,假設我們想要讀取v1 → v2之間的差分數據

Hudi會計算出v2到v1之間的差異是兩個文件:fileId01_002和fileId03_001,然後client從這兩個文件中讀到的就是增量數據。

有些朋友或許會發現,fileId01_002裏面包含了兩條老數據txn_id=1和txn_id=2,不屬於v2到v1的差分數據,不應該被讀取。確實如此。其實Hudi對每一條數據,都有 一個隱藏字段_hoodie_commit_time用於記錄commit時間 ,這個字段會和其他數據字段一起保存在parquet文件裏。Hudi在讀取parquet文件時,會同時用這個字段對結果進行過濾,把不屬於時間範圍內的記錄都過濾掉。

關於Hudi原理的講解 寫到這裏就 差不多告一段落了 接下來可能會繼續介紹”數據湖三劍客“的其他兩個——Iceberg和Delta,以及它們之間的對比。 如果朋友們對接下來想看到什麼內容有好的建議,歡迎在公眾號後台留言。