1. 程式人生 > 其它 >實時資料湖在位元組跳動的實踐

實時資料湖在位元組跳動的實踐

對實時資料湖的解讀

資料湖的概念是比較寬泛的,不同的人可能有著不同的解讀。這個名詞誕生以來,在不同的階段被賦予了不同的含義。

資料湖的概念最早是在 Hadoop World 大會上提出的。當時的提出者給資料湖賦予了一個非常抽象的含義,他認為它能解決資料集市面臨的一些重要問題。

其中最主要的兩個問題是:首先,資料集市只保留了部分屬性,只能解決預先定義好的問題;另外,資料集市中反映細節的原始資料丟失了,限制了通過資料解決問題。從解決問題的角度出發,希望有一個合適的儲存來儲存這些明細的、未加工的資料。因此在這個階段,人們對資料湖的解讀更多的是聚焦在中心化的儲存之上。

不同的雲廠商也把自己的物件產儲存產品稱為資料湖。比如 AWS 在那個階段就強調資料湖的儲存屬性,對應的就是自家的物件儲存 S3。在 Wiki 的定義中也是強調資料湖是一箇中心化儲存,可以存海量的不同種類的資料。但是當物件儲存滿足了大家對儲存海量資料的訴求之後,人們對資料湖的解讀又發生了變化。

第二階段,對資料湖的解讀更多的是從開源社群和背後的商業公司發起的。比如 Databricks 作為一個雲中立的產品,它將雲廠商的這個物件儲存稱為 data lakes storage,然後把自己的重心聚焦在如何基於一箇中心化的儲存構建一個數據分析、資料科學和機器學習的資料湖解決方案,並且把這個方案稱之為 lake。他們認為在這個中心化的儲存之上構建事務層、索引層,元資料層,可以去解決資料湖上的可靠性、效能和安全的問題。

與此同時,Uber 最初也將 Hudi 對外稱為一個事務型的資料湖,名字實際上也是由 Hadoop Updates and Incrementals 縮寫而來,最早也是被用於解決 Uber 內部離線資料的合規問題。現在他們更傾向的定義是一個流式資料湖平臺,Iceberg 也常常被人們納入資料湖的討論。儘管 Ryan Blue 一直宣稱 Iceberg 是一個 Open Table Format。這三者有一些共同點,一個是對 ACID 的支援,引入了一個事務層,第二是對 streaming 和 batch 的同等支援,第三就是聚焦在如何能更快的查詢資料。國內也有人將 Hudi、Iceberg、Delta Lake 稱為資料湖的三劍客。

講完了業界的解讀,來看一下位元組跳動對資料湖的解讀。我們是結合位元組的業務場景來解讀的。通過實踐總結,我們發現數據湖需要具備六大能力:

第一是高效的併發更新能力。 因為它能夠改變我們在 Hive 數倉中遇到的資料更新成本高的問題,支援對海量的離線資料做更新刪除。

第二是智慧的查詢加速。 使用者使用資料湖的時候,不希望感知到資料湖的底層實現細節,資料湖的解決方案應該能夠自動地優化資料分佈,提供穩定的產品效能。

第三是批流一體的儲存。 資料湖這個技術出現以來,被數倉行業給予了厚望,他們認為資料湖可以最終去解決一份儲存流批兩種使用方式的問題,從而從根本上提升開發效率和資料質量。

第四是統一的元資料和許可權。

在一個企業級的資料湖當中,元資料和許可權肯定是不能少的。同時在湖倉共存的情況下,使用者不希望元資料和許可權在湖倉兩種情況下是割裂的。

第五是極致的查詢效能。 使用者對於資料湖的期望就是能夠在資料實時入湖的同時還能做到資料的秒級視覺化。

第六是 AI + BI。 資料湖資料的對外輸出,不只侷限於 BI,同時 AI 也是資料湖的一等公民,資料湖也被應用在了位元組的整個推薦體系,尤其是特徵工程當中。實時資料湖其實是資料湖之上,更加註重資料的實時屬性或者說流屬性的一個數據湖發展方向。當然,正如業界對於資料湖的解讀一直在演變,我們對資料湖的解讀也不會侷限於以上場景和功能。

