資料湖Iceberg技術在小米的落地與場景應用

ITPUB社群 發表於 2022-11-24

導讀:隨著流批一體技術的發展,和對實時查詢的需求以及出於成本的最佳化考慮,小米對資料湖 iceberg 技術做了一些實踐和場景落地。

今天介紹的內容主要有以下四個方面:

  • Iceberg技術簡介

  • Iceberg在小米的應用實踐

  • 基於Iceberg的流批一體的探索

  • 未來規劃

分享嘉賓|李培殿 小米 軟體研發工程師

編輯整理|陳業利 英祐科技

出品社群|DataFun



01
Iceberg技術簡介

首先介紹一下Iceberg技術。

Iceberg是一個基於大型分析型資料上的一個表格式,它允許將一些檔案、資料集以表的形式提供給spark、trino、prestodb、flink、hive這些計算引擎。

資料湖Iceberg技術在小米的落地與場景應用

透過下面右圖可以看到iceberg所處的位置,與hudi, delta lake相同。

透過iceberg這個抽象層,將上層的計算與下層的儲存進行分離,這樣就使我們在儲存和計算上的選擇更靈活。

下層有parquet、orc、avro可以選擇,最底層的實際物理儲存上可以選擇s3, aliyun oss以及HDFS。透過iceberg這個抽象,最大的優勢是可以將底層檔案的細節對使用者遮蔽,使用者可以透過表的方式去訪問,而不需要關心底層到底是存了什麼格式的檔案,或是存在哪裡。

資料湖Iceberg技術在小米的落地與場景應用

Iceberg實現的本質原理是一種檔案的組織方式,包括4級的結構。

從下往上看,最底層是一些寫入的資料檔案,即data file。當我們寫完這些data file之後,它的後設資料檔案,會有一些清單檔案,即manifest檔案,來記錄檔案和分割槽的關係。在iceberg也有分割槽的概念,分割槽和檔案對應關係都記錄在清單檔案裡面。當這個清單檔案寫完之後都會形成一個快照,每次commit都會形成一個新的快照。快照會透過metadata檔案來進行記錄。這樣如果我們使用一些歷史回溯,就可以透過這個檔案索引去確定使用哪些快照。

資料湖Iceberg技術在小米的落地與場景應用

Iceberg有三大優勢,即:事務性、隱式分割槽和行級更新。

事務性帶來的好處是:

  • 首先,可以避免寫入失敗帶來的髒資料。

  • 其次,我們可以用剛才所說的快照的方式來實現讀寫分離。以hive為例,比如hive讀的程式已經啟動,結果這些檔案剛好被overwrite了,就可能會導致讀程式的失敗。而iceberg這種使用快照方式透過讀到不同的快照來進行分離,做到讀寫的隔離。

資料湖Iceberg技術在小米的落地與場景應用

Iceberg的第二大優勢是它提供了一個隱式分割槽。

我們可以在建表的時候對一個列做一個轉換,用一個如transform的函式來做一個隱式分割槽。這樣當我們寫入資料的時候,不需要額外指定一個分割槽來寫入,直接寫就可以了,它會根據資料去判定到底落在哪個分割槽,而且它的分割槽也不跟目錄強繫結。另外它提供了partition evolution機制,提供靈活的分割槽變更,當我們在把月替換成天,底層查詢時會執行不同的查詢計劃。

資料湖Iceberg技術在小米的落地與場景應用

它的第三大優勢是行級更新的能力。

iceberg現有兩個版本,即V1版本和V2版本,我們經常稱V1表和V2表。V1表的更新採用Copy On Write(COW)模式,就是將需要更新的檔案讀取出來做更新後再寫入。在V2表中除了Copy On Write,還增加了Merge On Read(MOR)。

下圖是對Merge On Read的一個簡單介紹,它透過記錄另外兩個檔案,即position delete和equality delete檔案來對已有的檔案進行刪除,當讀取的時候進行merge得到最終的結果。

