實(shí)時(shí)數(shù)據(jù)湖在字節(jié)跳動(dòng)的實(shí)踐
?導(dǎo)讀:今天分享的主題是實(shí)時(shí)數(shù)據(jù)湖在字節(jié)跳動(dòng)的實(shí)踐,將圍繞下面四點(diǎn)展開:
- 對(duì)實(shí)時(shí)數(shù)據(jù)湖的解讀
- 在落地實(shí)時(shí)數(shù)據(jù)湖的過程中遇到的一些挑戰(zhàn)和應(yīng)對(duì)方式
- 結(jié)合場景介紹實(shí)時(shí)數(shù)據(jù)湖在字節(jié)內(nèi)部的一些實(shí)踐案例
- 數(shù)據(jù)湖發(fā)展的一些規(guī)劃
01對(duì)實(shí)時(shí)數(shù)據(jù)湖的解讀
數(shù)據(jù)湖的概念是比較寬泛的,不同的人可能有著不同的解讀。這個(gè)名詞誕生以來,在不同的階段被賦予了不同的含義。

數(shù)據(jù)湖的概念最早是在Hadoop World大會(huì)上提出的。當(dāng)時(shí)的提出者給數(shù)據(jù)湖賦予了一個(gè)非常抽象的含義,他認(rèn)為它能解決數(shù)據(jù)集市面臨的一些重要問題。其中最主要的兩個(gè)問題是:首先,數(shù)據(jù)集市只保留了部分屬性,只能解決預(yù)先定義好的問題;另外,數(shù)據(jù)集市中反映細(xì)節(jié)的原始數(shù)據(jù)丟失了,限制了通過數(shù)據(jù)解決問題。
從解決問題的角度出發(fā),希望有一個(gè)合適的存儲(chǔ)來保存這些明細(xì)的、未加工的數(shù)據(jù)。因此在這個(gè)階段,人們對(duì)數(shù)據(jù)湖的解讀更多是聚焦在中心化的存儲(chǔ)之上。
不同的云廠商也把自己的對(duì)象產(chǎn)存儲(chǔ)產(chǎn)品稱為數(shù)據(jù)湖。比如AWS在那個(gè)階段就強(qiáng)調(diào)數(shù)據(jù)湖的存儲(chǔ)屬性,對(duì)應(yīng)的就是自家的對(duì)象存儲(chǔ)S3。在Wiki的定義中也是強(qiáng)調(diào)數(shù)據(jù)湖是一個(gè)中心化存儲(chǔ),可以存海量的不同種類的數(shù)據(jù)。但是當(dāng)對(duì)象存儲(chǔ)滿足了大家對(duì)存儲(chǔ)海量數(shù)據(jù)的訴求之后,人們對(duì)數(shù)據(jù)湖的解讀又發(fā)生了變化。
第二階段,對(duì)數(shù)據(jù)湖的解讀更多是從開源社區(qū)和背后的商業(yè)公司發(fā)起的。比如Databricks 作為一個(gè)云中立的產(chǎn)品,它將云廠商的這個(gè)對(duì)象存儲(chǔ)稱為 data lakes storage,然后把自己的重心聚焦在如何基于一個(gè)中心化的存儲(chǔ)構(gòu)建一個(gè)數(shù)據(jù)分析、數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)的數(shù)據(jù)湖解決方案,并且把這個(gè)方案稱之為lake。他們認(rèn)為在這個(gè)中心化的存儲(chǔ)之上構(gòu)建事務(wù)層、索引層、元數(shù)據(jù)層,可以解決數(shù)據(jù)湖上的可靠性、性能和安全的問題。
與此同時(shí),Uber最初也將Hudi對(duì)外稱為一個(gè)事務(wù)型的數(shù)據(jù)湖,名字實(shí)際上也是由 Hadoop Updates and Incrementals縮寫而來,最早也是被用于解決Uber內(nèi)部離線數(shù)據(jù)的合規(guī)問題。現(xiàn)在他們更傾向的定義是一個(gè)流式數(shù)據(jù)湖平臺(tái),Iceberg也常常被人們納入數(shù)據(jù)湖的討論。盡管Ryan Blue一直宣稱 Iceberg 是一個(gè)Open Table Format。這三者有一些共同點(diǎn),一個(gè)是對(duì) ACID的支持,引入了一個(gè)事務(wù)層,第二是對(duì) streaming 和 batch的同等支持,第三就是聚焦在如何能更快地查詢數(shù)據(jù)。國內(nèi)也有人將 Hudi、Iceberg、Delta Lake稱為數(shù)據(jù)湖的三劍客。
講完了業(yè)界的解讀,來看一下字節(jié)跳動(dòng)對(duì)數(shù)據(jù)湖的解讀。我們是結(jié)合字節(jié)的業(yè)務(wù)場景來解讀的。通過實(shí)踐總結(jié),我們發(fā)現(xiàn)數(shù)據(jù)湖需要具備六大能力:

- 高效的并發(fā)更新能力
因?yàn)樗軌蚋淖兾覀冊(cè)?Hive 數(shù)倉中遇到的數(shù)據(jù)更新成本高的問題,支持對(duì)海量的離線數(shù)據(jù)做更新刪除。
- 智能的查詢加速
用戶使用數(shù)據(jù)湖的時(shí)候,不希望感知到數(shù)據(jù)湖的底層實(shí)現(xiàn)細(xì)節(jié),數(shù)據(jù)湖的解決方案應(yīng)該能夠自動(dòng)地優(yōu)化數(shù)據(jù)分布,提供穩(wěn)定的產(chǎn)品性能。
- 批流一體的存儲(chǔ)
數(shù)據(jù)湖這個(gè)技術(shù)出現(xiàn)以來,被數(shù)倉行業(yè)給予了厚望,他們認(rèn)為數(shù)據(jù)湖可以最終去解決一份存儲(chǔ)流批兩種使用方式的問題,從而從根本上提升開發(fā)效率和數(shù)據(jù)質(zhì)量。
- 統(tǒng)一的元數(shù)據(jù)和權(quán)限
在一個(gè)企業(yè)級(jí)的數(shù)據(jù)湖當(dāng)中,元數(shù)據(jù)和權(quán)限肯定是不能少的。同時(shí)在湖倉共存的情況下,用戶不希望元數(shù)據(jù)和權(quán)限在湖倉兩種情況下是割裂的。
- 極致的查詢性能
用戶對(duì)于數(shù)據(jù)湖的期望就是能夠在數(shù)據(jù)實(shí)時(shí)入湖的同時(shí)還能做到數(shù)據(jù)的秒級(jí)可視化。
- AI + BI
數(shù)據(jù)湖數(shù)據(jù)的對(duì)外輸出,不只局限于BI,同時(shí)AI也是數(shù)據(jù)湖的一等公民,數(shù)據(jù)湖也被應(yīng)用在了字節(jié)的整個(gè)推薦體系,尤其是特征工程當(dāng)中。實(shí)時(shí)數(shù)據(jù)湖其實(shí)是數(shù)據(jù)湖之上,更加注重?cái)?shù)據(jù)的實(shí)時(shí)屬性或者說流屬性的一個(gè)數(shù)據(jù)湖發(fā)展方向。當(dāng)然,正如業(yè)界對(duì)于數(shù)據(jù)湖的解讀一直在演變,我們對(duì)數(shù)據(jù)湖的解讀也不會(huì)局限于以上場景和功能。
02落地實(shí)時(shí)數(shù)據(jù)湖過程中的挑戰(zhàn)和應(yīng)對(duì)方式
接下來介紹數(shù)據(jù)湖落地的挑戰(zhàn)和應(yīng)對(duì)。字節(jié)內(nèi)部的數(shù)據(jù)湖最初是基于開源的數(shù)據(jù)湖框架Hudi構(gòu)建的,選擇Hudi,最簡單的一個(gè)原因是相比于 Iceberg 和 Delta Lake,Hudi原生支持可擴(kuò)展的索引系統(tǒng),能夠幫助數(shù)據(jù)快速定位到所在的位置,達(dá)到高效更新的效果。
在嘗試規(guī)模化落地的過程中,我們主要遇到了四個(gè)挑戰(zhàn):數(shù)據(jù)難管理、并發(fā)更新弱、更新性能差,以及日志難入湖。
接下來會(huì)一一介紹這些挑戰(zhàn)背后出現(xiàn)的原因以及我們應(yīng)對(duì)的策略。