落地實時資料湖過程中的挑戰和應對方式

接下來介紹資料湖落地的挑戰和應對。位元組內部的資料湖最初是基於開源的資料湖框架 Hudi 構建的,選擇 Hudi,最簡單的一個原因就是因為相比於 Iceberg 和 Delta Lake,Hudi 原生支援可擴充套件的索引系統,能夠幫助資料快速定位到所在的位置,達到高效更新的效果。

在嘗試規模化落地的過程中,我們主要遇到了四個挑戰:資料難管理,併發更新弱,更新效能差,以及日誌難入湖。

接下來會一一介紹這些挑戰背後出現的原因以及我們應對的策略。

  1. 資料難管理

下圖是一個典型的基於中心化儲存構建數倉機器學習和資料科學的架構。這裡將加工過後的資料儲存在數倉中,通過數倉的元資料進行組織。資料科學家和機器學習框架都會直接去這個中心化的儲存中獲取原始資料。因此在這個中心化儲存之上的資料對使用者來說是完全分散的,沒有一個全域性的檢視。

為了解決這個資料難管理的問題,Databricks 提出了一個 Lakehouse 的架構,就是在儲存層之上去構建統一的元資料快取和索引層,所有對資料湖之上資料的使用都會經過這個統一的一層。在這一點上和我們的目標是很相似的,但是現實是比較殘酷的,我們面臨的是海量存量資料,這些存量資料不管是資料格式的遷移,還是使用方式的遷移,亦或是元資料的遷移,都意味著巨大的投入。因此在很長一段時間裡,我們都會面臨數倉和資料湖共存這樣一個階段。在這一階段,兩者的連通性是使用者最為關心的。

我們在資料湖和數倉之上,構建了一層統一的元資料層,這層元資料層遮蔽了下層各個系統的元資料的異構性,由統一的元資料層去對接 BI 工具,對接計算引擎,以及資料開發、治理和許可權管控的一系列資料工具。而這一層對外暴露的 API 是與 Hive 相容的。儘管 Hive 這個引擎已經逐漸被其他的更新的計算引擎代替了,比如 Spark、Presto、Flink,但是它的源資料管理依舊是業界的事實標準。另外一些雲廠商即使選擇構建了自己的元資料服務,也都同時提供了和 HMS 相容的元資料查詢介面,各個計算引擎也都內建了 Hive Catalog 這一層。

解決了上層的訪問統一的問題,但依舊沒有解決資料湖和數倉元資料本身的異構問題。這個異構問題是如何導致的呢?為什麼 Hive Matestore 沒有辦法去滿足元資料管理的這個訴求?

這就涉及到資料湖管理元資料的特殊性。以 Hudi 為例,作為一個典型的事務型資料湖,Hudi 使用時間線 Timeline 來追蹤針對表的各種操作。比如 commit compaction clean, Timeline 類似於資料湖裡的事務管理器,記錄對錶的更改情況。而這些更改或事務記錄了每次更新的操作是發生在哪些檔案當中,哪些檔案為新增,哪些檔案失效,哪些資料新增,哪些資料更新。

總結下來,資料湖是通過追蹤檔案來管理元資料。管理的力度更細了,自然也就避免了無效的讀寫放大,從而提供了高效的更新刪除、增量消費、時間旅行等一系列的能力。但這其實也就意味著另外一個問題,就是一個目錄中可以包含多個版本的檔案,這與 Hive 管理元資料的方式就產生了分歧,因為 Hive Metastore 是通過目錄的形式來管理元資料的,資料更新也是通過覆蓋目錄來保證事務。由於對元資訊的管理力度不同,基於 Hive Metastore 的元資料管理其實是沒有辦法實現資料湖剛剛提到的一系列能力的。針對這個問題,Hudi 社群的解決方案是使用一個分散式儲存來管理這個 Timeline 。Timeline 裡面記錄了每次操作的元資料,也記錄了一些表的 schema 和分割槽的資訊,通過同步到 Hive Metastore 來做元資料的展示。這個過程中我們發現了三個問題。