資料湖Iceberg技術在小米的落地與場景應用

02

Iceberg在小米的應用實踐

介紹了iceberg的一些背景知識後,這一章將介紹iceberg在小米的一些實踐場景。

現在iceberg在小米大概有4000多張表,資料大概是8PB,其中v1表1000多張,v2表3000多張。v1表主要是一些日誌場景,v2表是可以配一些需要透過主鍵進行更新的場景。

資料湖Iceberg技術在小米的落地與場景應用

v2表最多的場景就是一些cdc資料,也就是ChangeLog的資料入湖。鏈路大概如下,透過flink cdc採集mysql、oracle、tidb的一些資料,打到中間的mq,然後透過flink寫到iceberg的v2表裡面。

資料湖Iceberg技術在小米的落地與場景應用

這樣做的優勢在於:

  • 首先我們可以對cdc資料進行近實時分析。

  • 另外,iceberg可以實現支援flink引擎,因此我們可以進行流式消費,在這一塊上我們開發了對v2表進行flink流式消費的支援。

  • 第三,可以同步變更schema,比如上游的mysql schema變更了,我們可以在這個鏈路中將schema變更同步到iceberg。這樣,使用者不需要去關心整個鏈路的變動,直接去取下游的iceberg表就可以了。

  • 第四,使用iceberg來替換一些專有型的資料庫,價格更便宜。比如我們之前有些鏈路使用的是kudu,我們在某些適應場景用iceberg來替換它,成本更低。

資料湖Iceberg技術在小米的落地與場景應用

我們在資料入湖之中也遇到了一些問題,主要是像mysql這類以自增id為主鍵的資料入湖。我們給使用者提供兩種資料分割槽的方式,第一種是Bucket分割槽,第二種是Truncate分割槽。在以自增id為主鍵的場景我們更推薦使用者使用Truncate分割槽而不是Bucket分割槽。

Bucket分割槽示例如下,即分桶的形式,比如有4個桶,當自增id來了之後,相對來說可以比較均勻的分佈。然而這樣也就帶來一個問題,因為merge on read的效能比較差,需要進行非同步的compaction,可能就需要對所有的都進行compaction。另外在當前這個iceberg版本當中,不停止上游作業的情況下v2表的bucket分割槽是不可以修改的。這樣隨著資料量的增長,由於我們的分割槽不能變,可能會導致分割槽資料量變大,查詢效能就會越來越差。

而在truncate分割槽下,我們可以看到,它的分割槽是可以擴充套件的,而且由於基於自增id,資料寫入和compaction只會集中在某幾個分割槽。所以在這種場景下我們更建議使用者使用truncation分割槽。

資料湖Iceberg技術在小米的落地與場景應用

以下是我們資料入湖的一個簡單產品化的頁面,有一個schema的對應關係,左邊是mysql,右邊是我們的iceberg表。在這個示例中,使用者沒有選擇使用自增主鍵,而是選擇了一個自己的業務id(order_id)來做key。

資料湖Iceberg技術在小米的落地與場景應用

第二個iceberg常用的場景就是日誌入湖。選用iceberg優於之前的hive的原因在於以下幾點:

  • 第一點是隱式分割槽避免了在凌晨的時候出現資料漂移問題。

  • 第二點是隱式分割槽帶來的特性,延遲的資料我們可以將它落入到正確的分割槽,這點是使用者非常關心的,在小米電視裝置上的一些打點上報的資料的延遲情況是非常嚴重的。如果使用者選擇以前那種方式而不是資料湖,可能會導致最近幾個分割槽錯誤,可能就需要去搞另外一個表把這些資料重新存一遍,那麼下游也需要再重新算一遍。隱式分割槽就可以幫助解決這個延時資料正確的問題。使用者只需要對下游重新算一遍就可以了。

  • 第三點是flink的exactly once以及iceberg事務效能夠保證資料的不丟不重。

  • 第四點是在我們的日誌場景下也可以支援schema同步變更。