1. 數(shù)據(jù)難管理
下圖是一個(gè)典型的基于中心化存儲(chǔ)構(gòu)建數(shù)倉機(jī)器學(xué)習(xí)和數(shù)據(jù)科學(xué)的架構(gòu)。這里將加工過后的數(shù)據(jù)保存在數(shù)倉中,通過數(shù)倉的元數(shù)據(jù)進(jìn)行組織。數(shù)據(jù)科學(xué)家和機(jī)器學(xué)習(xí)框架都會(huì)直接去這個(gè)中心化的存儲(chǔ)中獲取原始數(shù)據(jù)。因此在這個(gè)中心化存儲(chǔ)之上的數(shù)據(jù)對(duì)用戶來說是完全分散的,沒有一個(gè)全局的視圖。

為了解決這個(gè)數(shù)據(jù)難管理的問題,Databricks 提出了一個(gè)Lakehouse 的架構(gòu),就是在存儲(chǔ)層之上去構(gòu)建統(tǒng)一的元數(shù)據(jù)緩存和索引層,所有對(duì)數(shù)據(jù)湖之上數(shù)據(jù)的使用都會(huì)經(jīng)過這個(gè)統(tǒng)一的一層。這和我們的目標(biāo)很相似,但是現(xiàn)實(shí)比較殘酷,我們面臨的是海量存量數(shù)據(jù),這些存量數(shù)據(jù)不管是數(shù)據(jù)格式的遷移,還是使用方式的遷移,亦或是元數(shù)據(jù)的遷移,都意味著巨大的投入。因此在很長一段時(shí)間里,我們都會(huì)面臨數(shù)倉和數(shù)據(jù)湖共存這樣一個(gè)階段。在這一階段,兩者的連通性是用戶最為關(guān)心的。
我們?cè)跀?shù)據(jù)湖和數(shù)倉之上,構(gòu)建了一層統(tǒng)一的元數(shù)據(jù)層,這層元數(shù)據(jù)層屏蔽了下層各個(gè)系統(tǒng)的元數(shù)據(jù)的異構(gòu)性,由統(tǒng)一的元數(shù)據(jù)層去對(duì)接 BI 工具,對(duì)接計(jì)算引擎,以及數(shù)據(jù)開發(fā)、治理和權(quán)限管控的一系列數(shù)據(jù)工具。而這一層對(duì)外暴露的 API 是與 Hive 兼容的。
盡管 Hive 這個(gè)引擎已經(jīng)逐漸被其他更新的計(jì)算引擎代替了,比如Spark、Presto、Flink,但是它的源數(shù)據(jù)管理依舊是業(yè)界的事實(shí)標(biāo)準(zhǔn)。另外,一些云廠商即使選擇構(gòu)建了自己的元數(shù)據(jù)服務(wù),也都同時(shí)提供了和 HMS 兼容的元數(shù)據(jù)查詢接口,各個(gè)計(jì)算引擎也都內(nèi)置了Hive Catalog 這一層。

解決了上層的訪問統(tǒng)一的問題,但依舊沒有解決數(shù)據(jù)湖和數(shù)倉元數(shù)據(jù)本身的異構(gòu)問題。這個(gè)異構(gòu)問題是如何導(dǎo)致的呢?為什么Hive Matestore 沒有辦法去滿足元數(shù)據(jù)管理的這個(gè)訴求?
這就涉及到數(shù)據(jù)湖管理元數(shù)據(jù)的特殊性。以Hudi為例,作為一個(gè)典型的事務(wù)型數(shù)據(jù)湖,Hudi使用時(shí)間線 Timeline 來追蹤針對(duì)表的各種操作。比如commit compaction clean,Timeline 類似于數(shù)據(jù)湖里的事務(wù)管理器,記錄對(duì)表的更改情況。這些更改或事務(wù)記錄了每次更新的操作是發(fā)生在哪些文件當(dāng)中,哪些文件為新增,哪些文件失效,哪些數(shù)據(jù)新增,哪些數(shù)據(jù)更新。