第一個問題就是分割槽的元資料是分散在兩個系統當中的,缺乏 single source of true。第二個是分割槽的元資料的獲取需要從 HDFS 拉取多個檔案,沒有辦法給出類似於 HMS 這樣的秒級訪問響應。服務線上的資料應用和開發工具時,這個延遲是沒有辦法滿足需求的。第三個是讀表的時候需要拉取大量的目錄和 Timeline 上記錄的表操作對應的元資料進行比對,找出最新的這個版本包含的檔案。元資料讀取本身就很重,並且缺乏裁剪能力,這在近實時的場景下帶來了比較大的 overhead。

Hudi Metastore Server 融合了 Hive Metastore 和 Hudi MetaData 管理的優勢。首先,Hudi Metastore Server 提供了多租戶的、中心化的元資料管理服務,將檔案一級的元資料儲存在適合隨機讀寫的儲存中,讓資料湖的元資料不再分散在多個檔案當中,滿足了 single source of true。其次,Hudi Metastore Server 針對元資料的查詢,尤其是一些變更操作。比如 Job position 提供了與 Hive Metastore 完全相容的介面,使用者在使用一張資料湖上的表的時候,享受到這些增加的高效更新、刪除、增量消費等能力的同時,也能享受到一張 Hive 表所具備的功能,例如通過 Spark、Flink、Presto 查詢,以及在一些資料開發工具上線上的去獲取到元資料以及一些分割槽 TTL 清理的能力。此外,Hudi Metastore Server 還解決了一個關鍵性的問題,就是多工併發更新弱的問題。

  1. 併發更新弱

我們最早是基於 Hudi 社群的 0.7 版本的核心進行研發的,當時 Hudi 的 Timeline 中的操作必須是完全順序的,每一個新的事務都會去回滾之前未完成的事務,因此無法支援併發寫入。後續社群也實現了一個併發寫入的方案,整體是基於分散式鎖實現的,並且只支援了 Spark COW 表的併發寫,並不適用於 Flink 或者實時的 MOR 表。但是多工的併發寫入是我們內部實踐當中一個非常通用的訴求。因此我們在 Hudi Metastore Server 的 Timeline 之上,使用樂觀鎖去重新實現了這個併發的更新能力。同時我們這個併發控制模組還能支援更靈活的行列級別併發寫策略,為後續要介紹到的實時資料關聯的場景的落地提供了一個可能。

除了多工的併發寫入之外,我們在單個 Flink 任務的併發寫入也遇到了瓶頸。由於 Hudi 設計之初嚴重依賴 Spark。0.7.0 的版本才剛剛支援 Flink。不管是在穩定性還是在功能上都和 Spark On Hudi 有非常大的差距。因此在進行高 QPS 入湖的情況下,我們就遇到了單個 Flink 任務的擴充套件性問題。

我們通過在 Flink 的 embedding term server 上支援對當前進行中的事務元資訊進行一下快取,大幅提升了單個任務能夠併發寫入的檔案量級,基本上是在 80 倍的量級。結合分割槽級別的併發寫入,我們整體支撐了近千萬 QPS 的資料量的增量入湖。

下一步的併發問題是批流併發衝突的問題。批流併發衝突問題類似於一個我們在傳統資料湖中遇到的場景,就是有一連串的小事務和一個週期比較長的長事務,如果這兩者發生衝突,應該如何處理。

如果讓短事務等長事務完成之後再進行,那對一個實時的鏈路來說,就意味著資料的可見性變低了。同時如果在等待過程中失敗了,還會有非常高的 fail over 成本。但是如果我們讓這個長事務失敗了,成本又會很高,因為這個長事務往往需要耗費更多的資源和時間。而在批流併發衝突的這個場景下,最好是兩都不失敗,但這從語義上來講又不符合我們認知中的隔離級別。