資料湖Iceberg技術在小米的落地與場景應用

這是我們內部的一個日誌入湖場景下的產品化。我們在最後一行加了個時間分割槽的對映,使用者可以選擇Talos(Kafka)的一個記錄時間,也可以選擇以一個實際的業務時間來做時間分割槽。這樣使用者在資料入湖時就有更多選擇。

資料湖Iceberg技術在小米的落地與場景應用

剛才我們提到了compaction,為了維護上層的作業正常,我們在後臺有這樣三個服務,這三個服務都是以spark的作業形式來執行:

  • 第一個是Compaction服務,用於合併小檔案。對於v2表來說,除了合併小檔案,也可以對資料的delete檔案進行提前merge。

  • 第二個是Expire snapshots服務,用於處理過期的snapshots。如果snapshots一直不清理,那麼後設資料的檔案就會越來越多,這會導致膨脹問題。因此需要服務定期做一些清理。

  • 第三個是Orphan Files clean服務,由於一些事務的失敗,或者一些快照的過期,導致檔案在後設資料檔案中已經不再引用了,需要定期清理掉。

資料湖Iceberg技術在小米的落地與場景應用

以上兩個產品基本上可以覆蓋資料整合的所有場景,無論是cdc資料還是日誌資料都可以灌到iceberg上。下一步就是想讓使用者去做一下技術架構的迭代,推動從Hive升級到Iceberg。在這一塊其實也遇到了很多問題,主要問題是使用者對這個新技術的接納性並不是很高。

後來我們調研了Parquet+ZSTD的技術方式。除了iceberg本身的優點,Parquet+ZSTD的方式還可以節約成本,這個是使用者比較關心的優點。如下可以看到當我們切換到Parquet + ZSTD之後,TEXT資料可以壓縮節約80%,因為TEXT資料本身是沒有壓縮的,因此這個效果比較好。像一些通用的SNAPPY+Parquet,也可以節約30%的儲存。當然,我們現在選擇的ZSTD級別是國內比較流行的level3級別,這個是在壓縮效率和壓縮時間上都比較合適的一個level。如果我們選擇更高的壓縮率,可能會導致壓縮時間更長,這可能在使用者作業中是不可接受的。所以我們也提供可以在 Compaction 階段配置更高的壓縮級別的選項,供有需要獲得更高的壓縮率的使用者自行選擇。

資料湖Iceberg技術在小米的落地與場景應用

為了方便使用者從Hive升級到Iceberg,我們也做了一個產品化。

  • 第一步會生成一張和hive表 schema 結構相同的iceberg表,並將歷史資料複製過來,這裡選擇複製歷史資料而不是引用原檔案是因為這樣可以對歷史資料進行壓縮,降低儲存成本;

  • 第二步是對上游的寫入作業做一個升級;

  • 第三步將下游作業也進行遷移。

資料湖Iceberg技術在小米的落地與場景應用

03

基於Iceberg的流批一體的探索

介紹完當前場景後,我們更進一步基於iceberg,做了一些流批一體上的探索。

我們現在的架構是基於常規的Lambda架構,如下的這條T+1鏈路,在ODS層算完之後,每天凌晨使用Spark或者MR去計算。有時使用者有實時的需求,比如一些實時大屏,就需要用Flink+Talos(Kafka)來搭建一條實時鏈路,這樣使用者就需要維護兩條鏈路。在實時鏈路上提供時效性,在離線鏈路上提供準確性。在hive離線鏈路上,如果資料錯誤可以做回溯,離線入湖上也可以支援查。

但是也存在一些缺點,比如在實時鏈路上, Talos/Kafka目前沒有辦法做一些OLAP查詢,由於它不能儲存全量的資料,它的回溯處理能力也有限,這樣我們就需要維護兩套程式碼和兩套儲存。也要面臨到實時離線資料不一致的情況。