總結(jié)下來,數(shù)據(jù)湖是通過追蹤文件來管理元數(shù)據(jù)。管理的力度更細(xì)了,自然也就避免了無效的讀寫放大,從而提供了高效的更新刪除、增量消費(fèi)、時(shí)間旅行等一系列的能力。但這其實(shí)也意味著另外一個(gè)問題,就是一個(gè)目錄中可以包含多個(gè)版本的文件,這與 Hive 管理元數(shù)據(jù)的方式產(chǎn)生了分歧,因?yàn)?Hive Metastore 是通過目錄的形式來管理元數(shù)據(jù)的,數(shù)據(jù)更新也是通過覆蓋目錄來保證事務(wù)。
由于對(duì)元信息的管理力度不同,基于 Hive Metastore的元數(shù)據(jù)管理其實(shí)是沒有辦法實(shí)現(xiàn)數(shù)據(jù)湖剛剛提到的一系列能力。針對(duì)這個(gè)問題,Hudi社區(qū)的解決方案是使用一個(gè)分布式存儲(chǔ)來管理這個(gè) Timeline 。Timeline 里面記錄了每次操作的元數(shù)據(jù),也記錄了一些表的 schema 和分區(qū)的信息,通過同步到 Hive Metastore 來做元數(shù)據(jù)的展示。這個(gè)過程中我們發(fā)現(xiàn)了三個(gè)問題。
第一個(gè)問題是分區(qū)的元數(shù)據(jù)是分散在兩個(gè)系統(tǒng)當(dāng)中的,缺乏 single source of true。
第二個(gè)問題是分區(qū)的元數(shù)據(jù)的獲取需要從 HDFS 拉取多個(gè)文件,沒有辦法給出類似于 HMS 這樣的秒級(jí)訪問響應(yīng)。服務(wù)在線的數(shù)據(jù)應(yīng)用和開發(fā)工具時(shí),這個(gè)延遲沒有辦法滿足需求。
第三個(gè)問題是讀表的時(shí)候需要拉取大量的目錄和 Timeline 上記錄的表操作對(duì)應(yīng)的元數(shù)據(jù)進(jìn)行比對(duì),找出最新的這個(gè)版本包含的文件。元數(shù)據(jù)讀取本身就很重,并且缺乏裁剪能力,這在近實(shí)時(shí)的場景下帶來了比較大的overhead。
Hudi Metastore Server 融合了 Hive Metastore 和 Hudi MetaData管理的優(yōu)勢(shì)。
首先,Hudi Metastore Server 提供了多租戶的、中心化的元數(shù)據(jù)管理服務(wù),將文件一級(jí)的元數(shù)據(jù)保存在適合隨機(jī)讀寫的存儲(chǔ)中,讓數(shù)據(jù)湖的元數(shù)據(jù)不再分散在多個(gè)文件當(dāng)中,滿足了single source of true。
其次,Hudi Metastore Server 針對(duì)元數(shù)據(jù)的查詢,尤其是一些變更操作。比如Job position 提供了與 Hive Metastore完全兼容的接口,用戶在使用一張數(shù)據(jù)湖上的表的時(shí)候,享受到這些增加的高效更新、刪除、增量消費(fèi)等能力的同時(shí),也能享受到一張 Hive 表所具備的功能,例如通過Spark、Flink、Presto查詢,以及在一些數(shù)據(jù)開發(fā)工具上在線的去獲取到元數(shù)據(jù)以及一些分區(qū) TTL清理的能力。
此外,Hudi Metastore Server還解決了一個(gè)關(guān)鍵性的問題,就是多任務(wù)并發(fā)更新弱的問題。
2. 并發(fā)更新弱
我們最早是基于Hudi社區(qū)的0.7版本的內(nèi)核進(jìn)行研發(fā)的,當(dāng)時(shí)Hudi的Timeline中的操作必須是完全順序的,每一個(gè)新的事務(wù)都會(huì)去回滾之前未完成的事務(wù),因此無法支持并發(fā)寫入。后續(xù)社區(qū)也實(shí)現(xiàn)了一個(gè)并發(fā)寫入的方案,整體是基于分布式鎖實(shí)現(xiàn)的,并且只支持了Spark COW表的并發(fā)寫,并不適用于 Flink 或者實(shí)時(shí)的MOR表。但是多任務(wù)的并發(fā)寫入是我們內(nèi)部實(shí)踐當(dāng)中一個(gè)非常通用的訴求。因此我們?cè)贖udi Metastore Server的Timeline之上,使用樂觀鎖去重新實(shí)現(xiàn)了這個(gè)并發(fā)的更新能力。同時(shí)我們這個(gè)并發(fā)控制模塊還能支持更靈活的行列級(jí)別并發(fā)寫策略,為后續(xù)要介紹到的實(shí)時(shí)數(shù)據(jù)關(guān)聯(lián)的場景的落地提供了一個(gè)可能。
除了多任務(wù)的并發(fā)寫入之外,我們?cè)趩蝹€(gè) Flink 任務(wù)的并發(fā)寫入也遇到了瓶頸。由于Hudi設(shè)計(jì)之初嚴(yán)重依賴Spark。0.7.0的版本才剛剛支持Flink。不管是在穩(wěn)定性還是在功能上都和 Spark On Hudi有非常大的差距。因此在進(jìn)行高QPS入湖的情況下,我們就遇到了單個(gè)Flink任務(wù)的擴(kuò)展性問題。
我們通過在Flink的 embedding term server上支持對(duì)當(dāng)前進(jìn)行中的事務(wù)元信息進(jìn)行一下緩存,大幅提升了單個(gè)任務(wù)能夠并發(fā)寫入的文件量級(jí),基本上是在80倍的量級(jí)。結(jié)合分區(qū)級(jí)別的并發(fā)寫入,我們整體支撐了近千萬QPS的數(shù)據(jù)量的增量入湖。
下一步的并發(fā)問題是批流并發(fā)沖突的問題。批流并發(fā)沖突問題類似于一個(gè)我們?cè)趥鹘y(tǒng)數(shù)據(jù)湖中遇到的場景,就是有一連串的小事務(wù)和一個(gè)周期比較長的長事務(wù),如果這兩者發(fā)生沖突,應(yīng)該如何處理。

如果讓短事務(wù)等長事務(wù)完成之后再進(jìn)行,那對(duì)一個(gè)實(shí)時(shí)的鏈路來說,意味著數(shù)據(jù)的可見性變低了。同時(shí)如果在等待過程中失敗了,還會(huì)有非常高的fail over成本。但是如果我們讓這個(gè)長事務(wù)失敗了,成本又會(huì)很高,因?yàn)檫@個(gè)長事務(wù)往往需要耗費(fèi)更多的資源和時(shí)間。而在批流并發(fā)沖突的這個(gè)場景下,最好是兩個(gè)都不失敗,但這從語義上來講又不符合我們認(rèn)知中的隔離級(jí)別。
為了解決批流沖突的問題,我們的思路是提供更靈活的沖突檢查和數(shù)據(jù)合并策略。最基礎(chǔ)的就是行級(jí)并發(fā)。首先兩個(gè)獨(dú)立的writer寫入的數(shù)據(jù)在物理上是隔離的,借助文件系統(tǒng)的租約機(jī)制也能夠保證對(duì)于一個(gè)文件同時(shí)只有一個(gè)writer。所以這個(gè)沖突實(shí)際上不是發(fā)生在數(shù)據(jù)層面的,而是發(fā)生在元數(shù)據(jù)層面。那數(shù)據(jù)的沖突與否,就可以交由用戶來定義。很多時(shí)候入湖的數(shù)據(jù)實(shí)際上并不是一個(gè)現(xiàn)實(shí)中正在發(fā)生的事情,而是一個(gè)現(xiàn)實(shí)操作的回放。比如圖中的這個(gè)場景,我們假設(shè)刪除的作業(yè)是針對(duì)一個(gè)特定的 Snapshot。即使有沖突,我們可以認(rèn)為整個(gè)刪除的過程是瞬時(shí)完成的,后續(xù)的新事物可以追加發(fā)生在這次刪除作業(yè)之后。

第二是列級(jí)并發(fā)。比如接下來在實(shí)踐實(shí)際案例中,我們要介紹的這個(gè)實(shí)時(shí)數(shù)據(jù)關(guān)聯(lián)場景,每個(gè)writer實(shí)際上只是根據(jù)主鍵去更新部分的列。因此這些數(shù)據(jù)其實(shí)在行級(jí)別看起來是沖突的,但是從列的角度來看是完全不沖突的。配合我們的一些確定性索引,數(shù)據(jù)能被寫入到同一個(gè)文件組中,這樣就不會(huì)出現(xiàn)一致性的問題。
最后是沖突合并。假如兩個(gè)數(shù)據(jù)真的是在行級(jí)別和列級(jí)別都發(fā)生了沖突,那真的只能通過 fail 掉一個(gè)事務(wù)才能完成嗎?我覺得是不一定的,這里我們受到了git的啟發(fā)。假如兩次 commit沖突了,我們可以提供merge值的策略,比如數(shù)據(jù)中帶有時(shí)間戳,在合并時(shí)就可以按照時(shí)間戳的先后順序來做合并。
3. 更新性能差
我們最早選擇基于Hudi也是因?yàn)榭蓴U(kuò)展的索引系統(tǒng),通過這個(gè)索引系統(tǒng)可以快速地定位到需要跟新的文件。這帶來了三點(diǎn)好處:
一個(gè)是避免讀取不需要的文件;二是避免更新不必要的文件;三是避免將更新的數(shù)據(jù)和歷史的數(shù)據(jù)做分布式關(guān)聯(lián),而是通過提前將文件分好組的方式直接在文件組內(nèi)進(jìn)行合并。

