一文帶你了解Lakehouse的並發控制:我們是否過於樂觀

  • 2022 年 3 月 27 日
  • 筆記

1. 概述

如今數據湖上的事務被認為是 Lakehouse 的一個關鍵特徵。 但到目前為止,實際完成了什麼? 目前有哪些方法? 它們在現實世界中的表現如何? 這些問題是本部落格的重點。

有幸從事過各種資料庫項目——RDBMS (Oracle)、NoSQL 鍵值存儲 (Voldemort)、流資料庫 (ksqlDB)、閉源實時數據存儲,當然還有 Apache Hudi, 我可以肯定地說,工作負載的不同深刻地影響了不同資料庫中採用的並發控制機制。本部落格還將介紹我們如何重新思考 Apache Hudi 數據湖的並發控制機制。

首先,我們直截了當點,RDBMS 資料庫提供了最豐富的事務功能集和最廣泛的並發控制機制,不同的隔離級別、細粒度鎖、死鎖檢測/避免等其他更多機制,因為它們必須支援行級變更和跨多個表的讀取,同時強制執行鍵約束並維護索引。而NoSQL 存儲提供了非常弱的保證,例如僅僅提供最終一致性和簡單的行級原子性,以換取更簡單的工作負載的更好的擴展性。傳統數據倉庫基於列存或多或少提供了您在 RDBMS 中可以找到的全套功能,強制執行鎖定和鍵約束,而雲數據倉庫似乎更多地關注存算分離架構,同時提供更少的隔離級別。作為一個令人驚訝的例子,沒有強制執行鍵約束。

2. 數據湖並發控制中的陷阱

從歷史看來,數據湖一直被視為在雲存儲上讀取/寫入文件的批處理作業,有趣的是看到大多數新工作如何擴展此視圖並使用某種形式的「樂觀並發控制」(OCC)來實現文件版本控制。 OCC 作業採用表級鎖來檢查它們是否影響了重疊文件,如果存在衝突則中止操作,鎖有時甚至只是在單個 Apache Spark Driver節點上持有的 JVM 級鎖,這對於主要將文件附加到表的舊式批處理作業的輕量級協調來說可能沒問題,但不能廣泛應用於現代數據湖工作負載。此類方法是在考慮不可變/僅附加數據模型的情況下構建的,這些模型不適用於增量數據處理或鍵控更新/刪除。 OCC 非常樂觀地認為真正的衝突永遠不會發生。將 OCC 與 RDBMS 或傳統數據倉庫的完全成熟的事務功能進行比較的開發人員佈道是完全錯誤的,直接引用維基百科——「如果頻繁地爭用數據資源,重複重啟事務的成本會顯著損害性能,在這種情況下,其他並發控制方法可能更適合。」 當衝突確實發生時,它們會導致大量資源浪費,因為你有每次嘗試運行幾個小時後都失敗的批處理作業!

想像一下兩個寫入進程的真實場景:一個每 30 分鐘生成一次新數據的攝取寫入作業和一個執行 GDPR 的刪除作業,需要 2 小時才能完成刪除。這些很可能與隨機刪除重疊文件,並且刪除作業幾乎可以保證每次都餓死並且無法提交。 在資料庫方面,將長期運行的事務與樂觀混合會導致失望,因為事務越長,它們重疊的可能性就越高。

img

那麼有什麼替代方案呢?鎖?維基百科還說 – 「但是,基於鎖(「悲觀」)的方法也可能提供較差的性能,因為即使避免了死鎖,鎖也會極大地限制有效的並發性。」。這就是 Hudi 採用不同方法的地方,我們認為這種方法更適合現代數據湖事務,這些事務通常是長期運行的,甚至是連續的。與資料庫的標準讀/寫相比,數據湖工作負載與高吞吐量流處理作業共享更多特徵,這就是我們借鑒的地方。在流處理中,事件被序列化為單個有序日誌,避免任何鎖/並發瓶頸,用戶可以每秒連續處理數百萬個事件。Hudi 在 Hudi 時間線上實現了一個文件級、基於日誌的並發控制協議,而該協議又依賴於對雲存儲的最低限度的原子寫入。通過將事件日誌構建為進程間協調的核心部分,Hudi 能夠提供一些靈活的部署模型,與僅跟蹤錶快照的純 OCC 方法相比,這些模型提供更高的並發性。

3. 模型 1:單寫入,內聯表服務