為了解決批流衝突的問題,我們的思路是提供更靈活的衝突檢查和資料合併策略。最基礎的就是行級併發,首先兩個獨立的 writer 寫入的資料在物理上就是隔離的,藉助檔案系統的租約機制也能夠保證對於一個檔案同時只有一個 writer。所以這個衝突實際上不是發生在資料層面的,而是發生在元資料層面。那資料的衝突與否,就可以交由使用者來定義。很多時候入湖的資料實際上並不是一個現實中正在發生的事情,而是一個現實操作的回放。比如圖中的這個場景,我們假設刪除的作業是針對一個特定的 Snapshot。即使有衝突,我們可以認為整個刪除的過程是瞬時完成的,後續的新事物可以追加的發生在這次刪除作業之後。

第二就是列級併發。比如接下來在實踐實際案例中,我們要介紹的這個實時資料關聯場景,每個 writer 實際上只是根據主鍵去更新部分的列。因此這些資料其實在行級別看起來是衝突的,但是從列的角度來看是完全不衝突的。配合我們的一些確定性索引,資料能被寫入到同一個檔案組中,這樣就不會出現一致性的問題。

最後就是衝突合併。假如兩個資料真的是在行級別和列級別都發生了衝突,那真的只能通過 fail 掉一個事務才能完成嗎?我覺得是不一定的,這裡我們受到了 git 的啟發。假如兩次 commit 衝突了,我們是不是可以提供 merge 值的策略,比如資料中帶有時間戳,在合併時就可以按照時間戳的先後順序來做合併。

  1. 更新效能差

我們最早選擇基於 Hudi 也是因為可擴充套件的索引系統,通過這個索引系統可以快速地定位到需要跟新的檔案。這帶來了三點好處,一個是避免讀取不需要的檔案;二是避免更新不必要的檔案;三是避免將更新的資料和歷史的資料做分散式關聯,而是通過提前將檔案分好組的方式直接在檔案組內進行合併。

在早期的落地過程當中,我們嘗試儘可能去複用 Hudi 的一些原生能力,比如 Boom Filter index。但是隨著資料規模的不停增長,當達到了千億的量級之後,upsert 的資料隨著資料量的增長逐漸放緩,到了數千億的量級後,消費的速度甚至趕不上生產者的速度。即使我們去為它擴充了資源,而這時的資料總量其實也只是在 TB 級別。我們分析了每個檔案組的大小,發現其實檔案組的大小也是一個比較合理的值,基本上是在 0.5g 到 1g 之間。進一步分析,我們發現隨著資料量的增長,新的匯入在通過索引定位資料的這一步花費的時間越來越長。

根本原因是 Bloom Filter 存在假陽性,一旦命中假陽性的 case,我們就需要把整個檔案組中的主鍵鏈讀取上來,再進一步地去判斷這個資料是否已經存在。通過這種方式來區分這個到底是 update 還是 insert。upsert 本身就是 update 和 insert 兩個操作的結合,如果發現相同元件資料不存在,就進行 insert。如果存在,我們就進行 update。而 Bloom Filter 由於假陽性的存在,只能加速資料的 insert 而沒有辦法去加速 update。這就和我們觀察到的現象很一致。因為這個 pipeline 在執行初期,大部分資料都是第一次入湖,是 insert 操作,因此可以被索引加速。但是規模達到一定量級之後,大部分資料都是更新操作,沒有辦法再被索引加速。為了解決這個問題,我們急需一個更穩定更高效的索引。

Bloom Filter 索引的問題,根因是讀取歷史資料進行定位,導致定位的時間越來越長。那有沒有什麼辦法是無需讀歷史資料,也可以快速定位到資料所在位置呢?很自然的,我們就想到了類似於 Hive 的 bucket,也就是雜湊的方法來解決這個問題。