在早期的落地過程當(dāng)中,我們嘗試盡可能復(fù)用Hudi的一些原生能力,比如Boom Filter index。但是隨著數(shù)據(jù)規(guī)模的不停增長,當(dāng)達(dá)到了千億的量級(jí)之后,upsert的數(shù)據(jù)隨著數(shù)據(jù)量的增長逐漸放緩,到了數(shù)千億的量級(jí)后,消費(fèi)的速度甚至趕不上生產(chǎn)者的速度。即使我們?nèi)樗鼣U(kuò)充了資源,而這時(shí)的數(shù)據(jù)總量其實(shí)也只是在 TB 級(jí)別。我們分析了每個(gè)文件組的大小,發(fā)現(xiàn)其實(shí)文件組的大小也是一個(gè)比較合理的值,基本上是在0.5g到1g之間。進(jìn)一步分析,我們發(fā)現(xiàn)隨著數(shù)據(jù)量的增長,新的導(dǎo)入在通過索引定位數(shù)據(jù)的這一步花費(fèi)的時(shí)間越來越長。
根本原因是Bloom Filter存在假陽性,一旦命中假陽性的case,我們就需要把整個(gè)文件組中的主鍵鏈讀取上來,再進(jìn)一步判斷這個(gè)數(shù)據(jù)是否已經(jīng)存在。通過這種方式來區(qū)分這個(gè)到底是 update 還是 insert。upsert本身就是update和insert兩個(gè)操作的結(jié)合,如果發(fā)現(xiàn)相同組件數(shù)據(jù)不存在,就進(jìn)行insert。如果存在,我們就進(jìn)行 update。而 Bloom Filter由于假陽性的存在,只能加速數(shù)據(jù)的insert而沒有辦法去加速update。這就和我們觀察到的現(xiàn)象很一致。因?yàn)檫@個(gè) pipeline 在運(yùn)行初期,大部分?jǐn)?shù)據(jù)都是第一次入湖,是insert操作,因此可以被索引加速。但是規(guī)模達(dá)到一定量級(jí)之后,大部分?jǐn)?shù)據(jù)都是更新操作,沒有辦法再被索引加速。為了解決這個(gè)問題,我們急需一個(gè)更穩(wěn)定更高效的索引。
Bloom Filter索引的問題,根因是讀取歷史數(shù)據(jù)進(jìn)行定位,導(dǎo)致定位的時(shí)間越來越長。那有沒有什么辦法是無需讀歷史數(shù)據(jù),也可以快速定位到數(shù)據(jù)所在位置呢?我們想到了類似于 Hive的bucket,也就是哈希的方法來解決這個(gè)問題。

Bucket Index原理比較簡單,整個(gè)表或者分區(qū)相當(dāng)于是一張哈希表,文件名中記錄的這個(gè)哈希值,就相當(dāng)于哈希表中這個(gè)數(shù)組的值。可以根據(jù)這個(gè)數(shù)據(jù)中的主鍵哈希值快速定位到文件組。一個(gè)文件組就類似于哈希表中的一個(gè)鏈表,可以將數(shù)據(jù)追加到這個(gè)文件組當(dāng)中。Bucket Index成功地解決了流式更新性能的問題。由于極低的定位數(shù)據(jù)的成本,只要設(shè)置了一個(gè)合適的bucket桶大小,就能解決導(dǎo)入性能的問題,將流式更新能覆蓋的場景從 TB 級(jí)別擴(kuò)展到了百 TB 級(jí)別。除了導(dǎo)入的性能,Bucket Index 還加速了數(shù)據(jù)的查詢,其中比較有代表性的就是 bucket Pruning和bucket join。
當(dāng)然這種索引方式也遇到了擴(kuò)展性的問題,用戶需要提前一步做桶數(shù)的容量規(guī)劃,給一個(gè)比較安全的值,避免單個(gè)桶擴(kuò)大,以便應(yīng)對(duì)接下來的數(shù)據(jù)增長。在數(shù)據(jù)傾斜的場景下,為了讓傾斜值盡可能分散在不同的bucket,會(huì)將bucket的數(shù)量調(diào)到很大。而每個(gè)bucket平均大小很小,會(huì)帶來大量的小文件,給文件系統(tǒng)帶來沖擊的同時(shí)也會(huì)帶來查詢側(cè)性能下滑和寫入側(cè)的資源浪費(fèi)。同時(shí)在一線快速增長的業(yè)務(wù),很難對(duì)容量有一個(gè)精準(zhǔn)的預(yù)估。如果估算少了,數(shù)據(jù)量飛速增長,單個(gè)的bucket的平均大小就會(huì)很大,這就會(huì)導(dǎo)致寫入和查詢的并發(fā)度不足,影響性能。如果估算多了,就會(huì)和傾斜的場景一樣出現(xiàn)大量的小文件。整體的rehash又是一個(gè)很重的運(yùn)維操作,會(huì)直接影響業(yè)務(wù)側(cè)對(duì)數(shù)據(jù)的生產(chǎn)和使用。因此不管從業(yè)務(wù)的易用性出發(fā),還是考慮到資源的使用率和查詢的效率,我們認(rèn)為兼具高效導(dǎo)入和查詢性能,也能支持彈性擴(kuò)展的索引系統(tǒng)是一個(gè)重要的方向。
這時(shí)我們想到了可擴(kuò)展hash這個(gè)數(shù)據(jù)結(jié)構(gòu)。利用這個(gè)結(jié)構(gòu),我們可以很自然地做桶的分裂和合并,讓整個(gè)bucket的索引從手動(dòng)駕駛進(jìn)化到自動(dòng)駕駛。在數(shù)據(jù)寫入的時(shí)候,也可以快速地根據(jù)現(xiàn)有的總數(shù),推斷出最深的有效哈希值的長度,通過不斷地對(duì) 2 的桶深度次方進(jìn)行取余的方式,匹配到最接近的分桶寫入。我們將Bucket Index這個(gè)索引貢獻(xiàn)到了社區(qū),已在Hudi的0.11版本對(duì)外發(fā)布。
4. 日志難入湖
本質(zhì)原因也是因?yàn)镠udi的索引系統(tǒng)。因?yàn)檫@個(gè)索引系統(tǒng)要求數(shù)據(jù)按照組件聚集,一個(gè)最簡單的方式就是把這個(gè)組件設(shè)成UUID,但這樣就會(huì)帶來性能上的問題以及資源上的浪費(fèi)。因此我們?cè)贖udi之內(nèi)實(shí)現(xiàn)了一套新的機(jī)制,我們認(rèn)為是無索引,即繞過Hudi的索引機(jī)制,做到數(shù)據(jù)的實(shí)時(shí)入湖。同時(shí)因?yàn)闆]有主鍵,Upsert 的能力也失效了。我們提供了用更通用的 update 能力,通過shuffle hash join和 broadcast join 去完成數(shù)據(jù)實(shí)時(shí)更新。


