Apache Hudi(發音為“Hoodie”)在DFS的數據集上提供以下流原語
- 插入更新 (如何改變數據集?)
- 增量拉取 (如何獲取變更的數據?)
在本節中,我們將討論重要的概念和術語,這些概念和術語有助於理解並有效使用這些原語。
時間軸
在它的核心,Hudi維護一條包含在不同的即時
時間所有對數據集操作的時間軸
,從而提供,從不同時間點出發得到不同的視圖下的數據集。Hudi即時包含以下組件
操作類型
: 對數據集執行的操作類型即時時間
: 即時時間通常是一個時間戳(例如:20190117010349),該時間戳按操作開始時間的順序單調增加。狀態
: 即時的狀態
Hudi保證在時間軸上執行的操作的原子性和基於即時時間的時間軸一致性。
執行的關鍵操作包括
COMMITS
- 一次提交表示將一組記錄原子寫入到數據集中。CLEANS
- 刪除數據集中不再需要的舊文件版本的后台活動。DELTA_COMMIT
- 增量提交是指將一批記錄原子寫入到MergeOnRead存儲類型的數據集中,其中一些/所有數據都可以只寫到增量日志中。COMPACTION
- 協調Hudi中差異數據結構的后台活動,例如:將更新從基於行的日志文件變成列格式。在內部,壓縮表現為時間軸上的特殊提交。ROLLBACK
- 表示提交/增量提交不成功且已回滾,刪除在寫入過程中產生的所有部分文件。SAVEPOINT
- 將某些文件組標記為"已保存",以便清理程序不會將其刪除。在發生災難/數據恢復的情況下,它有助於將數據集還原到時間軸上的某個點。
任何給定的即時都可以處於以下狀態之一
REQUESTED
- 表示已調度但尚未啟動的操作。INFLIGHT
- 表示當前正在執行該操作。COMPLETED
- 表示在時間軸上完成了該操作。
上面的示例顯示了在Hudi數據集上大約10:00到10:20之間發生的更新事件,大約每5分鍾一次,將提交元數據以及其他后台清理/壓縮保留在Hudi時間軸上。
觀察的關鍵點是:提交時間指示數據的到達時間
(上午10:20),而實際數據組織則反映了實際時間或事件時間
,即數據所反映的(從07:00開始的每小時時段)。在權衡數據延遲和完整性時,這是兩個關鍵概念。
如果有延遲到達的數據(事件時間為9:00的數據在10:20達到,延遲 >1 小時),我們可以看到upsert將新數據生成到更舊的時間段/文件夾中。
在時間軸的幫助下,增量查詢可以只提取10:00以后成功提交的新數據,並非常高效地只消費更改過的文件,且無需掃描更大的文件范圍,例如07:00后的所有時間段。
文件組織
Hudi將DFS上的數據集組織到基本路徑
下的目錄結構中。數據集分為多個分區,這些分區是包含該分區的數據文件的文件夾,這與Hive表非常相似。
每個分區被相對於基本路徑的特定分區路徑
區分開來。
在每個分區內,文件被組織為文件組
,由文件id
唯一標識。
每個文件組包含多個文件切片
,其中每個切片包含在某個提交/壓縮即時時間生成的基本列文件(*.parquet
)以及一組日志文件(*.log*
),該文件包含自生成基本文件以來對基本文件的插入/更新。
Hudi采用MVCC設計,其中壓縮操作將日志和基本文件合並以產生新的文件片,而清理操作則將未使用的/較舊的文件片刪除以回收DFS上的空間。
Hudi通過索引機制將給定的hoodie鍵(記錄鍵+分區路徑)映射到文件組,從而提供了高效的Upsert。
一旦將記錄的第一個版本寫入文件,記錄鍵和文件組/文件id之間的映射就永遠不會改變。 簡而言之,映射的文件組包含一組記錄的所有版本。
存儲類型和視圖
Hudi存儲類型定義了如何在DFS上對數據進行索引和布局以及如何在這種組織之上實現上述原語和時間軸活動(即如何寫入數據)。
反過來,視圖
定義了基礎數據如何暴露給查詢(即如何讀取數據)。
存儲類型 | 支持的視圖 |
---|---|
寫時復制 | 讀優化 + 增量 |
讀時合並 | 讀優化 + 增量 + 近實時 |
存儲類型
Hudi支持以下存儲類型。
-
寫時復制 : 僅使用列文件格式(例如parquet)存儲數據。通過在寫入過程中執行同步合並以更新版本並重寫文件。
-
讀時合並 : 使用列式(例如parquet)+ 基於行(例如avro)的文件格式組合來存儲數據。 更新記錄到增量文件中,然后進行同步或異步壓縮以生成列文件的新版本。
下表總結了這兩種存儲類型之間的權衡
權衡 | 寫時復制 | 讀時合並 |
---|---|---|
數據延遲 | 更高 | 更低 |
更新代價(I/O) | 更高(重寫整個parquet文件) | 更低(追加到增量日志) |
Parquet文件大小 | 更小(高更新代價(I/o)) | 更大(低更新代價) |
寫放大 | 更高 | 更低(取決於壓縮策略) |
視圖
Hudi支持以下存儲數據的視圖
- 讀優化視圖 : 在此視圖上的查詢將查看給定提交或壓縮操作中數據集的最新快照。
該視圖僅將最新文件切片中的基本/列文件暴露給查詢,並保證與非Hudi列式數據集相比,具有相同的列式查詢性能。 - 增量視圖 : 對該視圖的查詢只能看到從某個提交/壓縮后寫入數據集的新數據。該視圖有效地提供了更改流,來支持增量數據管道。
- 實時視圖 : 在此視圖上的查詢將查看某個增量提交操作中數據集的最新快照。該視圖通過動態合並最新的基本文件(例如parquet)和增量文件(例如avro)來提供近實時數據集(幾分鍾的延遲)。
下表總結了不同視圖之間的權衡。
權衡 | 讀優化 | 實時 |
---|---|---|
數據延遲 | 更高 | 更低 |
查詢延遲 | 更低(原始列式性能) | 更高(合並列式 + 基於行的增量) |
寫時復制存儲
寫時復制存儲中的文件片僅包含基本/列文件,並且每次提交都會生成新版本的基本文件。
換句話說,我們壓縮每個提交,從而所有的數據都是以列數據的形式儲存。在這種情況下,寫入數據非常昂貴(我們需要重寫整個列數據文件,即使只有一個字節的新數據被提交),而讀取數據的成本則沒有增加。
這種視圖有利於讀取繁重的分析工作。
以下內容說明了將數據寫入寫時復制存儲並在其上運行兩個查詢時,它是如何工作的。
隨着數據的寫入,對現有文件組的更新將為該文件組生成一個帶有提交即時時間標記的新切片,而插入分配一個新文件組並寫入該文件組的第一個切片。
這些文件切片及其提交即時時間在上面用顏色編碼。
針對這樣的數據集運行SQL查詢(例如:select count(*)
統計該分區中的記錄數目),首先檢查時間軸上的最新提交並過濾每個文件組中除最新文件片以外的所有文件片。
如您所見,舊查詢不會看到以粉紅色標記的當前進行中的提交的文件,但是在該提交后的新查詢會獲取新數據。因此,查詢不受任何寫入失敗/部分寫入的影響,僅運行在已提交數據上。
寫時復制存儲的目的是從根本上改善當前管理數據集的方式,通過以下方法來實現
- 優先支持在文件級原子更新數據,而無需重寫整個表/分區
- 能夠只讀取更新的部分,而不是進行低效的掃描或搜索
- 嚴格控制文件大小來保持出色的查詢性能(小的文件會嚴重損害查詢性能)。
讀時合並存儲
讀時合並存儲是寫時復制的升級版,從某種意義上說,它仍然可以通過讀優化表提供數據集的讀取優化視圖(寫時復制的功能)。
此外,它將每個文件組的更新插入存儲到基於行的增量日志中,通過文件id,將增量日志和最新版本的基本文件進行合並,從而提供近實時的數據查詢。因此,此存儲類型智能地平衡了讀和寫的成本,以提供近乎實時的查詢。
這里最重要的一點是壓縮器,它現在可以仔細挑選需要壓縮到其列式基礎文件中的增量日志(根據增量日志的文件大小),以保持查詢性能(較大的增量日志將會提升近實時的查詢時間,並同時需要更長的合並時間)。
以下內容說明了存儲的工作方式,並顯示了對近實時表和讀優化表的查詢。
此示例中發生了很多有趣的事情,這些帶出了該方法的微妙之處。
- 現在,我們每1分鍾左右就有一次提交,這是其他存儲類型無法做到的。
- 現在,在每個文件id組中,都有一個增量日志,其中包含對基礎列文件中記錄的更新。
在示例中,增量日志包含10:05至10:10的所有數據。與以前一樣,基本列式文件仍使用提交進行版本控制。
因此,如果只看一眼基本文件,那么存儲布局看起來就像是寫時復制表的副本。 - 定期壓縮過程會從增量日志中合並這些更改,並生成基礎文件的新版本,就像示例中10:05發生的情況一樣。
- 有兩種查詢同一存儲的方式:讀優化(RO)表和近實時(RT)表,具體取決於我們選擇查詢性能還是數據新鮮度。
- 對於RO表來說,提交數據在何時可用於查詢將有些許不同。 請注意,以10:10運行的(在RO表上的)此類查詢將不會看到10:05之后的數據,而在RT表上的查詢總會看到最新的數據。
- 何時觸發壓縮以及壓縮什么是解決這些難題的關鍵。
通過實施壓縮策略,在該策略中,與較舊的分區相比,我們會積極地壓縮最新的分區,從而確保RO表能夠以一致的方式看到幾分鍾內發布的數據。
讀時合並存儲上的目的是直接在DFS上啟用近實時處理,而不是將數據復制到專用系統,后者可能無法處理大數據量。
該存儲還有一些其他方面的好處,例如通過避免數據的同步合並來減少寫放大,即批量數據中每1字節數據需要的寫入數據量。