資料湖Iceberg技術在小米的落地與場景應用

下面是我們對iceberg批流一體的建設,我們會將儲存層,在ODS, DWD, DWS層全部換成iceberg。這樣的好處是可以在儲存上實現統一,不需要Kafka和Hive兩套儲存。如果將離線鏈路引擎切換成Flink,可以在Flink上實現計算引擎的統一。

另外,我們也可以做一些回溯,在一些開放的鏈路中提供一些實時查詢。基於iceberg的v2表我們還可以去構建一個變更流。從業務角度來看,也是一個不錯的實現方式。

資料湖Iceberg技術在小米的落地與場景應用

我們經常提到,在批流一體中為什麼需要一個離線作業來修資料?除了回溯資料導致的資料問題外,還有以下幾個原因:

  • 第一個就是Flink狀態過期了,由於記憶體等一些原因,狀態不能一直儲存,Flink的狀態過期了導致一直沒能join上。

  • 第二個是因為一些視窗的設定或者watermark的設定導致資料延遲資料丟失。

  • 第三個就是Lookup join維表完成之後,維表又發生了一些變更。

資料湖Iceberg技術在小米的落地與場景應用

對於離線的修正,目前一般用overwrite覆蓋分割槽的方式,overwrite的語義是將原來的分割槽刪除掉,然後追加進去新的資料。在spark,flink和iceberg的結合中,有一個merge Into的語法。它的語法簡單如下,merge into到一個目標表,我們選擇一個資料來源,然後將資料來源的資料merge到目標表中,需要一個on關鍵字指定連線詞進行連線,在這裡也可以指定一個目標表的分割槽來只對指定分割槽進行變更。在 merge into 語句中可以進行下面三個操作:如果我們join上(match)我們可以對目標表資料進行一個刪除,或者是一個更新。如果沒有match上,則可以執行插入操作。

資料湖Iceberg技術在小米的落地與場景應用

這兩種修復資料的方式有它們自身的一些特點

Overwrite是分割槽覆蓋的方式,相對merge Into來說其自身語法簡單,效能也比較好,不需要進行join操作,但缺點是使用overwrite去覆蓋歷史分割槽的時候,下游的實時作業還在跑,如果讀到了這部分資料可能導致下游的實時資料出現波動。

當使用merge Into的模式寫入,可以實現增量的更新,只更新變更的記錄。但語法比較複雜,並且需要做join,其效能比overwrite要差一些,但它的優點就是下游只會消費到一些變更的資料,對下游的影響比較小。

資料湖Iceberg技術在小米的落地與場景應用

下圖是用Merge Into去做修正的鏈路。上層的hive表或者iceberg表透過merge into的語法去增量的更新,這些增量更新的資料會透過flink sql對它做一些變更更新到下一層(MySQL)。這條鏈路還可以做增量同步。比如將Hive的一些資料變更增量同步到Mysql。如果我們全量同步寫入MySQL的話會造成MySQL的一些波動。使用merge into寫到iceberg裡面,然後將iceberg的變更增量同步到mysql。

資料湖Iceberg技術在小米的落地與場景應用

因為iceberg的隱式分割槽的特性,會帶來一些分割槽上的選擇。比如在構建這個鏈路中,一般如果以天為分割槽的話,會有兩種選擇:

  • 第一種是使用處理時間作為分割槽,這樣的話使用者可以將實時的資料落到當天的分割槽,也可以離線的去修昨天的資料(overwrite或用merge into去修)。這樣做的優點是實時和離線資料沒有交集。

  • 另外一種是選擇一些事物的時間作為分割槽,比如最常見的一些訂單的建立時間做為分割槽。這樣,當我們在變更的場景下實時的寫入資料會操作多個分割槽,離線修復全量資料也會操作多個分割槽,帶來的一個問題就是實時和離線處理的資料存在一個交集,在 iceberg 中處理的資料有交集則有可能出現提交衝突導致作業失敗。