03結(jié)合場景介紹實(shí)時(shí)數(shù)據(jù)湖在字節(jié)內(nèi)部的一些實(shí)踐案例
接下來詳細(xì)介紹實(shí)時(shí)數(shù)據(jù)湖在字節(jié)的實(shí)踐場景。電商是字節(jié)發(fā)展非常快速的業(yè)務(wù)之一,數(shù)據(jù)增長非常快,這也對(duì)數(shù)倉的建設(shè)提出了較高的要求。目前電商業(yè)務(wù)數(shù)據(jù)還是典型的lambda架構(gòu),分為是離線數(shù)倉和實(shí)時(shí)數(shù)倉建設(shè)。在實(shí)際場景中,lambda架構(gòu)的問題相信大家都已經(jīng)比較了解了,我就不多做贅述了。這次的場景介紹是圍繞一個(gè)主題,通過數(shù)據(jù)湖來構(gòu)建實(shí)時(shí)數(shù)倉,使實(shí)時(shí)數(shù)據(jù)湖切入到實(shí)時(shí)數(shù)倉的建設(shè)當(dāng)中。這不是一蹴而就的,是分階段一步一步滲透到實(shí)時(shí)數(shù)倉的建設(shè)當(dāng)中,而實(shí)時(shí)數(shù)據(jù)湖的終極目標(biāo)也是在存儲(chǔ)側(cè)形成一個(gè)真正意義上的批流一體的架構(gòu)。

我們切入的第一個(gè)階段是實(shí)時(shí)數(shù)據(jù)的近實(shí)時(shí)可見可測。
坦白說,在實(shí)時(shí)數(shù)據(jù)湖的落地初期,對(duì)于數(shù)據(jù)湖是否能在實(shí)時(shí)數(shù)倉中真正勝任,大家都是存疑的。因此最早的切入點(diǎn)也比較保守,用在數(shù)據(jù)的驗(yàn)證環(huán)節(jié)。在電商的實(shí)時(shí)數(shù)倉中,由于業(yè)務(wù)發(fā)展快,上游系統(tǒng)變更,以及數(shù)據(jù)產(chǎn)品需求都非常多。導(dǎo)致實(shí)時(shí)數(shù)倉開發(fā)周期短,上線變更頻繁。當(dāng)前這個(gè)實(shí)時(shí)的數(shù)據(jù)的新增字段和指標(biāo)邏輯變更,或者在任務(wù)重構(gòu)優(yōu)化時(shí),都要對(duì)新版本的作業(yè)生成的指標(biāo)進(jìn)行驗(yàn)證。驗(yàn)證的目標(biāo)主要有兩點(diǎn),一是原有指標(biāo),數(shù)據(jù)是否一致,二是新增指標(biāo)的數(shù)據(jù)是否合理。
在采用數(shù)據(jù)湖的方案之前,數(shù)據(jù)湖的驗(yàn)證環(huán)節(jié)需要將結(jié)果導(dǎo)入到Kafka然后再dump到 Hive,進(jìn)行全量數(shù)據(jù)校驗(yàn)。這里存在的一個(gè)問題就是數(shù)據(jù)無法實(shí)時(shí)或者近實(shí)時(shí)可見可檢的,基本上都是一個(gè)小時(shí)級(jí)的延遲。在很多緊急上線的場景下,因?yàn)檠訒r(shí)的問題,只能去抽測數(shù)據(jù)進(jìn)行測試驗(yàn)證,就會(huì)影響數(shù)據(jù)質(zhì)量。實(shí)時(shí)數(shù)據(jù)湖的方案,是通過將實(shí)時(shí)數(shù)據(jù)低成本的增量導(dǎo)入到數(shù)據(jù)湖中,然后通過Presto進(jìn)行查詢,然后進(jìn)行實(shí)時(shí)計(jì)算匯總,計(jì)算的結(jié)果做到近實(shí)時(shí)的全面的可見可測。

當(dāng)然在這個(gè)階段中,我們也暴露出了很多數(shù)據(jù)湖上易用性的問題。業(yè)務(wù)側(cè)的同學(xué)反饋?zhàn)疃嗟膯栴}就是數(shù)據(jù)湖的配置過于復(fù)雜。比如要寫一個(gè)數(shù)據(jù)湖的任務(wù),Hudi自身就存在十多個(gè)參數(shù)需要在寫入任務(wù)中配置。這增加了業(yè)務(wù)側(cè)同學(xué)的學(xué)習(xí)成本和引擎?zhèn)韧瑢W(xué)的解釋成本。同時(shí)還需要在Flink SQL里定義一個(gè)sync table 的DDL,寫一個(gè)完整的 schema,很容易會(huì)因?yàn)轫摰捻樞蚧蛘咂磳戝e(cuò)誤導(dǎo)致任務(wù)失敗。
我們借助了Hudi Metastore Server 的能力,封裝了大量的參數(shù)。同時(shí)使用Flink Catalog的能力,對(duì)Meta Server進(jìn)一步封裝,讓用戶在配置一個(gè) Fink SQL任務(wù)的時(shí)候,從最初的寫DDL配置十多個(gè)參數(shù),到現(xiàn)在只要寫一條 create table like的語句,配置一張臨時(shí)表,用戶對(duì)這種方式的接受度普遍是比較高的。
第二個(gè)階段,也就是第二個(gè)應(yīng)用場景是數(shù)據(jù)的實(shí)時(shí)入湖和實(shí)時(shí)分析。
數(shù)據(jù)湖可以同時(shí)滿足高效的實(shí)時(shí)數(shù)據(jù)增量導(dǎo)入和交互式分析的需求,讓數(shù)據(jù)分析師可以自助搭建看板,同時(shí)也可以進(jìn)行低成本的數(shù)據(jù)回刷,真正做到一份數(shù)據(jù)批流兩種使用方式。在這個(gè)階段,由于數(shù)據(jù)實(shí)際上已經(jīng)開始生產(chǎn)了,用戶對(duì)于數(shù)據(jù)入湖的穩(wěn)定性和查詢性能都有很高的要求。我們通過將Compaction任務(wù)與實(shí)時(shí)導(dǎo)入任務(wù)拆分,首先解決了資源搶占導(dǎo)致的入湖時(shí)效性比較低的問題,同時(shí)設(shè)計(jì)了compaction service,負(fù)責(zé)compaction任務(wù)的調(diào)度,整個(gè)過程對(duì)業(yè)務(wù)側(cè)同學(xué)完全屏蔽。我們?cè)诜?wù)層面也對(duì)報(bào)警和監(jiān)控進(jìn)行了加強(qiáng),能夠做到先于業(yè)務(wù)去發(fā)現(xiàn)問題,處理問題,進(jìn)一步提升了任務(wù)的穩(wěn)定性,也讓我們的使用方能夠更有信心地去使用實(shí)時(shí)數(shù)據(jù)湖。