Bucket Index 原理比較簡單,整個表或者分割槽就相當於是一張雜湊表,檔名中記錄的這個雜湊值,就相當於雜湊表中這個陣列的值。可以根據這個資料中的主鍵雜湊值快速地定位到檔案組。一個檔案組就類似於雜湊表中的一個連結串列,可以將資料追加到這個檔案組當中。Bucket Index 成功地解決了流式更新效能的問題。由於極低的定位資料的成本,只要設定了一個合適的 bucket 桶大小,就能解決匯入效能的問題。將流式更新能覆蓋的場景從 TB 級別擴充套件到了百 TB 級別。除了匯入的效能,Bucket Index 還加速了資料的查詢,其中比較有代表性的就是 bucket Pruning 和 bucket join。

當然這種索引方式我們也遇到了一些擴充套件性的問題,使用者需要提前一步做桶數的容量規劃,給一個比較安全的值,避免單個桶擴大,以便應對接下來的資料增長。在資料傾斜的場景下,為了讓傾斜值儘可能分散在不同的 bucket,會將 bucket 的數量調到很大。而每個 bucket 平均大小很小,會帶來大量的小檔案,給檔案系統帶來衝擊的同時也會帶來查詢側效能下滑和寫入側的資源浪費。同時在一線快速增長的業務,很難對容量有一個精準的預估。如果估算少了,資料量飛速增長,單個的 bucket 的平均大小就會很大,這就會導致寫入和查詢的併發度不足,影響效能。如果估算多了,就會和傾斜的場景一樣出現大量的小檔案。整體的 rehash 又是一個很重的運維操作,會直接影響業務側對資料的生產和使用。因此不管從業務的易用性出發,還是考慮到資源的使用率和查詢的效率,我們認為兼具高效匯入和查詢效能,也能支援彈性擴充套件的索引系統是一個重要的方向。

這時我們就想到了可擴充套件 hash 這個資料結構。利用這個結構,我們可以很自然地去做桶的分裂和合並,讓整個 bucket 的索引從手動駕駛進化到自動駕駛。 在資料寫入的時候,我們也可以快速地根據現有的總數,推斷出最深的有效雜湊值的長度,通過不斷地對 2 的桶深度次方進行取餘的方式,匹配到最接近的分桶寫入。我們將 Bucket Index 這個索引貢獻到了社群,已在 Hudi 的 0.11 版本對外發布。

  1. 日誌難入湖

本質原因也是因為 Hudi 的索引系統。因為這個索引系統要求資料按照元件聚集,一個最簡單的方式就是把這個元件設成 UUID。但這樣就會帶來效能上的問題以及資源上的浪費。因此我們在 Hudi 之內實現了一套新的機制,我們認為是無索引。就是繞過 Hudi 的索引機制,去做到資料的實時入湖。同時因為沒有主鍵, Upsert 的能力也失效了。我們提供了用更通用的 update 能力,通過 shuffle hash join 和 broadcast join 去完資料實時更新。

實時資料湖在位元組內部的一些實踐案例

接下來詳細介紹實時資料湖在位元組的實踐場景。電商是位元組發展非常快速的業務之一,資料增長非常快,這也對數倉的建設提出了較高的要求。目前電商業務資料還是典型的 lambda 架構,分為是離線數倉和實時數倉建設。在實際場景中, lambda 架構的問題相信大家都已經比較瞭解了,我就不多做贅述了。這次的場景介紹是圍繞一個主題,通過資料湖來構建實時數倉,使實時資料湖切入到實時數倉的建設當中。這不是一蹴而就的,是分階段一步一步滲透到實時數倉的建設當中。而實時資料湖的終極目標也是在儲存側形成一個真正意義上的批流一體的架構。

我們切入的第一個階段是實時資料的近實時可見可測。坦白說,在實時資料湖的落地初期,對於資料湖是否能在實時數倉中真正勝任,大家都是存疑的。因此最早的切入點也比較保守,用在資料的驗證環節。在電商的實時數倉中,由於業務發展快,上游系統變更,以及資料產品需求都非常多。導致實時數倉開發週期短,上線變更頻繁。當前這個實時的資料的新增欄位和指標邏輯變更,或者在任務重構優化時,都要對新版本的作業生成的指標進行驗證。驗證的目標主要有兩點,一是原有指標,資料是否一致,二是新增指標的資料是否合理。