資料湖Iceberg技術在小米的落地與場景應用

Merge Into透過隔離級別處理有存在交集資料(事務)帶來的問題。

在Merge Into實現上,每個引擎對不同的語法有不同的實現,它提供了兩個隔離級別。

  • 第一個隔離級別是最高的隔離級別,“可序列化隔離級別”,它也是預設的隔離級別。如果配置這個級別,在merge into事務提交的過程中,如果有其它已經提交的事務和本次事務操作相同的檔案,那麼這個作業就會失敗掉。這種情況下使用者作業可能會經常失敗,我們可能需要一些其它的辦法,比如我們在實時上去做一個過濾只處理當天的一個分割槽,把其它的歷史分割槽交由離線去做修正來避免這些衝突。

  • 第二個是“快照隔離級別”,在快照隔離級別下,在我們merge Into提交的時候,會提交的一些衝突的事務給覆蓋掉,此時如果有多個作業同時去寫,它們到底是哪一個提交成功,這種情況是不可預知的。這導致我們結果其實也不準確。這兩種問題也是當時我們設想過的使用這兩種隔離界別都會存在一些問題。

資料湖Iceberg技術在小米的落地與場景應用

下圖是一個正式應用中使用的批流一體鏈路,圖中紅線的流程是初始化資料的階段,可以使用 spark 或者 flink。

當初始化完成之後,就可以把streaming作業跑起來,一般來說從ODS到DWD這一層,如果只是做一些簡單的資料清洗邏輯,這塊並不需要去做修正,當在DWD到下一層的時候,可能需要做一些修復,這裡就選用spark merge into對DWM層做一個修復,完成之後這個streaming作業把包括當天實時的以及一些修正的資料都寫到下一層,比較方便。

資料湖Iceberg技術在小米的落地與場景應用

以上就是我們對批流一體的探索。

04

未來規劃

最後介紹一下我們對未來的規劃。

  • 對Flink CDC 2.0持續跟進。Flink CDC2.0當中對全量和增量的切換比較友好,而我們現在的實現方式是參考hypersource的方式來做切換的。

  • 最佳化治理compaction 服務。當前iceberg做compaction都是在後臺去執行的,這樣每個表(尤其是where表)都需要起一個作業,如果多了的話會有資源佔用的問題。

  • 跟進iceberg和flink1.14的結合。當前我們的flink是1.12的版本,它的source設計沒有那麼的穩定,我們去讀iceberg的時候經常會遇到一些反壓,我們會在後面去跟進一下flink1.14和iceberg的結合。

資料湖Iceberg技術在小米的落地與場景應用

05

問答環節

Q1:Iceberg資料如何把資料回滾另一個snapshot版本?

A1:Iceberg有一個語法(rollback)可以回滾到指定snapshot。

Q2:Upsert生成過多小檔案導致合併時候OOM?

A2:因為生成equality file量太多,在我們實踐當中也經常會遇到這種問題,其中一方面需要解決衝突問題,另一方面增加策略,條件判斷等,提高Compaction排程次數(如每10分鐘排程一次)。

Q3:Upsert基於分割槽還是基於主鍵來更新? 

A3:Upsert是基於主鍵來更新的,但是要求分割槽必須在主鍵中選擇。比如我們基於id更新的話,分割槽只能以id來做,不管bucket分割槽還是truncate分割槽。

Q4:如果需要按月年做財務賬單資料,那麼flink如何讀取資料?

A4:跟實際業務有關,flink 流式消費讀到的都是增量資料,需要進行一些視窗的運算。

Q5:Iceberg成熟度是晚於Hudi的,因此hudi的成熟度也不會比iceberg差,那麼為什麼要選用iceberg做資料湖技術?