在查詢的優(yōu)化上面,我們優(yōu)化了讀文件系統(tǒng)的長尾問題,支持了實(shí)時(shí)表的列裁剪。同時(shí)我們對(duì)Avro日志進(jìn)行了短序列化和序列化的case by case的優(yōu)化,還引入了列存的 log進(jìn)一步提升查詢性能。除了實(shí)時(shí)數(shù)據(jù)分析之外,這種能力還可以用于機(jī)器學(xué)習(xí)。在特征過程當(dāng)中,有些label是可以快速地從日志中實(shí)時(shí)獲取到的。比如對(duì)一個(gè)視頻點(diǎn)了個(gè)贊,和特征是可以關(guān)聯(lián)上的。
有些label的生成則是長周期的,比如在抖音上買了一個(gè)東西,或者把一個(gè)東西加入購物車,到最后的購買,這整個(gè)鏈路是很長的,可能涉及到天級(jí)別或者周級(jí)別的一個(gè)不定周期。但是在這兩種情況下,它的特征數(shù)據(jù)基本上都是相同的,這也使底層的存儲(chǔ)有了批流兩種使用方式的訴求,以往都是通過冗余的存儲(chǔ)和計(jì)算來解決的。通過數(shù)據(jù)湖可以將短周期的特征和標(biāo)簽實(shí)時(shí)地入湖,長周期的每天做一次調(diào)度,做一個(gè)批式入湖,真正能做到一份數(shù)據(jù)去適用多個(gè)模型。
第三個(gè)階段的應(yīng)用場景是數(shù)據(jù)的實(shí)時(shí)多維匯總。
在這個(gè)階短最重要的目標(biāo)是實(shí)時(shí)數(shù)據(jù)的普惠。因?yàn)楹芏嗟膶?shí)時(shí)數(shù)據(jù)使用方都是通過可視化查詢或者是數(shù)據(jù)服務(wù)去消費(fèi)一個(gè)特定的匯總數(shù)據(jù),而這些重度匯總過后的實(shí)時(shí)數(shù)據(jù)使用率相對(duì)來說是比較低的。因此我們和數(shù)倉的同學(xué)共同推進(jìn)了一個(gè)實(shí)時(shí)多維匯總的方案落地。數(shù)倉的同學(xué)通過實(shí)時(shí)計(jì)算引擎完成數(shù)據(jù)的多維度的輕度匯總,并且實(shí)時(shí)地更新入湖。下游可以靈活地按需獲取重度匯總的數(shù)據(jù),這種方式可以縮短數(shù)據(jù)鏈路,提升研發(fā)效能。

在實(shí)際的業(yè)務(wù)場景中,對(duì)于不同的業(yè)務(wù)訴求,又可以細(xì)分成三個(gè)不同的子場景。
第一個(gè)場景是內(nèi)部用戶的可視化查詢和報(bào)表這一類場景。它的特點(diǎn)是查詢頻率不高,但是維度和指標(biāo)的組合靈活,同時(shí)用戶也能容忍數(shù)秒的延遲。在這種場景下,上層的數(shù)據(jù)應(yīng)用直接調(diào)用底層的 Presto 引擎行為實(shí)時(shí)入庫的數(shù)據(jù)進(jìn)行多維度的重度聚合之后,再做展現(xiàn)。
另外一個(gè)主要的場景就是面向在線的數(shù)據(jù)產(chǎn)品。這種場景對(duì)高查詢頻率、低查詢延遲的訴求比較高,但是對(duì)數(shù)據(jù)可見性的要求反而不那么高。而且,經(jīng)過重度匯總的數(shù)據(jù)量也比較小,這就對(duì)數(shù)據(jù)分析工具提出了比較大的挑戰(zhàn)。因此在當(dāng)前階段,我們通過增加了一個(gè)預(yù)計(jì)算鏈路來解決。
下面一個(gè)問題,多維重度匯總的多維計(jì)算結(jié)果是從我們湖里批量讀出來,然后定時(shí)地去寫入 KV存儲(chǔ),由存儲(chǔ)去直接對(duì)接數(shù)據(jù)產(chǎn)品。從長期來看,我們下一步計(jì)劃就是對(duì)實(shí)時(shí)數(shù)據(jù)湖之上的表去進(jìn)行自動(dòng)地構(gòu)建物化視圖,并且加載進(jìn)緩存,以此來兼顧靈活性和查詢性能,讓用戶在享受這種低運(yùn)維成本的同時(shí),又能滿足低延低查詢延遲、高查詢頻率和靈活使用的訴求。
第四個(gè)典型的場景是實(shí)時(shí)數(shù)據(jù)關(guān)聯(lián)。數(shù)據(jù)的關(guān)聯(lián)在數(shù)倉中是一個(gè)非常基礎(chǔ)的訴求,數(shù)倉的同學(xué)需要將多個(gè)流的指標(biāo)和維度列進(jìn)行關(guān)聯(lián),形成一張寬表。但是使用維表join,尤其是通過緩存加速的方式,數(shù)據(jù)準(zhǔn)確性往往很難保障。而使用多流join 的方式又需要維持一個(gè)大狀態(tài),尤其是對(duì)于一些關(guān)聯(lián)周期不太確定的場景,穩(wěn)定性和準(zhǔn)確性之間往往很難取舍。

基于以上背景,我們的實(shí)時(shí)數(shù)據(jù)湖方案通過了這個(gè)列級(jí)的并發(fā)寫入和確定性的索引。我們支持多個(gè)流式任務(wù)并發(fā)地去寫入同一張表中,每個(gè)任務(wù)只寫表中的部分列。數(shù)據(jù)寫入的 log 件在物理上其實(shí)是隔離的,每個(gè)log文件當(dāng)中也只包含了寬表中的部分列,實(shí)際上不會(huì)產(chǎn)生互相影響。再異步地通過compaction任務(wù)定期的對(duì)之前對(duì)log數(shù)據(jù)進(jìn)行合并,在這個(gè)階段對(duì)數(shù)據(jù)進(jìn)行真正的實(shí)際的關(guān)聯(lián)操作。通過這種方式,提供一個(gè)比較穩(wěn)定的性能。使用這一套方案,實(shí)時(shí)關(guān)聯(lián)用戶也不用再關(guān)注狀態(tài)大小和TTL該如何設(shè)置這個(gè)問題了,寬表的數(shù)據(jù)也可以做到實(shí)時(shí)可查。
最后一個(gè)階段是實(shí)時(shí)數(shù)據(jù)湖的終極階段,目前仍在探索中。我們只在部分場景開啟了驗(yàn)證。在這個(gè)架構(gòu)里面,數(shù)據(jù)可以從外部的不同數(shù)據(jù)源中實(shí)時(shí)或者批量的入湖和出湖,而流批作業(yè)完成湖內(nèi)的數(shù)據(jù)實(shí)時(shí)流轉(zhuǎn),形成真正意義上的存儲(chǔ)層批流一體。