並發控制的最簡單形式就是完全沒有並發。 數據湖表通常在其上運行公共服務以確保效率,從舊版本和日誌中回收存儲空間、合併文件(Hudi 中的Clustering)、合併增量(Hudi 中的Compaction)等等。 Hudi 可以簡單地消除對並發控制的需求,並通過支援這些開箱即用的表服務並在每次寫入表後內聯運行來最大化吞吐量。

執行計劃是冪等的,持久化至時間線並從故障中自動恢復。對於大多數簡單的用例,這意味著只需寫入就足以獲得一個不需要並發控制的管理良好的表。

//hudi.apache.org/assets/images/SingleWriterInline-d18346421aa3f1d11a3247164389e1ce.gif

4. 模型2:單寫入,非同步表服務

我們上面的刪除/攝取示例並不是那麼簡單。雖然攝取/寫入可能只是更新表上的最後 N 個分區,但刪除甚至可能跨越整個表,將它們混合在同一個工作負載中可能會大大影響攝取延遲,因此Hudi 提供了以非同步方式運行表服務的選項,其中大部分繁重的工作(例如通過壓縮服務實際重寫列數據)是非同步完成的,消除了任何重複的浪費重試,同時還使用Clustering技術。因此單個寫入可以同時使用常規更新和 GDPR 刪除並將它們序列化到日誌中。鑒於 Hudi 具有記錄級索引並且 avro 日誌寫入要便宜得多(與寫入 parquet 相比,後者可能要貴 10 倍或更高),攝取延遲可以持續,同時享受出色的可回溯性。事實上我們能夠在 Uber 將這個模型擴展到 100 PB數據規模,通過將所有刪除和更新排序到同一個源 Apache Kafka 主題中,並發控制不僅僅是鎖,Hudi 無需任何外部鎖即可完成所有這一切。

//hudi.apache.org/assets/images/SingleWriterAsync-3d7ddf7312381eab7fdb91a7f2746376.gif

5. 模型3:多寫入

但是並不總是可以將刪除序列化到相同的寫入流中,或者需要基於 sql 的刪除。 對於多個分散式進程,某種形式的鎖是不可避免的,但就像真正的資料庫一樣,Hudi 的並發模型足夠智慧,可以將實際寫入表的內容與管理或優化表的表服務區分開來。 Hudi 提供了類似的跨多個寫入器的樂觀並發控制,但表服務仍然可以完全無鎖和非同步地執行。 這意味著刪除作業只能對刪除進行編碼,攝取作業可以記錄更新,而壓縮服務再次將更新/刪除應用於基本文件。 儘管刪除作業和攝取作業可以像我們上面提到的那樣相互競爭和餓死,但它們的運行時間要低得多,浪費也大大降低,因為壓縮完成了parquet/列數據寫入的繁重工作。

//hudi.apache.org/assets/images/MultiWriter-6068037346e21d41e0e620fb514e2342.gif

綜上所述,在這個基礎上我們還有很多方法可以改進。

  • 首先,Hudi 已經實現了一種標記機制,可以跟蹤作為活動寫入事務一部分的所有文件,以及一種可以跟蹤表的活動寫入者的心跳機制。這可以由其他活動事務/寫入器直接使用來檢測其他寫入器正在做什麼,如果檢測到衝突,則儘早中止,從而更快地將集群資源返回給其他作業。

  • 雖然在需要可序列化快照隔離時樂觀並發控制很有吸引力,但它既不是最佳方法,也不是處理寫入者之間並發性的唯一方法。我們計劃使用 CRDT 和廣泛採用的流處理概念,通過我們的日誌合併 API 實現完全無鎖的並發控制,這已經被證明可以為數據湖維持巨大的連續寫入量。

  • 談到鍵約束,Hudi 是當今唯一確保唯一鍵約束的湖事務層,但僅限於表的記錄鍵。我們將尋求以更通用的形式將此功能擴展到非主鍵欄位,並使用上述較新的並發模型。

最後,要使數據湖成功轉型為Lakehouse,我們必須從「Hadoop 倉庫」願景的失敗中吸取教訓,它與新的「Lakehouse」願景有著相似的目標。 設計人員沒有密切關注與數據倉庫相關的缺失技術差距,並且對實際軟體產生了不切實際的期望。 隨著事務和資料庫功能最終成為數據湖的主流,我們必須應用這些經驗教訓並對當前的缺點保持坦率。 如果您正在構建一個 Lakehouse,我希望這篇文章能鼓勵您仔細考慮圍繞並發控制的各種操作和效率方面。 通過試用 Apache Hudi 加入我們快速發展的社區,或加入 Slack 進行進一步交流。

//hudi.apache.org/blog/2021/12/16/lakehouse-concurrency-control-are-we-too-optimistic