A5:我們是在去年5月份開始調研,當時iceberg的版本是0.10,hudi版本在0.9左右,還有沒有正式release。之所以選擇iceberg是因為iceberg要比hudi早一點支援flink。因為我們使用者比較看重flink流式的場景。當時的hudi版本還沒有完全和spark解耦以及支援flink。如果使用者在做具體選擇時候可能跟自己的業務場景來做判斷,比如是一些日誌場景還是變更資料的場景,然後可以參考社群的活躍度以及各個大廠的使用情況,這是當時我們的一些評判方法。

Q6:現在小米落地的iceberg表是v1還是v2?

A6:都有,如果是變更資料處理v2表比較多,日誌的話v1比較多。

Q7:現在小米只有iceberg和hive,然後從hive切換到iceberg的量大概有多少比例?

A7:從hive切換到iceberg的比例不是非常高,跟歷史原因有點關係,像iceberg對離線spark支援的最低版本是2.4,而我們內部的spark版本有2.3,對使用者的jar作業切換比較困難。因此在新場景新業務接的比較多,然後也在做產品化來推動hive到iceberg的遷移,但是過程比較漫長,需要業務更新技術棧,比如從MR/spark2.3切換到spark3.1。

Q8:在業務場景用Iceberg究竟可以解決什麼問題,有哪些收益?

A8:這個也是我們跟使用者分享中使用者反饋的。我們講的iceberg事務性使用者並不是很關心。有幾個點是使用者比較關心的:第一我們可以解決ods層資料準確性問題;第二隱式分割槽可以解決延遲資料問題;第三用zstd壓縮方式可以降本增效,吸引使用者切換到iceberg。

Q9:大資料量下upsert效能如何?

A9:當前 upsert 寫入不會成為瓶頸,但 compaction 可能會比較慢。大批次資料離線入湖還是建議先去重然後再 merge 寫入;如果是流式入湖則可以增大二級分割槽。

Q10:資料是如何儲存的,存在哪裡?什麼格式?

A10:目前都是儲存在hdfs上,儲存格式很多,有parquet,還有text格式的資料。換用 iceberg 後統一使用 parquet 來儲存。

Q11:跟alluxio比較有什麼差異?

A11:iceberg是上層對檔案的一個表抽象,alluxio可以做檔案的快取,提高查詢的效能。

Q12:如果更新過於頻繁,如何解決檔案數量的膨脹?

A12:1. 增大flink的checkpoint間隔,可以降低檔案數量,2. 檔案數量跟分割槽有關係,選擇合適的分割槽也不會產生小檔案問題。

Q13:Iceberg/Hudi都缺一層快取,未來如何考慮?

A13:iceberg、hudi這兩個都是檔案抽象層,都是可以對alluxio做支援,社群都有在跟進

Q14:底層支援clickhouse嗎?

A14:目前是不支援,不過可能會基於 iceberg 建外表。

Q15:Iceberg和olap的StarRocks有什麼關係?

A15:這兩個還是在場景使用方面,Iceberg更偏向於數倉中構建的pipeline的鏈路,StarRocks更偏向多維分析的場景,在數倉設計中更靠上的一層。

Q16:為什麼在流批一體的flink鏈路中,在dwd層加了個spark任務?

A16:因為目前社群版本只有spark支援merge into語法,我們內部其實實現了flink的類merge into語法,另外也可以使用flink的overwrite來更新。

Q17:業務表入湖資料表結構變化是怎麼實現的?(mysql schema變化到iceberg schema變化的問題)

A17:我們實現了一個定製化作業,去捕捉ddl資訊,對iceberg的schema做變更,對於talos(kafka)的ddl我們捕捉不到,我們採用的是用上游訊息的schema去替換iceberg的schema。

Q18:如何低延遲讀取資料,資料延遲情況?

A18:分鐘級別,flink構建的鏈路依賴flink的checkpoint配置,我們建議使用者設定5分鐘延遲,也可以3分鐘,最低不能低於1分鐘。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2924964/,如需轉載,請註明出處,否則將追究法律責任。