簡介
在資料不斷寫入 Hudi 期間,Hudi 會不斷生成 commit、deltacommit、clean 等 Instant 記錄每一次操作型別、狀態及詳細的後設資料,這些 Instant 最終都會存到 .hoodie 後設資料目錄下,為了避免後設資料檔案數量過多,ActiveTimeline 越來越長,需要對比較久遠的操作進行歸檔(archive),將這部分操作移到 .hoodie/archive 目錄下,單獨形成一個 ArchivedTimeline。
由此可知,archive 是 Hudi Timeline 上的操作,操作的資料是 instant 粒度。
相關配置
// org.apache.hudi.config.HoodieArchivalConfig
public static final ConfigProperty<String> ASYNC_ARCHIVE = ConfigProperty
.key("hoodie.archive.async")
.defaultValue("false")
.sinceVersion("0.11.0")
.withDocumentation("Only applies when " + AUTO_ARCHIVE.key() + " is turned on. "
+ "When turned on runs archiver async with writing, which can speed up overall write performance.");
public static final ConfigProperty<String> MAX_COMMITS_TO_KEEP_PROP = ConfigProperty
.key("hoodie.keep.max.commits")
.defaultValue("150")
.withDocumentation("Archiving service moves older entries from timeline into an archived log after each write, to "
+ " keep the metadata overhead constant, even as the table size grows."
+ "This config controls the maximum number of instants to retain in the active timeline. ");
public static final ConfigProperty<String> MIN_COMMITS_TO_KEEP_PROP = ConfigProperty
.key("hoodie.keep.min.commits")
.defaultValue("145")
.withDocumentation("Similar to " + MAX_COMMITS_TO_KEEP_PROP.key() + ", but controls the minimum number of"
+ "instants to retain in the active timeline.");
public static final ConfigProperty<String> COMMITS_ARCHIVAL_BATCH_SIZE_PROP = ConfigProperty
.key("hoodie.commits.archival.batch")
.defaultValue(String.valueOf(10))
.withDocumentation("Archiving of instants is batched in best-effort manner, to pack more instants into a single"
+ " archive log. This config controls such archival batch size.");
MAX_COMMITS_TO_KEEP_PROP:ActiveTimeLine 最多保留的 instant 個數
MIN_COMMITS_TO_KEEP_PROP:ActiveTimeLine 最少保留的 instant 個數
COMMITS_ARCHIVAL_BATCH_SIZE_PROP:每次 archive 最多的 instant 個數
注:archive 配置與 clean 配置存在關係:hoodie.cleaner.commits.retained <= hoodie,keep.min.commits
入口
checkpoint 的時候提交完 writeStat 後(org.apache.hudi.client.BaseHoodieWriteClient#commitStats),會嘗試進行 archive:
// HoodieFlinkWriteClient
protected void mayBeCleanAndArchive(HoodieTable table) {
autoArchiveOnCommit(table);
}
根據是否配置了 hoodie.archive.async 決定是同步/非同步歸檔。
歸檔
HoodieTimelineArchiver
public boolean archiveIfRequired(HoodieEngineContext context) throws IOException {
try {
List<HoodieInstant> instantsToArchive = getInstantsToArchive().collect(Collectors.toList());
boolean success = true;
if (!instantsToArchive.isEmpty() && instantsToArchive.size() >= this.config.getCommitArchivalBatchSize()) {
this.writer = openWriter();
LOG.info("Archiving instants " + instantsToArchive);
archive(context, instantsToArchive);
LOG.info("Deleting archived instants " + instantsToArchive);
success = deleteArchivedInstants(instantsToArchive);
} else {
LOG.info("No Instants to archive");
}
return success;
} finally {
close();
}
}
該方法定義了整體 archive 的流程:
- 找出所有可歸檔的 instant;
- 執行歸檔,即寫到 archive 目錄下;
- 刪除歸檔檔案,即刪除 .hoodie 下所有對應的 instant,以及 .aux 目錄下所有的 compaction.requested。
找出所有可歸檔的 instant
獲取所有已完成的 clean、rollback instant,按操作型別分組,如果某類操作 instant 超過了最大保留的 instant,則觸發歸檔,只留下最少需保留的 instant。
getCommitInstantsToArchive:
- 獲取最老的未完成的 compaction、replace commit instant;
- 獲取最老的處於 inflight 狀態的 commit、delta commit instant;
- 找到該表的第一個 savepoint instant;
- 獲取 completed commit timline(COW 表為 commit、replace commit,MOR 為 commit、delta commit、replace commit),判斷其 instant 個數是否超過 maxInstantsToKeep;
- 如果超過,針對 MOR 表找到尚未參與 compaction 的最早的 delta commit instant;
獲取 clutering 場景下需保留的最早的 instant:
- 找到最近一次已完成的 clean instant,讀取後設資料中的 earliestInstantToRetain,取比 earliestInstantToRetain 新的 instants 中最老的 replace commit instant;
- 找到最老的處於 inflight 狀態的 replace commit instant,如果存在,找到在此之前最近一次 commit/delta commit/replace commit;
- 取 1 和 2 中更老的 instant。
不能歸檔 clustering instant,除非其對應的資料檔案已被清理,不然 fs view 可能會重複讀資料(替換前後檔案都存在的情況下)
- 如果開啟 hoodie.archive.beyond.savepoint,則歸檔的 instant 只跳過 savepoint instant,否則則跳過第一個 sapoveint 及之後所有的 instant;
- 篩選 instant time 比上述 instant 小的 compleated commit/delat commit/replace commit,且滿足保留 instant 個數大於 minInstantsToKeep。
getCleanInstantsToArchive:
- 獲取所有已完成的 clean、rollback instant;
- 如果 clean 或 rollback 的 instant 個數超過 maxInstantsToKeep,則去掉最新的 minInstantsToKeep 個數的 instants,否則返回空;
透過這兩個把需要 archive 的 completed instants 篩選出來後,再結合 timeline 填充這些 instant 對應的 requested/inflight instants。
執行歸檔
刪除這些 instant 對應的 markfile 目錄,.hoodie/.temp/\<instantTs\>
將每個要歸檔的 instant 轉成一個 IndexedRecord,對應的具體實現為 HoodieArchivedMetaEntry,主要包含四部分資訊:
- commitTime:instant 對應時間戳;
- actionState:操作狀態;
- actionType:操作型別;
- metadata:操作的後設資料;
最後將這些 record 組織成 HoodieAvroDataBlock 按 log 格式落盤,落盤的檔名格式與 logfile 一致,需要將 .log字尾改為 .archive,比如 .hoodie/archived/commits_.archive.1。
刪除已歸檔的 Instant
刪除需要先刪 requested/inflight 再刪 completed。如果先刪 completed 的檔案,則其他基於 timeline 的服務(比如 compaction、clustering)可能會錯誤的將某次操作狀態認為是 pending,從而造成 bug。
如果 archive 的 instants 中包含 commit/delta commit,則刪除對應 aux 目錄下的後設資料檔案。
合併歸檔
從0.11版本開始,Hudi 支援對歸檔的結果檔案進行合併HoodieTimelineArchiver#mergeArchiveFilesIfNecessary
。雖然每次歸檔都將多個 Instant 生成單個 .commit_.arvhive 檔案,但歸檔的次數多後也會有大量的歸檔結果檔案,所以對於較小的歸檔結果檔案進行合併。
當啟用歸檔合併時(hoodie.archive.merge.enable)且檔案系統支援append(StorageSchemas,例如:file://),如果arvhive目錄下存在大小小於 hoodie.archive.merge.small.file.limit.bytes 的檔案數量達到 hoodie.archive.merge.files.batch.size,即觸發合併
- 從新到舊獲取連續所有大小小於 hoodie.archive.merge.small.file.limit.bytes 的候選檔案 candidateFiles;
- 在這次歸檔生成的檔案的基礎上 rollover,作為合併歸檔用的資料檔案;
- 生成合並計劃,記錄要合併的檔案、合併後的檔名等資訊,將這些內容儲存在 arvhive 目錄下的 mergeArchivePlan 檔案;
- 合併檔案,將小檔案的內容都讀出來寫到新的合併檔案中去;
- 成功後刪除候選檔案。