同時(shí)在這套架構(gòu)中,為了解決實(shí)時(shí)數(shù)據(jù)湖從分鐘級(jí)到秒級(jí)的最后一公里,我們?cè)趯?shí)時(shí)引擎與數(shù)據(jù)湖的表之間增加了一層數(shù)據(jù)加速服務(wù)。在這層數(shù)據(jù)加速服務(wù)之上,多個(gè)實(shí)時(shí)作業(yè)可以做到秒級(jí)的數(shù)據(jù)流轉(zhuǎn),而這個(gè)服務(wù)也會(huì)解決頻繁流式寫入頻繁提交導(dǎo)致的小文件問題,為實(shí)時(shí)數(shù)據(jù)的交互查詢進(jìn)一步提速。
除此之外,由于流批作業(yè)的特性不同,批計(jì)算往往會(huì)需要更高的瞬時(shí)吞吐。因此這些批計(jì)算任務(wù)也可以直接讀寫底層的池化文件系統(tǒng),做到極強(qiáng)的擴(kuò)展性,真正意義上做到批流寫入的隔離,批作業(yè)的寫入不會(huì)受限于加速服務(wù)的帶寬。在這個(gè)批流一體的架構(gòu)中,數(shù)據(jù)湖之上的用戶,不管是SQL查詢,還是BI 、AI ,都可以通過一個(gè)統(tǒng)一的 table format 享受到數(shù)據(jù)湖之上數(shù)據(jù)的開放性。
04數(shù)據(jù)湖發(fā)展的一些規(guī)劃
未來規(guī)劃主要聚焦于三個(gè)維度:功能層面的規(guī)劃,開源層面的規(guī)劃,以及商業(yè)化輸出相關(guān)的一些規(guī)劃。
1. 功能層面
首先是功能維度,我們認(rèn)為一個(gè)更智能的實(shí)時(shí)數(shù)據(jù)湖的加速系統(tǒng)是我們最重要的目標(biāo)之一。

- 元數(shù)據(jù)層面的加速
數(shù)據(jù)湖托管了文件級(jí)別的元數(shù)據(jù),元數(shù)據(jù)的數(shù)據(jù)量相比數(shù)倉有了幾個(gè)量級(jí)的增長,但同時(shí)也給我們帶來了一些優(yōu)化的機(jī)會(huì)。比如我們未來計(jì)劃將查詢的謂詞直接下推到元數(shù)據(jù)系統(tǒng)當(dāng)中,讓這個(gè)引擎在scan階段無需訪問系統(tǒng),直接去跳過無效文件來提升查詢的性能。
- 數(shù)據(jù)的加速
當(dāng)前的實(shí)時(shí)數(shù)據(jù)湖由于其 serverless 架構(gòu)對(duì)文件系統(tǒng)的重度依賴,在生產(chǎn)實(shí)踐中還是處于分鐘級(jí),秒級(jí)依舊處于驗(yàn)證階段。那我們接下來計(jì)劃將這個(gè)數(shù)據(jù)湖加速服務(wù)不斷地去打磨成熟,用來做實(shí)時(shí)數(shù)據(jù)的交換和熱數(shù)據(jù)的存儲(chǔ),以解決分鐘級(jí)到秒級(jí)的最后一公里問題。智能加速層面臨的最大的挑戰(zhàn)是批流數(shù)據(jù)寫入的一致性問題,這也是我們接下來重點(diǎn)要解決的問題。例如在這種端到端的實(shí)時(shí)生產(chǎn)鏈路中,如何在提供秒級(jí)延時(shí)的前提下解決類似于跨表事務(wù)的問題。
- 索引加速
通過bucket, zorder等一系列的主鍵索引,進(jìn)一步提升數(shù)據(jù)湖之上的數(shù)據(jù)的查詢性能,過濾掉大量的原始數(shù)據(jù),避免無效的數(shù)據(jù)交換。同時(shí)我們接下來也會(huì)非常注重二級(jí)索引的支持,因?yàn)槎?jí)索引的支持可以延伸湖上數(shù)據(jù)的更新能力,從而去加速非主線更新的效率。
- 智能優(yōu)化
我們接下來會(huì)通過一套表優(yōu)化服務(wù)來實(shí)現(xiàn)智能優(yōu)化,因?yàn)閷?duì)于兩個(gè)類似的查詢能否去提供一個(gè)穩(wěn)定的查詢性能,表的數(shù)據(jù)分布是一個(gè)關(guān)鍵因素。從用戶的角度來看,用戶只要查詢快、寫入快,像類似于compaction或clustering、索引構(gòu)建等一系列的表優(yōu)化的方式,只會(huì)提升用戶的使用門檻。我們的計(jì)劃是通過一個(gè)智能的表優(yōu)化服務(wù)分析用戶的查詢特征,同時(shí)監(jiān)聽這個(gè)數(shù)據(jù)湖上數(shù)據(jù)的變化,自適應(yīng)地觸發(fā)這個(gè)表的一系列優(yōu)化操作,可以做到在用戶不需要了解過多細(xì)節(jié)的情況下,做到智能的互加速。
2. 開源層面
第二個(gè)維度是開源貢獻(xiàn)。我們現(xiàn)在一直在積極地投入到Hudi的社區(qū)貢獻(xiàn)當(dāng)中,參與了多個(gè)Hudi的核心feature的開發(fā)和設(shè)計(jì)。其中Bucket index是我們合入到社區(qū)的第一個(gè)核心功能,而當(dāng)下我們也在同時(shí)貢獻(xiàn)著多個(gè)重要的功能,比如最早提到的解決數(shù)據(jù)難管理的Hudi MetaStore Server,我們已經(jīng)貢獻(xiàn)到社區(qū)了,去普惠到開源社區(qū)。因?yàn)槲覀儼l(fā)現(xiàn)Hudi MetaStore Server不止解決我們?cè)谏a(chǎn)實(shí)踐中遇到的問題,也是業(yè)界普遍遇到的一個(gè)問題。現(xiàn)在也在跟Hudi社區(qū)的PMC共同探討數(shù)據(jù)湖的元數(shù)據(jù)管理系統(tǒng)制定標(biāo)準(zhǔn)。