在採用資料湖的方案之前,資料湖的驗證環節需要將結果匯入到 Kafka 然後再 dump 到 Hive。進行全量資料校驗。這裡存在的一個問題就是資料無法實時或者近實時可見可檢的,基本上都是一個小時級的延遲。在很多緊急上線的場景下,因為延時的問題,只能去抽測資料進行測試驗證,就會影響資料質量。實時資料湖的方案,是通過將實時資料低成本的增量匯入到資料湖中,然後通過 Presto 進行查詢,然後進行實時計算彙總,計算的結果做到近實時的全面的可見可測。

當然在這個階段中,我們也暴露出了很多資料湖上易用性的問題。業務側的同學反饋最多的問題就是資料湖的配置過於複雜。比如要寫一個數據湖的任務,Hudi 自身就存在十多個引數需要在寫入任務中配置。這增加了業務側同學的學習成本和引擎側同學的解釋成本。同時還需要在 Flink SQL 裡定義一個 sync table 的 DDL,寫一個完整的 schema。很容易會因為頁的順序或者拼寫錯誤導致任務失敗。

我們藉助了 Hudi Metastore Server 的能力,封裝了大量的引數。同時使用 Flink Catalog 的能力,對 Meta Server 進一步封裝,讓使用者在配置一個 Fink SQL 任務的時候,從最初的寫 DDL 配置十多個引數,到現在只要寫一條 create table like 的語句,配置一張臨時表,使用者對這種方式的接受度普遍是比較高的。

第二個階段,也就是第二個應用場景是資料的實時入湖和實時分析。資料湖可以同時滿足高效的實時資料增量匯入和互動式分析的需求,讓資料分析師可以自助地去搭建看板,同時也可以進行低成本的資料回刷,真正做到一份資料批流兩種使用方式。在這個階段,由於資料實際上已經開始生產了,使用者對於資料入湖的穩定性和查詢效能都有很高的要求。我們通過將 Compaction 任務與實時匯入任務拆分,首先解決了資源搶佔導致的入湖時效性比較低的問題,同時設計了 compaction service,負責 compaction 任務的排程,整個過程對業務側同學完全遮蔽。我們在服務層面也對報警和監控進行了加強,能夠做到先於業務去發現問題,處理問題,進一步提升了任務的穩定性,也讓我們的使用方能夠更有信心地去使用實時資料湖。

在查詢的優化上面,我們優化了讀檔案系統的長尾問題,支援了實時表的列裁剪。同時我們對 Avro 日誌進行了短序列化和序列化的 case by case 的優化,還引入了列存的 log 進一步提升查詢效能。除了實時資料分析之外,這種能力還可以用於機器學習。在特徵過程當中,有些 label 是可以快速地從日誌中實時獲取到的。比如對一個視訊點了個贊,和特徵是可以關聯上的。而有些 label 的生成則是長週期的。比如在抖音上買了一個東西,或者把一個東西加入購物車,到最後的購買,這整個鏈路是很長的,可能涉及到天級別或者周級別的一個不定週期。但是在這兩種情況下,它的特徵資料基本上都是相同的,這也使底層的儲存有了批流兩種使用方式的訴求,以往都是通過冗餘的儲存和計算來解決的。通過資料湖可以將短週期的特徵和標籤實時地入湖,長週期的每天做一次排程,做一個批式入湖,真正能做到一份資料去適用多個模型。

第三個階段的應用場景是資料的實時多維彙總。在這個階短最重要的目標是實時資料的普惠。因為很多的實時資料使用方都是通過視覺化查詢或者是資料服務去消費一個特定的彙總資料。而這些重度彙總過後的實時資料使用率相對來說是比較低的。因此我們和數倉的同學共同推進了一個實時多維彙總的方案落地。數倉的同學通過實時計算引擎完成資料的多維度的輕度彙總,並且實時地更新入湖。下游可以靈活地按需獲取重度彙總的資料,這種方式可以縮短資料鏈路,提升研發效能。

