如何不加鎖地將資料並行寫入Apache Hudi?

2023-07-09 18:00:38

最近一位 Hudi 使用者詢問他們是否可以在不需要任何鎖的情況下同時從多個寫入端寫入單個 Hudi 表。 他們場景是一個不可變的工作負載。 一般來說對於任何多寫入端功能,Hudi 建議啟用鎖定設定。 但這是一個有趣的問題,我們進行探索並找到了解決方案,因此與更廣泛的社群分享。

需要並行寫入的鎖提供程式

對於某些場景來說可能是必要的,但可能並不適合所有場景。 因此我們首先看看為什麼當並行寫入Hudi 或任何表格式時我們需要鎖提供程式。 如果兩個並行寫入修改同一組資料,我們只能允許其中一個成功並中止另一個,因為至少與樂觀並行控制(OCC)存在衝突。 我們可以嘗試設計和實現基於 MVCC 的模型,但當前還沒有做到這一點。 因此僅使用純 OCC,任何兩個並行寫入重疊資料都無法成功。 因此為了解決衝突和某些表管理服務,我們需要鎖,因為在任何時間點只有其中一個可以操作臨界區。 因此我們採用鎖提供程式來確保兩個寫入之間協調此類衝突解決和表管理服務。總結如下

  1. 出於解決衝突的目的,我們不會讓兩個寫入端成功寫入重疊的資料。
  2. 對於清理、歸檔、聚簇等表管理服務,需要協調不同寫入端。

那麼如果上述兩個原因可以放寬呢?

  • 如果工作負載是不可變的,或者不同的寫入端寫入完全不同的分割區,那麼真的不需要解決任何衝突。顯然聲稱沒有一個寫入端重疊這是由使用者承擔的,因為 Hudi 可能不會做任何衝突解決。
  • 禁用除一個寫入端之外的所有寫入端的表服務。

不可變的工作負載

不可變的工作負載是關鍵。 因此建議他們使用 bulk_insert作為操作型別,因為它相當於寫入Parquet表。 沒有索引查詢,沒有小檔案管理,因此兩個寫入端不會以任何方式發生衝突。

表服務

Hudi 有一個全域性設定,可以在需要時禁用表服務("hoodie.table.services.enabled")。 預設情況下設定設定為 true,因此啟動的每個寫入端都可能正在執行表服務。但我們可以使用此設定來禁用除一個之外的所有寫入端。

後設資料表

必須禁用後設資料表,因為我們有一個先決條件,即如果有多個寫入端,需要鎖定後設資料表。

本質上其中一個寫入端將與所有表服務一起進行攝取,而所有其他寫入端只會進行攝取,這可能不會與任何其他寫入端重疊。如下是兩個寫入端的設定。

寫入端1

忽略典型的必填欄位,如記錄鍵、表名等。這些是必須為寫入端 1 設定的設定。

option("hoodie.datasource.write.operation","bulk_insert"). 
option("hoodie.write.concurrency.mode","OPTIMISTIC_CONCURRENCY_CONTROL").
option("hoodie.cleaner.policy.failed.writes","LAZY").
option("hoodie.write.lock.provider","org.apache.hudi.client.transaction.lock.InProcessLockProvider").
option("hoodie.metadata.enable","false").

注意到我們啟用了 InProcessLockProvider 並將操作型別設定為"bulk_insert"並禁用了後設資料表。

因此寫入端將負責清理和歸檔等表服務。

寫入端2

寫入端2設定如下

option("hoodie.datasource.write.operation","bulk_insert"). 
option("hoodie.cleaner.policy.failed.writes","LAZY"). 
option("hoodie.metadata.enable","false").
option("hoodie.table.services.enabled","false").

注意到我們禁用了表服務和後設資料表,並將操作型別設定為"bulk_insert"。 因此寫入端2所做的就是將新資料攝取到表中,而無需擔心任何表服務。

小檔案管理

如果希望利用小檔案管理也可以將寫入端1的操作型別設定為"insert"。 如果希望將"insert"作為所有寫入的操作型別,則應小心。 如果它們都寫入不同的分割區,那麼它可能會起作用。 但如果它們可能寫入相同的分割區,則可能會導致意想不到的後果,需要避免。

或者我們可以將操作型別保留為"bulk_insert",但使用寫入端1啟用聚簇來合併小檔案,如下所示:

option("hoodie.datasource.write.operation","bulk_insert"). 
option("hoodie.write.concurrency.mode","OPTIMISTIC_CONCURRENCY_CONTROL").
option("hoodie.cleaner.policy.failed.writes","LAZY").
option("hoodie.write.lock.provider","org.apache.hudi.client.transaction.lock.InProcessLockProvider").
option("hoodie.metadata.enable","false").
option("hoodie.clustering.inline","true").
option("hoodie.clustering.inline.max.commits","4").

為兩個並行 Spark 寫入端嘗試上述一組設定,並使用清理和歸檔設定進行了 100 多次提交測試。 還進行故障演練並且事物完好無失真。 輸入資料與兩個寫入端從 Hudi 讀取的快照相匹配。

結論

如果用例符合前面提到的約束,這將非常有助於提高 Hudi 寫入的吞吐量。不必為鎖提供者管理基礎設施也將減輕操作負擔。