Apache Hudi Timeline:支持 ACID 事务的基础
Apache Hudi 維護在給定表上執行的所有操作的Timeline(時間線),以支持以符合 ACID 的方式高效檢索讀取查詢的數據。 在寫入和表服務期間也會不斷查閱時間線,這是表正常運行的關鍵。 如果任何時間線操作出現混亂(由于多寫入未配置鎖提供程序等),則可能導致數據一致性問題(數據丟失或數據重復)或最終導致不可恢復的錯誤。 因此讓我們深入研究時間線Timeline的細微差別,以幫助操作 Apache Hudi 表。
Instant
在表格上執行的所有操作都表示為 Hudi 時間軸中的Instant(瞬間)。 可以在表基本路徑下找到一個名為“.hoodie”的目錄,其中維護這些Instant。 Hudi instant由以下組件組成:
- Instant操作:在表上執行的操作類型。
- Instant時間:毫秒格式的時間戳,被視為時間線上操作的標識符。
- 狀態:當前Instant狀態。有 3 種不同的狀態:Requested(請求)、Inflight(執行)和 Completed(完成)。 給定instant將處于任何時間點的狀態之一。 每個操作都從"requested"狀態開始,然后移至"inflight",最后進入 "completed" 狀態,在這種情況下,整個操作被視為已完成。 在操作進入 "completed" 狀態之前,其被視為待處理,并且不允許讀取查詢從任何此類操作中讀取任何數據。
Hudi保證在Timeline時間軸上執行的操作是原子的并且基于Instant時間的時間軸一致。
Action
我們在 Apache Hudi 表上發生了很多不同的操作,每個操作都有不同的目的,例如由常規寫入者攝取數據、壓縮和聚簇、清理和歸檔等表服務。
對于博客的大部分內容,我們將假設單寫入模型,因為重點是說明時間線事件。 但如果有必要的話,也會討論一些多寫入端的場景。
Commit
Commit(提交) 操作代表寫入 COW 表。 每當新批次被攝取到表中時,就會生成新的 CommtTime 并且操作進入請求狀態。 可以在 Hudi 時間軸中找到"tN.commit.requested"。 例如,20230705155904980.commit.requested(其中"20230705155904980"是該操作的提交時間,請求標志著規劃階段的完成。對于常規寫入,準備階段沒有太多事情要做,執行階段從添加"inflight"開始,一旦執行完成,在時間軸中看到“已完成”的提交文件。
| — 20230705155904980.commit.requested
| — 20230705155904980.commit.inflight
| — 20230705155904980.commit
因此在我們看到 20230705155904980.commit 之前,所有查詢都不會讀取此提交部分寫入的任何數據。 一旦通過將 20230705155904980.commit 添加到時間線來標記完成,任何命中表的新讀取都將讀取此感興趣的提交提交的數據。
Delta Commit
Delta Commit(增量提交)表示對 MOR 表的寫入。 這可能會產生日志文件或基本Parquet文件。 但"增量提交"是指定期寫入 MOR 表。 該序列類似于我們上面看到的"提交"。
| — 20230707081934362.deltacommit.requested
| — 20230707081934362.deltacommit.inflight
| — 20230707081934362.deltacommit
提交和增量提交都只會導致添加新文件。 完成的文件將列出有關添加的文件的所有元信息,以及寫入的字節數、寫入的記錄、更新的記錄等統計信息。
Clean
Hudi 在對現有文件組的任何更新中添加名為FileSlice(文件切片)的新版本文件。 舊版本的文件切片由Cleaner(清理器)根據清理器配置清理(或刪除)。 與常規寫入(提交和增量提交)不同,Cleaner 還將經歷一個計劃階段,最終將導致 tX.clean.requested 包含清理計劃。 它將跟蹤需要在清理過程中刪除的所有文件。
將計劃序列化到請求文件中的主要原因是為了確保冪等性。 為了在清理過程中能夠防止中途崩潰,我們希望確保清理計劃一旦完成就能夠順利完成而不會失敗。 此外完成的清理準確顯示了哪些文件作為清理提交的一部分被刪除,而不僅僅是部分文件列表,無論重新嘗試清理多少次。 同樣的原理也適用于聚簇計劃、壓縮計劃和恢復計劃。
| — 20230708091954360.clean.requested
| — 20230708091954360.clean.inflight
| — 20230708091954360.clean
可以在完成的"20230708091954360.clean"文件中找到有關清理器刪除的所有文件的信息。 讓我們通過一個簡單的示例來了解 Cleaner 的作用。
t1.commit:
- 插入新數據
- 添加新文件fg1_fs1(fg指文件組,fs指文件切片)
t2.commit:
- 更新同一組數據。
- 將新文件片 fg1_fs2 添加到現有文件組 fg1。
t3.commit:
- 更新同一組數據。
- 將新文件片 fg1_fs3 添加到現有文件組 fg1。
現在Cleaner被觸發,Cleaner配置設置為“2”,以保留要保留的提交數。 因此任何早于最近 2 次提交創建的文件切片都會被清理。 因此 Cleaner 會將 fg1_fs1 添加到 clean 計劃中,然后在執行過程中將其刪除。 因此存儲中僅留下 fg1_fs2 和 fg1_fs3。
t4.clean
- 清理fg1_fs1
這個循環將會重復。 例如 t5.commit 將添加 fg1_fs4,t6.clean 將刪除 fg1_fs2 等等。 可以在此處閱讀有關Cleaner的更多信息。
Replace Commit
與提交和增量提交不同,某些操作可能會導致替換某些數據文件。 例如,對于Clustering(聚簇),insert_overwrite 操作會添加新的數據文件,但也會替換某些數據文件。 其中大多數都是異步的,因為替換的文件不會同步刪除,而只是標記為替換。 在稍后的某個時間點,由清理器負責刪除文件。
| — 20230707081954360.replacecommit.requested
| — 20230707081954360.replacecommit.inflight
| — 20230707081954360.replacecommit
比方說,使用 4 個提交 t1.commit(file1)、t2.commit(file2)、t3.commit(file3) 和 t4.commit(file4) 將 4 個數據文件寫入表中。 這里的每個文件代表Hudi中的一個不同的文件組。 假設我們觸發將小文件批處理為大文件。
t1.commit:
- 插入新數據 fg1_fs1
t2.commit:
- 插入新數據 fg2_fs1
t3.commit:
- 插入新數據 fg3_fs1
t4.commit:
- 插入新數據 fg4_fs1
t5.replacecommit 將創建一個新文件,file5 替換先前提交創建的 4 個文件。
t5.replacecommit
- 通過替換文件組(1 至 4)創建新文件組 fg5_fs1
在將 t5.replacecommit(已完成的時間線文件)添加到時間線之前,讀取查詢將從 4 個文件中讀取數據,一旦將完成的 t5.replacecommit 添加到時間線,任何新的讀取查詢將僅讀取 file5 并忽略 file1 到 file4。 完成的 t5.replacecommit 將包含有關添加哪些文件和替換哪些文件的所有信息。
此外,Commit和Replace Commit之間的另一個區別是,常規提交的規劃階段沒有太多涉及。 但在Replace Commit情況下,規劃涉及遍歷現有文件組,并根據聚簇計劃策略和配置,Hudi 將確定要考慮聚簇的文件組以及如何將它們打包到不同的聚簇操作中。 因此對于非常大的表,即使是計劃也可能需要一些不小的時間。 此外在規劃階段結束時,有可能不會生成任何聚簇計劃,因此我們可能看不到任何".replacecommit.requested"文件。 這意味著此時沒有任何東西可以聚簇,并且聚簇計劃將在稍后的某個時間再次重新嘗試。 可以在此處閱讀有關聚簇的更多信息。
聚簇就是這樣的一個例子。 但還有其他操作會導致Replace Commit操作,其中包括insert_overwrite、insert_overwrite_table 和 delete_partition 操作。
Compaction Commit
Compaction(壓縮)是指將 MOR 表中的基礎文件和關聯日志文件壓縮為新的基礎文件的過程。 可以在此處閱讀有關壓縮的更多信息。 與聚簇類似,這也將經歷一個規劃階段,并基于壓縮策略,可選地生成一個壓縮計劃,跟蹤日志文件列表和要壓縮的基本文件。 如果生成了計劃,它將在時間線中生成一個compaction.requested 文件。 這標志著規劃階段的結束。 然后在執行階段,將創建一個inflight文件,最終一旦壓縮完成,一個完成的文件將被添加到時間線中以標記感興趣的壓縮的完成。
| — 20230707091954370.compaction.requested
| — 20230707091954370.compaction.inflight
| — 20230707091954370.commit
同樣與 Clean 和 Clustering 類似,計劃一旦序列化(換句話說,一旦requested文件寫出),Hudi 就可以適應任意數量的崩潰和重新嘗試,最終 Hudi 一定會完成它,確保所有部分失敗的嘗試都得到正確清理,并且只有最終成功嘗試的數據文件完好無損。 當操作一個非常大的表并且必須壓縮大量文件組時,這一點非常關鍵。 此外假設計劃的壓縮最終完成,表中的其他操作也將繼續進行。 因此我們永遠無法恢復計劃的壓縮。 如果表中有更多寫入端,則必須不惜一切代價完成它,這是Hudi支持異步壓縮的關鍵設計之一。 如果看到具有以下序列的時間線,則它是有效的事件序列。
| — t100.compaction.requested
| — t110.deltacommit.requested
| — t110.deltacommit.inflight
| — t100.compaction.inflight
| — t110.deltacommit
| — t100.commit
如果在連續模式下使用 Deltastreamer,這是通常看到的時間線事件序列。
Rollback
使用Rollback(回滾)操作回滾任何部分失敗的寫入。 在單寫入端模式下,回滾是急切的,即每當開始新的提交時,Hudi 都會檢查任何待處理的提交并觸發回滾。 在Hudi支持的所有不同操作中,只有Clean、Rollback和Restore會刪除文件,其他操作都不會刪除任何數據文件,Replace Commit可以將某些文件標記為已替換,但不會刪除它們。
回滾計劃階段包括查找作為部分失敗提交的一部分添加的所有文件并將其添加到回滾計劃中。正如我們之前所看到的,計劃被序列化到 rollback.requested 文件中。 執行首先在時間線中創建一個運行中的文件,最終當回滾完成時,完成的回滾文件將被添加到時間線中。
假設這是崩潰之前的時間線。
| — t10.commit.requestet
| — t10.commit.inflight
| — t10.commit
| — t20.commit.requested
| — t20.commit.inflight
就在這之后,進程崩潰了。 因此用戶重新啟動管道并將觸發回滾,因為 t20 被推斷為待處理。
| — t10.commit.requested
| — t10.commit.inflight
| — t10.commit
| — t20.commit.requested
| — t20.commit.inflight
| — t25.rollback.requested
回滾結束時,Hudi 會刪除正在回滾的提交的提交元文件。 在這種情況下,與提交 t20 相關的所有時間線文件都將被刪除。 因此回滾完成后的時間線可能如下所示。
| — t10.commit.requested
| — t10.commit.inflight
| — t10.commit
| — t25.rollback.requested。
| — t25.rollback.inflight
| — t25.rollback
對于多寫入端,Hudi 還引入了延遲回滾,即它使用基于心跳的回滾機制,我們會在未來的博客中更深入地了解回滾算法。
與聚簇、壓縮類似,回滾也被設計成冪等的。我們在請求文件中序列化計劃,因此即使回滾中途崩潰,我們也可以重新嘗試,不會出現任何問題。 Hudi 確保重復使用相同的回滾即時時間來回滾給定的提交。 完成的回滾文件將列出在回滾過程中刪除的所有文件。 COW中的回滾將刪除部分寫入的文件,但在MOR的情況下,如果部分失敗的提交添加了一個日志文件,則回滾將添加另一個帶有回滾塊的日志文件,并且不會刪除原始日志文件。 這是 MOR 表的關鍵設計之一,以將任何寫入保留為追加。 我們還可以在以后的一些博客中查看日志文件設計。
Savepoint
為了在災難和恢復場景中提供幫助,Hudi 引入了兩種操作,稱為Savepoint(保存點)和Restore(恢復)。 將保存點添加到提交可確保清理和歸檔不會觸及與保存點提交相關的任何內容。 這意味著用戶可以根據需要將表恢復到感興趣的保存點提交。 僅當保存點尚未清理時才允許將其添加到提交中。
Savepoint 只有兩種狀態:正在運行和已完成。 由于沒有計劃階段,因此沒有保存點請求。 在執行階段,Hudi 會查找截至感興趣的提交時間提供讀取查詢所需的所有文件。 這些文件將添加到 tX.savepoint.inflight 文件中。 并立即將完整的保存點文件添加到時間線中。
| — t10.commit.requested
| — t10.commit.inflight
| — t10.commit
| — t10.savepoint.inflight
| — t10.savepoint
也可以在稍后階段添加保存點,只是清理程序不應該清理文件。 例如表可能有從 t10 到 200 的提交(每 10 秒一次)。 因此在時間 t210,如果 Cleaner 清理 t30 之前的數據文件,則允許為t50添加保存點。
Restore
Restore(恢復)用于將整個表恢復到某個較舊的時間點。 萬一表中出現了一些壞數據,或者數據損壞或其他正當原因,如果用戶希望將表恢復到 10 小時前的狀態,恢復操作就會派上用場。 用戶可以將保存點添加到 10 小時前的提交之一并觸發恢復。 從技術上講,恢復意味著按時間倒序回滾 N 個提交。 例如如果表有提交 t10、t20、t30、t40、t50、t60、t70、t80、t90 和 t100。 用戶更愿意將表恢復到 t40。 Hudi 將回滾 t100,然后回滾 t90,然后回滾 t80,依此類推。直到 t50 回滾開始。
Hudi 將像其他表服務一樣經歷類似的狀態轉換。 將生成請求的計劃來跟蹤需要回滾的所有提交,然后在執行過程中,將創建一個運行中的文件,最終完成后,完整的恢復文件將添加到時間線中。
| — t10.commit.requested
| — t10.commit.inflight
| — t10.commit
| — t10.savepoint.inflight
| — t10.savepoint
| — t20.commit.requested
| — t20.commit.inflight
| — t20.commit
| - ..
| - ..
| — t100.commit.requested
| — t100.commit.inflight
| — t100.commit
恢復后時間線可能如下所示
| — t10.commit.requested
| — t10.commit.inflight
| — t10.commit
| — t10.savepoint.inflight
| — t10.savepoint
| — t120.restore.requested
| — t120.restore.inflight|
| — t120.restore
Index
Hudi支持添加各種索引來輔助讀寫延遲,此類分區包括列統計分區和布隆過濾器分區,要首次為大型表初始化這些索引,我們不能阻止攝取寫入器,因為它可能會占用大量時間。 因此 Hudi 引入了 AsyncIndexer 來協助異步初始化這些分區。
| — t200.indexing.requested
| — t200.indexing.inflight
| — t200.indexing
與任何其他操作一樣,這會經歷典型的狀態轉換,我們將在單獨的博客中詳細介紹異步索引。
Active/Archive Timeline
Hudi 將整個時間線剖析為Active Timeline(活動時間線)和Archive Timeline(存檔時間線)。 在".hoodie"目錄下看到的任何Instant均指活動時間線,而存檔的那些Instant將進入".hoodie/archived"目錄。 可以在此處閱讀有關存檔時間表的更多信息。區分Ative/Archive Timeline背后的基本原理是確保我們對元數據(時間線)有最大限制,防止隨著時間線越來越長,讀取出現延遲增加的情況。
Hudi CLI
Hudi CLI 有查看表時間線的命令。我們將在其他一些博客中通過示例詳細介紹它們,如果想嘗試一下,命令是"timeline"。
結論
時間線在提供符合 ACID 的正確數據方面發揮著非常重要的作用。 了解不同的時間線事件對于管理任何組織中的 Apache Hudi 表都非常有益,并且還有助于根據需要進行問題排查。
總結
以上是生活随笔為你收集整理的Apache Hudi Timeline:支持 ACID 事务的基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kubernetes:kube-apis
- 下一篇: 抽动症孩子的饮食?