在實際的業務場景中,對於不同的業務訴求,又可以細分成三個不同的子場景。第一個場景是內部使用者的視覺化查詢和報表這一類場景。它的特點就是查詢頻率不高,但是維度和指標的組合靈活,同時使用者也能容忍數秒的延遲。在這種場景下,上層的資料應用直接呼叫底層的 Presto 引擎行為實時入庫的資料進行多維度的重度聚合之後,再做展現。另外一個主要的場景就是面向線上的資料產品,這種場景對高查詢頻率、低查詢延遲的訴求比較高,但是對資料可見性的要求反而不那麼高。而且經過重度彙總的資料量也比較小,這就對資料分析工具提出了比較大的挑戰。因此在當前階段,我們通過增加了一個預計算鏈路來解決。

下面一個問題,多維重度彙總的多維計算結果是從我們湖裡批量讀出來,然後定時地去寫入 KV 儲存,由儲存去直接對接資料產品。從長期來看,我們下一步計劃就是對實時資料湖之上的表去進行自動地構建物化檢視,並且載入進快取,以此來兼顧靈活性和查詢效能,讓使用者在享受這種低運維成本的同時,又能滿足低延低查詢延遲、高查詢頻率和靈活使用的訴求。

第四個典型的場景是實時資料關聯,資料的關聯在數倉中是一個非常基礎的訴求。數倉的同學需要將多個流的指標和維度列進行關聯,形成一張寬表。但是使用維表 join,尤其是通過快取加速的方式,資料準確性往往很難保障。而使用多流 join 的方式又需要維持一個大狀態,尤其是對於一些關聯週期不太確定的場景,穩定性和準確性之間往往很難取捨。

基於以上背景,我們的實時資料湖方案通過了這個列級的併發寫入和確定性的索引。我們支援多個流式任務併發地去寫入同一張表中,每個任務只寫表中的部分列。資料寫入的 log 件在物理上其實是隔離的,每個 log 檔案當中也只包含了寬表中的部分列,實際上不會產生互相影響。再非同步地通過 compaction 任務定期的對之前對 log 資料進行合併,在這個階段對資料進行真正的實際的關聯操作。通過這種方式,提供一個比較穩定的效能。使用這一套方案,實時關聯使用者也不用再關注狀態大小和 TTL 該如何設定這個問題了,寬表的資料也可以做到實時可查。

最後一個階段。我們認為是實時資料湖的終極階段,目前仍在探索中。我們只在部分場景開啟了驗證。在這個架構裡面,資料可以從外部的不同資料來源中實時或者批量的入湖和出湖,而流批作業完成湖內的資料實時流轉,形成真正意義上的儲存層批流一體。

同時在這套架構中,為了解決實時資料湖從分鐘級到秒級的最後一公里,我們在實時引擎與資料湖的表之間增加了一層資料加速服務。在這層資料加速服務之上,多個實時作業可以做到秒級的資料流轉。而這個服務也會解決頻繁流式寫入頻繁提交導致的小檔案問題,為實時資料的互動查詢進一步提速。除此之外,由於流批作業的特性不同,批計算往往會需要更高的瞬時吞吐。因此這些批計算任務也可以直接地去讀寫底層的池化檔案系統,來做到極強的擴充套件性,真正意義上做到批流寫入的隔離,批作業的寫入不會受限於加速服務的頻寬。在這個批流一體的架構中,資料湖之上的使用者,不管是 SQL 查詢,還是 BI 、AI ,都可以通過一個統一的 table format 享受到資料湖之上資料的開放性。

資料湖發展的一些規劃

最後來看一下未來規劃。主要聚焦於三個維度:功能層面的規劃,開源層面的規劃,以及商業化輸出相關的一些規劃。

  1. 功能層面

首先是功能維度,我們認為一個更智慧的實時資料湖的加速系統是我們最重要的目標之一。

