如何不加鎖地將數據併發寫入Apache Hudi?

最近一位 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 寫入的吞吐量。不必爲鎖提供者管理基礎設施也將減輕操作負擔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章