數(shù)據(jù)湖是近年來比較火熱的領(lǐng)域讨阻,Apache Iceberg被譽(yù)為數(shù)據(jù)湖技術(shù)“三劍客”(Delta Lake窒所、Hudi殊橙、Iceberg)之一,而iceberg高度抽象和優(yōu)雅的設(shè)計(jì)成為了它最吸引人的優(yōu)勢邑滨,這一點(diǎn)也是我閱讀iceberg源碼的主要動力遥皂。
寫這一篇iceberg源碼閱讀主要目的叶眉,是想系統(tǒng)整理下過去Iceberg源碼閱讀過程中留下的記錄,一方面是回顧僻焚,另一方面也是方便在之后繼續(xù)閱讀源碼的過程中能更高效地查閱允悦,正所謂“書讀百遍,其義自現(xiàn)”虑啤,因此內(nèi)容會比較基礎(chǔ)隙弛,不會面面俱到架馋,也會摻雜一些我個人的理解。當(dāng)然全闷,對于想要接觸Iceberg源碼的同學(xué)叉寂,尤其是對于大數(shù)據(jù)體系還不太熟悉的同學(xué),這篇內(nèi)容也能有所幫助总珠。
本篇主要圍繞Iceberg文件組織和具體實(shí)現(xiàn)展開屏鳍。
準(zhǔn)備工作:
Iceberg的基礎(chǔ)介紹推薦Iceberg的官方文檔:https://iceberg.apache.org/
Iceberg的源碼見github(0.12版本):https://github.com/apache/iceberg
為節(jié)省篇幅,文中不會大篇幅引用代碼姚淆,因此推薦將代碼拉取到本地配合閱讀孕蝉。
基礎(chǔ)類介紹
一開始閱讀源碼的主要難點(diǎn)在于放眼望去都是陌生的類,不清楚這些類是什么作用腌逢,以及已經(jīng)應(yīng)該從哪里入手降淮,或者看完官方的文檔后,對文檔中功能的實(shí)現(xiàn)和代碼不能映射起來搏讶,這部分主要就是來解決這個問題佳鳖。
1.Iceberg 文件組織概述
閱讀源碼之前,首先應(yīng)該對Iceberg的功能設(shè)計(jì)做一個系統(tǒng)的了解媒惕,這部分官方文檔做了比較清晰的介紹系吩,我只對核心部分做一個簡要描述,看完后對這部分仍然不太理解的同學(xué)建議閱讀下官方文檔妒蔚。
在我看來穿挨,Iceberg最核心的功能就是對數(shù)據(jù)文件組織管理,正如Iceberg對自己的介紹:Apache Iceberg is an open table format for huge analytic datasets. 所謂的table format(表格式)肴盏,其實(shí)就是將底層的數(shù)據(jù)文件以特定的方式組織好科盛,再以表的方式暴露出來。
插一句題外話菜皂,從文件組織的功能上來說贞绵,這一點(diǎn)和Hive是類似的,只不過Hive對文件的組織比較粗糙恍飘,是以目錄的形式進(jìn)行組織榨崩,即只是聲名了某個分區(qū)的文件在哪個目錄下,而Iceberg精確到了文件級別章母,并且有單獨(dú)的metadata文件來索引數(shù)據(jù)文件母蛛,metadata文件中保存了數(shù)據(jù)文件的信息,比如最大值乳怎、最小值等溯祸,這樣,只需要讀少量的metadata文件,就能過濾掉大量不需要訪問的數(shù)據(jù)文件焦辅,這是Iceberg的一個重要特點(diǎn)博杖。
Iceberg的文件組織如下圖所示
我們重點(diǎn)關(guān)注下面的metadata layer層和data layer層,metadata layer層中的文件就是metadata文件筷登,而data layer層中的文件就是數(shù)據(jù)文件剃根。
metadata layer層中的文件分為三類:metadata file、manifest list前方、manifest file狈醉。名字比較相似,需要仔細(xì)區(qū)分惠险,下面會反復(fù)出現(xiàn)這些詞匯苗傅。
metadata file本質(zhì)上就是Iceberg表的所有信息,可以說從一個metadata file就可以構(gòu)建出一個完整的Iceberg Table班巩,包括表結(jié)構(gòu)渣慕、分區(qū)字段、所有快照等等信息抱慌,后面介紹的代碼的部分會詳細(xì)說明逊桦,每次對Iceberg Table做一次操作,都會產(chǎn)生一個新的metadata file文件抑进。
manifest list文件向上對應(yīng)的是一個快照(圖中的s0强经、s1),向下對應(yīng)的是若干個manifest file文件寺渗。
manifest file文件是對一系列數(shù)據(jù)文件的索引匿情,保存了每個數(shù)據(jù)文件的路徑等信息,也就是在這里信殊,Iceberg對文件的組織精確到了文件級別炬称。
補(bǔ)充:
之所以要拆分manifest list和manifest file兩層,主要的目的是為了復(fù)用歷史的manifest file鸡号,如圖中所示转砖,一個manifest file可以被多個manifest list索引须鼎;相反鲸伴,如果刪掉manifest list這一層,只保留manifest file晋控,則可以想象汞窗,每個manifest file都要索引到當(dāng)前快照到全部文件,不同manifest file之間會存在大量重復(fù)赡译,這點(diǎn)仔細(xì)推演一下仲吏,想必可以理解。
在這樣的設(shè)計(jì)下,Iceberg構(gòu)建了一套快照體系裹唆,這是Iceberg支持ACID特性誓斥、Time travel等功能的核心。簡單來說许帐,對Iceberg數(shù)據(jù)文件的任何增加或刪除劳坑,都會產(chǎn)生一個新的快照(伴隨產(chǎn)生一個新的manifest list文件),一系列快照組成了一個連續(xù)的快照鏈成畦,可以指定任何快照進(jìn)行讀染喾摇(只要快照沒有過期)。
為了更具體的展示循帐,我們實(shí)際創(chuàng)建一張iceberg表框仔,并append兩張數(shù)據(jù)文件,可以看到文件組織如下所示:
iceberg_table_test1/
├── metadata
│ ├── 4b461a01-cdca-49fe-85bc-cb0702fb76d1-m0.avro
│ ├── snap-2427807540524010093-1-4b461a01-cdca-49fe-85bc-cb0702fb76d1.avro
│ ├── v1.metadata.json
│ ├── v2.metadata.json
│ └── version-hint.text
├── data_file1
└── data_file2
iceberg有一個單獨(dú)的metadata目錄來保存metadata文件
- V1.metadata.json拄养、V2.metadata.json文件即metadata file
- snap-2427807540524010093-1-4b461a01-cdca-49fe-85bc-cb0702fb76d1.avro离斩,這個文件就是manifest list
- manifest file,如4b461a01-cdca-49fe-85bc-cb0702fb76d1-m0.avro
數(shù)據(jù)文件data_file1衷旅、data_file2默認(rèn)在根目錄捐腿,實(shí)際上可以寫在任意目錄下。
2.文件組織相關(guān)的基礎(chǔ)類
1.Table(org.apache.iceberg.Table)
推薦閱讀代碼的入口柿顶,是定義了對Iceberg表的所有操作的接口茄袖,實(shí)現(xiàn)類先關(guān)注org.apache.iceberg.BaseTable。
2.TableMetadata(org.apache.iceberg.TableMetadata)
從邏輯上看:TableMetadata就是表的元數(shù)據(jù)嘁锯,這個元數(shù)據(jù)不僅是最新的數(shù)據(jù)宪祥,也包含了變更歷史,比如schema家乘、partition蝗羊、sortOrders、snapshot的變更歷史都記錄在TableMetadata中仁锯。
從物理上看:映射到磁盤的metadata.json文件(metadata file)耀找,每個TableMetadata對象對應(yīng)一個metadata.json文件;
需要注意的是對表的每次操作都會產(chǎn)生一個新的metadata.json文件业崖,最新的一個metadata.json文件已經(jīng)包含了完整的信息野芒,包括歷史信息在內(nèi)。因此双炕,理論上只保留最后一個metadata.json文件即可狞悲。
對TableMetadata的讀寫等操作由TableOperations接口封裝。
3.ManifestFile(org.apache.iceberg.ManifestFile)
從邏輯上看:ManifestFile是對磁盤上manifest file的指針(索引)妇斤,記錄了manifest file的位置和一些統(tǒng)計(jì)信息摇锋;
從物理上看:ManifestFile會持久化到磁盤的manifest list文件丹拯,每個ManifestFile對象對應(yīng)manifestList文件中的一行記錄;
snapshot對應(yīng)一個唯一的manifestList文件(待確認(rèn)描述是否精確)荸恕;
4.ManifestEntry(org.apache.iceberg.ManifestEntry)
從邏輯上看:ManifestEntry是對磁盤上一個數(shù)據(jù)文件的指針(索引)乖酬,記錄了文件的位置和一些統(tǒng)計(jì)信息,此外還標(biāo)注了文件的status融求,snapshot_id剑刑,sequence_number
從物理上看:ManifestEntry會持久化到磁盤的manifest file,每個ManifestEntry對象對應(yīng)manifest file中的一行記錄双肤;
補(bǔ)充:
ManifestEntry中的status:entry中標(biāo)注了文件的三種狀態(tài)(Status):EXISTING ADDED DELETED施掏,分別表示這個文件是本次提交之前就存在的、本次提交新增的茅糜、本次提交刪除的七芭,需要注意的是,這里的DELETED和DeleteFile是兩個概念蔑赘,需要區(qū)分清楚狸驳;
ManifestEntry中的snapshot_id:ADDED和EXISTING,指的是文件第一次ADD時(shí)的snapshot_id缩赛,之后不會變化耙箍;DELETED,文件刪除時(shí)的snapshot_id酥馍;
ManifestEntry中的sequence_number:ADDED和EXISTING辩昆,指的是文件第一次ADD時(shí)的sequence_number,之后不會變化旨袒;DELETED汁针,null;
一個比較細(xì)節(jié)的疑問:在提交時(shí)歷史的manifest不一定會被重寫砚尽,因此單純通過ManifestEntry中記錄的ADDED/EXISTING判斷是否是本次提交添加的是不準(zhǔn)確的施无,需要上游manifestlist中的snapshot_id協(xié)助判斷,才能真正確定文件是不是這次提交添加的必孤。
5.ContentFile(org.apache.iceberg.ContentFile)
從物理上看:ContentFile是上述ManifestEntry中的一個屬性猾骡,也是ManifestEntry中的核心信息;
首先要明確一點(diǎn)敷搪,ContentFile代表的只是數(shù)據(jù)文件的元數(shù)據(jù)信息兴想,而不是文件的實(shí)際數(shù)據(jù)。
文件的元數(shù)據(jù)信息主要包括:文件類型购啄、文件路徑襟企、文件格式嘱么、分區(qū)信息狮含、以及一些統(tǒng)計(jì)信息等顽悼。
最頂層接口ContentFile<F>:F是指子類的具體類型
ContentFile分為兩種:接口DataFile和接口DeleteFile,這兩個接口的共同的實(shí)現(xiàn)邏輯在抽象類BaseFile中几迄。
BaseFile主要是實(shí)現(xiàn)了IndexedRecord接口蔚龙,這是一個avro接口,實(shí)現(xiàn)了這個接口意味著這個類可以持久化到avro文件中的一行記錄映胁,以及反過來從avro文件中構(gòu)造出原來的對象(使用了反射的方式實(shí)現(xiàn)木羹,這一部分和avro的SpecificData.SchemaConstructable的接口聲明有關(guān),如果類實(shí)現(xiàn)了這個街口解孙,則承諾一定提供一個構(gòu)造器允許傳入avro的schema坑填,用來構(gòu)造這個類)。
除了DataFile和DeleteFile弛姜,還有一個IndexedDataFile是ContentFile的直接實(shí)現(xiàn)脐瑰,IndexedDataFile是ContentFile的裝飾器,增加了IndexedRecord的實(shí)現(xiàn)廷臼,因此苍在,它的作用也是和avro文件進(jìn)行交互。
此外荠商,ContentFile還有一個方法splitOffset()方法寂恬,是這個文件推薦的劃分位置點(diǎn),說明這個文件可以在這些位置點(diǎn)上被劃分為多個部分莱没,進(jìn)行并發(fā)讀取初肉。
補(bǔ)充:
IndexedRecord接口是avro文件的接口,表示avro文件內(nèi)的一行記錄饰躲,且這行記錄的各個列的值是可以按照0朴译、1、2属铁、3...隨機(jī)訪問的眠寿,因此稱為indexed,類似于ArrayList<Object>焦蘑。
public interface IndexedRecord extends GenericContainer {
/**
* Set the value of a field given its position in the schema.
* <p>
* This method is not meant to be called by user code, but only by
* {@link org.apache.avro.io.DatumReader} implementations.
*/
void put(int i, Object v);
/**
* Return the value of a field given its position in the schema.
* <p>
* This method is not meant to be called by user code, but only by
* {@link org.apache.avro.io.DatumWriter} implementations.
*/
Object get(int i);
}
IndexedRecord的上級接口是GenericContainer盯拱,GenericContainer要求提供Schema。
public interface GenericContainer {
/** The schema of this instance. */
Schema getSchema();
}
類似于IndexedRecord例嘱,iceberg也定義了自己的StructLike狡逢,相比IndexedRecord,StructLike可以返回列的數(shù)量拼卵,而且可以在取出值時(shí)動態(tài)指定值的類型奢浑。
public interface StructLike {
int size();
<T> T get(int pos, Class<T> javaClass);
<T> void set(int pos, T value);
}
將StructLike裝飾成IndexedRecord的工具類為IndexedStructLike,使用這個工具腋腮,需要提供avroSchema雀彼,然后就可以將iceberg的StructLike寫成avro文件的一行記錄壤蚜。
3.其他基礎(chǔ)類
還有一些很重要的基礎(chǔ)類,比如和表結(jié)構(gòu)相關(guān)的Schema Type徊哑,和快照相關(guān)的Snapshot等袜刷,不放在本篇介紹,本篇聚焦在文件組織上莺丑。
文件組織的實(shí)現(xiàn)
文件組織實(shí)際上就是Iceberg對metadata layer層的操作著蟹,這一步操作最終會落實(shí)到對metadata file、manifest list梢莽、manifest file文件這三類文件的操作萧豆。
這些對文件的組織全部在Table接口中暴露出來,包括對文件的增加昏名、刪除炕横、替換等等,所有這些操作的繼承關(guān)系如下葡粒。
1.頂級接口:PendingUpdate
最上層的接口是PendingUpdate份殿,這個接口邏輯很簡單,主要定義了apply和commit兩個操作步驟嗽交,后來還為監(jiān)聽機(jī)制添加了updateEvent接口(可以參考https://github.com/apache/iceberg/pull/939卿嘲,暫時(shí)不在這里討論)。
2.第二級接口:重點(diǎn)SnapshotUpdate
第二級接口總共包括:ReplaceSortOrder夫壁、UpdateLocation拾枣、Rollback、UpdateProperties盒让、UpdateSchema梅肤、ExpireSnapshots、UpdatePartitionSpec邑茄、ManageSnapshots姨蝴、SnapshotUpdate
當(dāng)前重點(diǎn)關(guān)注的:SnapshotUpdate
SnapshotUpdate是所有新生成snapshot的操作的頂級接口,額外定義了三個操作:
- set:設(shè)置snapshot summary
- deleteWith:重新定義刪除文件的方式
- stageOnly:參照https://github.com/apache/iceberg/pull/342
3.第三級接口:
SnapshotUpdate的下級接口肺缕,首先是功能描述接口左医,如下:
- ReplacePartitions
- RewriteFiles
- RowDelta
- AppendFiles
- RewriteManifests
- OverwriteFiles
- DeleteFiles
然后是上述功能接口的具體實(shí)現(xiàn):SnapshotProducer
SnapshotProducer是SnapshotUpdate的抽象實(shí)現(xiàn),上述功能接口的實(shí)現(xiàn)都是基于SnapshotProducer同木,而這些功能接口的絕大多數(shù)實(shí)現(xiàn)都是基于SnapshotProducer的抽象子類MergingSnapshotProducer浮梢,只有兩個具體實(shí)現(xiàn)是特例:FastAppend BaseRewriteManifests。
因此彤路,對于實(shí)現(xiàn)我們重點(diǎn)來討論這一對父子:SnapshotProducer和MergingSnapshotProducer秕硝。
首先:對于SnapshotProducer
SnapshotProducer提供的通用實(shí)現(xiàn):
- apply
- Commit
SnapshotProducer擴(kuò)展的方法主要是一些對manifest的通用的操作實(shí)現(xiàn),可供子類調(diào)用洲尊,比較核心的只有一個apply方法
- protected abstract List<ManifestFile> apply(TableMetadata metadataToUpdate);
總的來說远豺,SnapshotProducer只需要子類實(shí)現(xiàn)一個核心邏輯:在舊的TableMetadata的基礎(chǔ)上實(shí)現(xiàn)產(chǎn)生新的manifestFiles奈偏,其他問題SnapshotProducer會解決。
接著:對于MergingSnapshotProducer憋飞,從類名上就可以看出,這個類要進(jìn)行merge操作姆吭,這個merge指的是manifest file的合并榛做;與此相對照的是FastAppend,F(xiàn)astAppend每次都是生成新的manifest file内狸,不進(jìn)行manifest file的合并检眯,因此FastAppend提交的代價(jià)更低,但是可能引起manifest file的碎片化昆淡。除了FastAppend操作之外锰瘸,其他繼承自MergingSnapshotProducer的操作都是要進(jìn)行manifest合并的,比如和FastAppend相對應(yīng)的MergeAppend昂灵。
MergingSnapshotProducer提供的通用實(shí)現(xiàn):
- apply:實(shí)現(xiàn)了上述SnapshotProducer要求的接口避凝,因此MergingSnapshotProducer的子類不需要再實(shí)現(xiàn)apply,只有特殊情況下需要額外修改一下apply方法眨补,專指BaseReplacePartitions管削。
具體看下MergingSnapshotProducer對apply的實(shí)現(xiàn):
圍繞4個成員變量:
- filterManager:對data文件的manifest file的過濾,類ManifestFilterManager
- mergeManager:對data文件的manifest file的合并撑螺,類ManifestMergeManager
- deleteFilterManager:對delete文件的manifest file的過濾含思,類ManifestFilterManager
- deleteMergeManager:對delete文件的manifest file的合并,類ManifestMergeManager
先看下ManifestMergeManager ManifestFilterManager的作用甘晤,這兩個類沒有繼承關(guān)系含潘。
ManifestFilterManager是進(jìn)行ManifestFile過濾的工具,簡單來說线婚,是將(磁盤上)舊的manifest中的需要刪除的entry剔除遏弱,生成新的manifest file(或者不存在需要刪除的entry時(shí),保留舊的manifest file)塞弊。
ManifestMergeManager是進(jìn)行ManifestFile合并操作的工具腾窝,簡單來說,是將(磁盤上)舊的manifest合并成若干個大小不超過8m的新的manifest(合并不是必然觸發(fā)的居砖,受TableProperties控制)虹脯。
合并后寫入到新的manifest時(shí),會剔除對舊的Status=DELETED文件的索引奏候,如果manifest上記錄的是本次添加的文件循集,則記錄為ADD,如果是之前添加的文件蔗草,則記錄為EXISTED咒彤,如果是本次刪除的文件疆柔,則記錄為DELETED,如果是之前產(chǎn)生的deleteFile镶柱,則忽略旷档。
不管是ManifestMergeManager還是ManifestFilterManager都會進(jìn)行manifest文件的讀寫,因此都可能是很耗時(shí)的操作歇拆。
MergingSnapshotProducer沒有將上述4個成員變量直接暴露給子類操作鞋屈,而是暴露了一些語義更加簡單的方法,比如添加數(shù)據(jù)文件故觅、刪除數(shù)據(jù)文件厂庇、按照Expression過濾等,這樣各種文件組織操作的具體子類输吏,只需要調(diào)用這些接口就能實(shí)現(xiàn)自己的功能权旷,各類操作的詳細(xì)實(shí)現(xiàn)不再展開討論。