首先是元資料層面的加速,資料湖託管了檔案級別的元資料,元資料的資料量,相比數倉有了幾個量級的增長,但同時也給我們帶來了一些優化的機會。比如我們未來計劃將查詢的謂詞直接下推到元資料系統當中,讓這個引擎在 scan 階段無需訪問系統,直接去跳過無效檔案來提升查詢的效能。

其次就是資料的加速。當前的實時資料湖由於其 serverless 架構對檔案系統的重度依賴,在生產實踐中還是處於分鐘級,秒級依舊處於驗證階段。那我們接下來計劃將這個資料湖加速服務不斷地去打磨成熟,用來做實時資料的交換和熱資料的儲存,以解決分鐘級到秒級的最後一公里問題。智慧加速層面臨的最大的挑戰是批流資料寫入的一致性問題,這也是我們接下來重點要解決的問題。例如在這種端到端的實時生產鏈路中,如何在提供秒級延時的前提下解決類似於跨表事務的問題。

第三是索引加速。通過 bucket, zorder 等一系列的主鍵索引,來進一步地提升資料湖之上的資料的查詢效能,過濾掉大量的原始資料,避免無效的資料交換。同時我們接下來也會非常注重二級索引的支援,因為二級索引的支援可以延伸湖上資料的更新能力,從而去加速非主線更新的效率。

第四是智慧優化。我們接下來會通過一套表優化服務來實現智慧優化,因為對於兩個類似的查詢能否去提供一個穩定的查詢效能,表的資料分佈是一個關鍵因素。而從使用者的角度來看,使用者只要查詢快、寫入快,像類似於 compaction 或 clustering、索引構建等一系列的表優化的方式,只會提升使用者的使用門檻。而我們的計劃是通過一個智慧的表優化服務分析使用者的查詢特徵,去同時監聽這個資料湖上資料的變化,自適應的去觸發這個表的一系列的優化操作,可以做到在使用者不需要了解過多細節的情況下,做到智慧的互加速。
2. 開源層面

第二個維度是開源貢獻。我們現在一直在積極地投入到 Hudi 的社群貢獻當中。參與了多個 Hudi 的核心 feature 的開發和設計。其中 Bucket index 是我們合入到社群的第一個核心功能,而當下我們也在同時貢獻著多個重要的功能,比如最早提到的解決資料難管理的 Hudi MetaStore Server,我們已經貢獻到社群了,去普惠到開源社群。因為我們發現 Hudi MetaStore Server 不止解決我們在生產實踐中遇到的問題,也是業界普遍遇到的一個問題。現在也在跟 Hudi 社群的 PMC 共同探討資料湖的元資料管理系統制定標準。

其它一些功能我們也計劃分兩個階段貢獻到社群。比如 RPC 42,將我們的湖表管理服務與大家共享,長期來看能夠做到資料湖上的表的自動優化。還有 Trino 和 Presto DB 的 Hudi Connector,目前也是在和 Hudi 背後的生態公司共同推進投入到開源社群當中。
(3)商業化輸出
當前在火山引擎之上,我們將內部的資料湖技術實踐同時通過​​LAS​​和​​EMR​​這兩個產品向外部企業輸出。其中 LAS 湖倉一體分析服務是一個整體面向湖倉一體架構的 Serverless 資料處理分析服務,提供一站式的海量資料儲存計算和互動分析能力,完全地去相容 Spark、Presto 和 Flink 生態。同時這個產品具備了完整的位元組內部的實時資料湖的成熟能力,能夠幫助企業輕鬆完成湖倉的構建和資料價值的洞察。

另外一個產品 EMR 是一個 Stateless 的雲原生數倉,100%開源相容,在這個產品當中也會包含位元組資料湖實踐中一些開源相容的優化,以及一些引擎的企業級增強,以及雲上便捷的運維能力。

最後,歡迎大家關注位元組跳動資料平臺公眾號,在這裡有非常多的技術乾貨、產品動態和招聘資訊。
立即跳轉了解:火山引擎LAS火山引擎EMR 產品