其它一些功能我們也計(jì)劃分兩個(gè)階段貢獻(xiàn)到社區(qū)。比如 RPC 42,將我們的湖表管理服務(wù)與大家共享,長期來看能夠做到數(shù)據(jù)湖上的表的自動(dòng)優(yōu)化。還有 Trino 和Presto DB 的 Hudi Connector,目前也是在和Hudi背后的生態(tài)公司共同推進(jìn)投入到開源社區(qū)當(dāng)中。
3. 商業(yè)化輸出
當(dāng)前在火山引擎之上,我們將內(nèi)部的數(shù)據(jù)湖技術(shù)實(shí)踐同時(shí)通過LAS和EMR這兩個(gè)產(chǎn)品向外部企業(yè)輸出。其中LAS湖倉一體分析服務(wù)是一個(gè)整體面向湖倉一體架構(gòu)的Serverless數(shù)據(jù)處理分析服務(wù),提供一站式的海量數(shù)據(jù)存儲(chǔ)計(jì)算和交互分析能力,完全兼容 Spark、Presto和Flink生態(tài)。同時(shí)這個(gè)產(chǎn)品具備了完整的字節(jié)內(nèi)部的實(shí)時(shí)數(shù)據(jù)湖的成熟能力,能夠幫助企業(yè)輕松完成湖倉的構(gòu)建和數(shù)據(jù)價(jià)值的洞察。
另外一個(gè)產(chǎn)品 EMR 是一個(gè)Stateless的云原生數(shù)倉,100%開源兼容,在這個(gè)產(chǎn)品當(dāng)中也會(huì)包含字節(jié)數(shù)據(jù)湖實(shí)踐中一些開源兼容的優(yōu)化,以及一些引擎的企業(yè)級(jí)增強(qiáng),以及云上便捷的運(yùn)維能力。
最后,歡迎大家關(guān)注字節(jié)跳動(dòng)數(shù)據(jù)平臺(tái)公眾號(hào),在這里有非常多的技術(shù)干貨、產(chǎn)品動(dòng)態(tài)和招聘信息。
05 問答環(huán)節(jié)
Q:可擴(kuò)展性的 Bucket Index 具體是怎么做的?
A:可控?cái)U(kuò)展性的Bucket Index其實(shí)是把哈希值的 String 用一個(gè)字典樹的思路去解決。我們把它當(dāng)成一個(gè)一個(gè)的 bit ,比如說當(dāng)我們把兩個(gè) bucket 合并了之后,我們就可以少用一個(gè) bit,如果我們把一個(gè) bucket 分裂之后,就會(huì)增加一個(gè) bit。
然后這里面其實(shí)主要是兩點(diǎn),一個(gè)是查詢層我們?cè)趺慈プR(shí)別它到底屬于哪個(gè) bucket。這個(gè)我們是可以通過一個(gè)當(dāng)前的桶數(shù)算出一個(gè)最大的這個(gè)哈希深度。然后我們?nèi)?duì)哈希值和這個(gè)桶的深度的N次方去進(jìn)行取余。如果取余能匹配上,就說明這個(gè)桶是存在的。如果匹配不上,我們就把這個(gè)深度減1,然后再進(jìn)行取余,直到能匹配上為止,這個(gè)是在寫入的時(shí)候。
第二個(gè)就是在查詢層面,我們會(huì)找一個(gè)合理的并行度,比如說我們這個(gè)桶的深度可能是6,但是這個(gè)6的文件占的數(shù)量特別少,那我們可能就再把它減少一位。然后從整個(gè)查詢的這個(gè)角度來看,我們減少一位的話,這個(gè)數(shù)據(jù)分布其實(shí)應(yīng)該是更為合理的。我們把文件先分好組,讓每個(gè) task 去拿到對(duì)應(yīng)的一個(gè)特定的哈希值上的一個(gè)文件。
還有一個(gè)就是當(dāng)數(shù)據(jù)真正發(fā)生這merge 和 split 的時(shí)候,這個(gè)階段我們是如何處理的?這個(gè)階段其實(shí)這樣的,當(dāng)一個(gè)文件發(fā)生分裂的時(shí)候,它原始的數(shù)據(jù)是不用動(dòng)的。我們可以認(rèn)為它就是一個(gè)引用,因?yàn)槲覀兤ヅ涞搅诵碌膄ile group。我們可以找到之前它引用的原生沒有擴(kuò)容的這個(gè)bucket,然后我們依舊還是可以去把這個(gè)數(shù)據(jù)拿到,并且在這個(gè)沒有擴(kuò)容的file group上,我們可以套一層 hash filter ,然后可以保證這個(gè)數(shù)據(jù)不會(huì)有重復(fù)。最后我們異步地去做一個(gè) clustering這個(gè)時(shí)候真正地去對(duì)數(shù)據(jù)物理上面去完成一個(gè)歷史數(shù)據(jù)的重分布。
Q:這邊對(duì)數(shù)據(jù)湖的應(yīng)用主要是實(shí)時(shí)數(shù)倉嗎?
A:實(shí)時(shí)數(shù)倉是我們非常重要的一個(gè)落地場景。這次為什么著重介紹實(shí)時(shí)數(shù)倉,也是這次的這個(gè)整體的 topic 是字節(jié)跳動(dòng)實(shí)時(shí)數(shù)據(jù)湖的引用。這個(gè)數(shù)據(jù)湖在我們內(nèi)部其實(shí)也會(huì)用于離線數(shù)倉,可能也會(huì)用于推薦系統(tǒng),很多場景都會(huì)有相應(yīng)的一個(gè)應(yīng)用。
Q:感覺schema on read的這種特性的實(shí)踐和預(yù)期并不一致。
A:其實(shí)是這樣的,schema on read目前的實(shí)踐整體來說是比較少的,但是其實(shí)我們是有一些預(yù)期的。我可以大概講一下我的理解,首先我們?cè)跀?shù)據(jù)入湖的時(shí)候,對(duì)數(shù)據(jù)的期望還是它要是結(jié)構(gòu)化的。但是我們schema on read的核心可能不是說去支持這種類似于非結(jié)構(gòu)化或者說是沒有辦法去結(jié)構(gòu)化的數(shù)據(jù),我們的核心可能是要去支持?jǐn)?shù)據(jù)的一個(gè)靈活的演變能力。那這里面其實(shí)有幾種思路。
第一種思路的話就是我們?cè)诒淼膕chema層,去做一個(gè)靈活演變的支持。第二個(gè)思路也非常的類似于 git 的思路,就是我們的這個(gè)用戶其實(shí)對(duì)同一份數(shù)據(jù)它有不同視圖的需求。我們可以把這個(gè)數(shù)據(jù)以git 的思路去把它做成分支。每個(gè)人在同一份數(shù)據(jù)上面,有一個(gè)自己的數(shù)據(jù)的視圖,這個(gè)我認(rèn)為可能也是 schema on read 的下一個(gè)重要的發(fā)展方向,我們可能有一張表,這張表每個(gè)人他看到的這個(gè)視圖可能是不一樣的。然后每個(gè)人可以往自己的視圖里頭去加上一些自己想要的數(shù)據(jù)。這個(gè)在實(shí)際的業(yè)務(wù)場景中其實(shí)也是存在的。比如說一個(gè)實(shí)時(shí)數(shù)據(jù),它進(jìn)來的時(shí)候,它可能這個(gè)指標(biāo)不是很全的,但是我們有些指標(biāo)可能是需要在這個(gè)離線加工完之后再回灌進(jìn)去。那這樣的話,其實(shí)這一張表對(duì)用戶呈現(xiàn)的就是兩個(gè)視圖。那我們接下來可能要做的就是如何去解決這個(gè)不同視圖之間的這個(gè)隔離的問題。不管是存儲(chǔ)上面的這個(gè)隔離,還是權(quán)限上面的隔離,還是元數(shù)據(jù)上面的隔離。
Q:數(shù)據(jù)湖里面是否還需要考慮類似數(shù)倉的分層架構(gòu),如果需要的話是如何實(shí)現(xiàn)的?
A:這主要取決于上層用戶如何使用數(shù)據(jù)湖,目前來看實(shí)際依舊還是有分層架構(gòu)的,但是從底層來看,不管用戶是否分層,數(shù)據(jù)湖提供的能力是一樣的。
今天的分享就到這里,謝謝大家。?





































