這是一篇較爲完整的介紹Apache Paimon和Flink進階應用的文章。
0 序
Apache Paimon 畢業(2024.04.16)
-
北京時間 2024 年 4 月 16日,開源軟件基金會
Apache Software Foundation
(以下簡稱ASF
)正式宣佈 Apache Paimon 畢業成爲 Apache 頂 級項目(TLP, Top Level Project)。經過社區的共同努力和持續創新,Apache Paimon 在構建實時數據湖與流批處理技術領域取得了重大突破,數據湖步入實時新篇章! -
Apache 官方博客發佈了 Apache Paimon 畢業的消息:
項目背景:大數據的不可能三角(成本、查詢延時、新鮮度)
- 大數據系統都是有着不同側重的考慮,甚至還要加上開發人員的精力,構建了一系列的權衡 (Trade-off),這個世界上目前並沒有 Silver Bullet (銀彈、具有極端有效性的解決方法),沒有大一統的系統,每個場景有對應的解決方案。
流計算+存儲 場景 1:實時預處理 | Queue + Flink(SQL + Stateful Streaming) + HBase/Redis/MySQL + Online Query
- 大概 10 年前,Storm 開源,8000 行 Clojure 代碼組成一個完整的流計算系統,驚豔的設計,巧妙的 Ack 機制解決 At-least-once 的問題,解決流計算的基本訴求:
- 數據流起來。
- 數據不丟。但是隨着業務的發展,流計算越來越需要 Exactly-once 的保證和 SQL API。
- 在還沒有分佈式流計算時,人們往往想通過批的調度來達到實時計算的效果。
- 最早使用 Hive 進行分鐘級的調度,但是調度和進程啓停的成本比較高,所以調度時延並不能太低。
- Spark 憑藉 RDD 優秀的設計,在其之上提供了 Spark Streaming,進程常駐,數據每批次調度過去進行一次 mini-batch 的計算,完全複用了批計算的能力,達到了流計算的能力,以此來提供了 Exactly-once 的一致性保障,但是它並不是常駐進程的流計算,由於調度的開銷,最低延時只能到分鐘級。
-
Apache Flink
在 2014 年橫空出世,通過內置狀態存儲和全局一致性快照設計,很好的解決了純流(early-fire) 模式下保證 Exactly-once 的問題,實現了數據一致性和低延遲的兼顧。 -
同年筆者加入阿里 Galaxy 團隊支撐阿里內部的流計算業務(Galaxy 系統的架構跟 Flink 有很多相似之處,限於篇幅不做展開),並且負責流計算入 Tair (阿里自研的一種 KV 存儲,類似 Redis + HBase)的工作。當時的流計算可以說等同於 :流計算 + 在線 KV 服務:
本質上是一種將數據進行多維預處理的技術,預處理後的數據寫入模式簡單的 KV 系統(HBase 或 Redis)中,或者寫入數據存儲量較小的關係型數據庫中。流計算在此中的作用是 計算流動的數據、並維護狀態、把結果輸出到 KV 系統中。
這個架構的優缺點分別是:
- 1、查詢速度超快:業務需要查詢到的數據都是已經準備好的,KV 系統和關係型數據庫作爲在線服務,提供毫秒級查詢延時。
- 2、靈活性低、開發成本高:數據集有限(關係型數據庫),或者數據模式受限(KV 系統),新增業務需要新增開發一條完整的鏈路。
它利用了KV系統或關係型數據庫的可更新性,能夠以冪等更新的方式在達到秒級實時的同時實現最終一致(類Exactly-once)的效果。
當然這個架構也是目前實時數倉比較主流的架構,對於重要業務來說,這是不二之選。
那有沒有系統可以更靈活一點,不用爲了業務的新增定製一條鏈路?
流計算+存儲 場景 2:實時數倉 | Queue + Flink + OLAP + Online Query
- ClickHouse 由 Yandex 開發,在 2016 年開源,ClickHouse 看來只是一個單機的 OLAP 引擎,需要一些功夫也能拼出分佈式的 OLAP,它的核心亮點是通過向量化的計算帶來了超快的查詢性能。
這帶來了一個可能性,可以將業務數據放到
Clickhouse
中,能提供比較靈活的業務查詢,而不僅僅只能預定義計算。
-
隨着 ClickHouse 的逐漸流行,國內也湧現出很多優秀的 OLAP 項目,包括 Doris、StarRocks,阿里雲的 Hologres,同時阿里內部 JStorm、Galaxy、Blink 合併成一個團隊,大力發展 Blink,Blink 基於開源的 Flink,有着更新的架構、優秀的設計、高質量的代碼和活躍的開源社區。
-
Flink + OLAP 系統(如: Clickhouse/Doris/Hologres) 形成了一個新的場景:通過 Flink 流計算做一些預處理,但是不用形成直接的業務數據,輸出到 Hologres,這樣 Hologres 保存相對更加完整的數據,提供給業務方高性能的查詢,解決了部分 KV 系統不靈活導致無法靈活分析的問題**。
- 這個架構的好處是:
1、查詢速度很快:OLAP 系統使用向量化計算,結合固態 SSD 磁盤,高性能機型,提供毫秒級的計算。
2、靈活性較高:OLAP 系統保存 Schema 化的數據,可以根據業務場景使用不同的查詢模式。 (OLAP 系統有個通病是 分佈式 Join 遠不如 聚合,所以一般更多使用大寬表來滿足查詢性能。)
- 但這個架構也不是完全通用的,有個主要問題是:OLAP 的存儲很貴。
OLAP 也做了一些工作來避免過高的成本,比如存算分離,但是它需要保證查詢的實時性,所以從架構上仍然是高成本的設計。
1、其中 Flink 不僅僅只是做一些數據的打寬,也需要做一些數據ETL來確保最後落到 OLAP 系統的數據不會太多,這會帶來額外的成本。
2、另外 OLAP 系統也不能保存太久的歷史數據。比如,一般情況下,保存最近7天的數據,7天前的數據通過 TTL 被淘汰掉。
-
所以這裏就有一個
Trade-Off
,是預處理(流計算)多一些,還是把更多的數據放入 OLAP 系統,不同的業務有不同的選擇。 -
那有沒有辦法可以再靈活一點,以比較低的成本保存所有數據?
流計算+存儲 場景 3:實時湖倉
流計算+存儲 場景 3.1:Hive 實時化 | ... + Flink + Hive
-
既然 OLAP 存儲比較貴,那我們就將流計算的結果寫入到一個不那麼貴的存儲裏,通過存儲所有數據,提供靈活的 Ad-hoc 查詢 (雖然沒 OLAP 那麼快)。
-
另外筆者加入 Blink 團隊後,最開始的主要工作是 Blink 的批計算上,隨着後續的發展,也在思考流批一體不僅僅只是計算的一體,計算的一體本質上並不能帶來足夠的業務價值。
-
開啓 Flink + Hive 存儲 (流批融合) 的一些工作:
Flink Hive Sink
支持寫入 Parquet、ORC、CSV 等格式,提供 Exactly-once 的寫入一致性保證,也支持配置分區提交的能力。這解決日誌數據流入 Hive 離線數倉的能力,它的優缺點有:
- 1、離線數倉近實時化,爲了一致性保證,延時是 Checkpoint 級別的,一般在分鐘級。
- 2、存儲成本低,存儲大部分原始數據,非常靈活。
- 3、查詢性能差,數據只是列存,存儲到便宜的機器和便宜的磁盤上,讀取較慢。
流計算+存儲 場景 3.2:Iceberg 實時化
-
隨着國外 Snowflake 和 Databricks 的崛起,湖存儲成爲了取代傳統 Hive 數倉的新興力量。
-
Apache Iceberg
是比較典型的湖存儲,它作爲替代 Hive 的方案有着以下好處:
- 1、提供 ACID 一致性保證:
>> a. 數據更安全了,這意味着你可以做一些小數據的操作:比如 INSERT INTO 一些數據,DELTE \ UPDATE \ MERGE_INTO 有着更好的支持。
>> b. 而不是像 Hive 一樣,要安全的動數據只能 INSERT OVERWRITE 整個分區。- 2、易擴展的元數據管理:
>> a. 對象存儲的 list 文件非常慢,使用 Iceberg 的元數據管理,可以避免 list 文件帶來的性能瓶頸。(爲什麼要上對象存儲,因爲存儲可分級————便宜啊)
>> b. 這意味着你可以擺脫 Hive Metastore 裏面保存 Partition 信息的各種瓶頸問題 (HMS 的 Mysql 又掛了)。
>> c. 基於元數據的 Data Skipping,可以在離線數倉近實時化場景下,除了提升數據新鮮度,還能(基於排序字段的過濾)降低查詢的延遲,也給進一步的索引加速帶來了可能。
- 3、流讀流寫更容易和自然。
- 數據可以實時入湖,甚至
Iceberg
的數據也可以被實時流讀。在Hive 離線數倉
的基礎上增強了可用性。
但是,該解決方案缺乏對 Upsert 場景的支持,存在以下問題:
- 1、離線數倉最頭疼的場景是 CDC 入倉,傳統的全量表 + 增量表的方案,不僅存儲成本高,計算成本也高,操作還繁瑣。
- 2、流計算過程產生的 CDC 數據無法很好的處理。
CDC 入倉:全量與增量
- 傳統的 Hive 數倉在解決
CDC
入倉時使用了 : 全量表 + 增量表的技術。
具體如圖:
- 具體步驟如下:
- 1、等到 24 點後,同步當天的 Binlog 數據產出一份增量數據,形成增量表的一個分區。
- 2、將當天的增量表分區與前一天的全量表分區進行合併,產出全量表的當天分區。
- 3、全量表分區面向全量查詢,增量表分區面向增量查詢。
- 可以看出,這個過程的成本非常高,特別對於大全量、少增量的表:
- 1、存儲成本非常高,每天一個全量,每天一個增量,完全沒有複用。
- 2、計算成本非常高,每天都需要全量的讀寫合併。
- 3、延時 T + 1,只能用作離線查詢。
- 4、離線表的準備時間也很長,需要等待全增量合併。
- 那假設有一個能夠更好支持 Upsert 更新的數據湖系統,數據直接 Upsert 寫進去怎麼樣?那此時數據湖裏面的這張表就是業務數據庫那張表的鏡像,流 + 湖存儲 需要一個支持按主鍵更新的數據湖!
流計算+存儲 場景 3.3:Upsert 的探索 | Iceberg、Hudi、Delta、Hive-ACID
- 2020 年,筆者所在阿里雲團隊調研了三大數據湖:Iceberg、Hudi、Delta,當時阿里雲的胡爭 (後面的 Iceberg PMC 成員) 寫了一篇非常好的文章:
- 深度對比 Delta、Iceberg 和 Hudi 三大開源數據湖方案 - INFOQ 裏面的總結非常有趣:
- 如果用一個比喻來說明 Delta、Iceberg、Hudi、Hive-ACID 四者差異的話,可以把四個項目比做建房子。
- 1、 Delta 的房子底座相對結實,功能樓層也建得相對高,但這個房子其實可以說是 Databricks 的 。
(筆者補充:今天 Delta 依然是 Databricks 的,也對接了其它引擎,但成熟度不高,開源版本距離 Databricks 商業版也有一定差距)- 2、Iceberg 的建築基礎非常紮實,擴展到新的計算引擎或者文件系統都非常的方便,但是現在功能樓層相對低一點
(筆者補充:今天 Iceberg 建築紮實,北美數倉 SAAS 強者衆多,各個批數倉對接 Iceberg,在北美風生水起)- 3、Hudi 的情況有所不同,它的建築基礎設計不如 iceberg 結實,舉個例子,如果要接入 Flink 作爲 Sink 的話,需要把整個房子從底向上翻一遍,同時還要考慮不影響其他功能;但是 Hudi 的樓層是比較高的,功能是比較完善的 。
(筆者補充:Hudi 功能多,各種功能都有,目前在中國流行起來,Upsert 寫入在中國打開了局面)- 4、Hive-ACID 的房子,看起來是一棟豪宅,絕大部分功能都有,但是細看這個豪宅的牆面是其實是有一些問題的 。
(筆者補充:Hive-ACID 國內應用案例較少)
紮實的房子對我們來說更具有誘惑力,我們堅信能基於 Iceberg 打造出屬於 Streaming 的 Lakehouse,所以選取了 Iceberg 作爲後續主要的方向來突破,胡爭和筆者在 Iceberg 社區突破兩個方向:
- 1、Flink 集成:如何基於 Flink 和 Iceberg 構建雲原生數據湖?
- 2、CDC 入湖:Flink 如何實時分析 Iceberg 數據湖的 CDC 數據
-
一個階段性產出是 Flink + Iceberg 進入生產可用,Flink 入湖成爲主流應用,CDC 入湖基本可用。
-
但是,CDC 只是基本可用,離我們的想象還差的較遠,大規模更新與近實時延時都有距離,更別說流讀的增強了,主要的原因有如下:
- 1、Iceberg 社區基本盤還是在離線處理,它在國外的應用場景主要是離線取代
Hive
,它也有強力的競爭對手 Delta,很難調整架構去適配 CDC 流更新。
- 2、Iceberg 擴展性強,對其它計算引擎也暴露的比較多的優化空間,但是這也導致後續的發展難以迅速,涉及到衆多已經對接好的引擎。
-
這並沒有什麼錯,後面也證明了 Iceberg 主打離線數據湖和擴展性是有很大的優勢,得到了衆多國外廠商的支持。
-
Upsert 探索的另一個方向是 Flink + Hudi,當時阿里雲的陳玉兆 (後面的 Hudi PMC 成員) 加入 Hudi 社區一起推出 Flink + Hudi Connector。
-
2021 年 12 月 FFA 的分享:使用 Flink Hudi 構建流式數據湖平臺 - Aliyun
- Hudi 默認使用 Flink State 來保存 Key 到 FileGroup 的 Index,這是一套和 Spark 完全不同的玩法:
- 1、好處是全自動,想 Scale Up 只用調整併發就行了
- 2、壞處是性能差,直接讓湖存儲變成了實時點查,超過5億條數據性能更是急劇下降。
- 3、存儲成本也高,RocksDB State 保存所有索引。
- 4、數據非常容易不一致,甚至再也不能有別的引擎來讀寫,因爲一旦讀寫就破壞了 State 裏面的 Index。
- 針對 Flink State Index 諸多問題,字節跳動的工程師們在 Hudi 社區提出了 Bucket Index 的方案 :
- 此方案非常簡單,但又解決問題:“給定 n 個桶, 用 Hash 函數決定某個記錄屬於哪個桶。最終所有分區被分成 N 個桶,每個桶對應一個 File Group。”
1、好處是去除了 Index 帶來了諸多性能問題。
2、壞處是需要手動選取非常合適的 Bucket Number
,多了小文件操作很多,少了性能不行。
-
這套方案也是目前 Hudi 體量較大的用戶的主流方案。
-
Hudi 在阿里雲上也有不少的用戶,但是隨着用戶的增多,問題浮現出來了:
- 1、Hudi 衆多的模式讓用戶難以選擇。
>> a. 使用 Flink State 還是 Bucket Index?一個易用性好、但是性能不行,一個難以使用。
>> b. 使用 CopyOnWrite 還是 Merge On Read?一個寫入吞吐很差,一個查詢性能很差。
- 2、更新效率低,1-3 分鐘 Checkpoint 容易反壓,默認 5 次 Checkpoint 合併,一般業務可接受的查詢是查詢合併後的數據;全增量一體割裂,難以統一。
- 3、系統設計複雜,Bugs 難以收斂,工單層出不窮 (筆者也當過半年 Hudi 負責人);各引擎之間的兼容性也非常差;參數衆多。
-
Hudi
天然面向 Spark 批處理模式設計而誕生,不斷在面向批處理的架構上進行細節改造,無法徹底適配流處理更新場景,在批處理架構上不斷強行完善流處理更新能力,導致架構越來越複雜,可維護性越來越差。 -
Hudi 的穩定性隨着近幾個版本已經好很多了,我覺得這可以歸功於來自中國的開發者和使用者們,他們解決 Hudi 各種各樣的穩定性和正確性的漏洞,一步一步踩坑探索實時數據湖。
-
但是,看看最新的 Hudi Roadmap:hudi.apache.org/roadmap
-
你會發現 Flink 相關,流相關依然少的可憐,往往 Hudi 社區做了一個新 Feature,只有 Spark 支持,Flink 支持不了,如果 Flink 要支持,需要天翻地覆的重構,大重構又會誕生很多的 Bugs,還不一定能支持的很好。Hudi 是個好系統,但Hudi不是爲實時數據湖而生的。
-
那我們需要什麼?
- 1、一個湖存儲有着類似 Iceberg 良好的建築基礎,功能滿足湖存儲的基本訴求。
- 2、一個具有很強
Upsert
能力的存儲,需要 OLAP 系統 & 流計算 State & KV 系統 都使用的 LSM 結構。- 3、一個
Streaming First
,面向 Flink 有最好集成的存儲,這座房子的地基應該直接考慮 Streaming & Flink 的場景,而不是在一個複雜的系統上修修補補,越走到後面,越喫力。- 4、一個更多面向中國開發者以及使用者的社區,而且社區的主方向應該是長期投入 Streaming + Lake。
- Streaming Lakehouse 的路還很長,那就造一個吧。
Apache Paimon 的誕生 //TODO
1 項目簡介
簡介
- Flink 社區希望能夠將 Flink 的 Streaming 實時計算能力和 Lakehouse 新架構優勢進一步結合,推出新一代的 Streaming Lakehouse 技術,促進數據在數據湖上真正實時流動起來,併爲用戶提供實時離線一體化的開發體驗。爲此,Flink 社區內部孵化了 Flink Table Store (簡稱
FTS
)子項目,一個真正面向 Streaming 以及 Realtime 的數據湖存儲項目。
2023年3月12日,FTS 進入 Apache 軟件基金會 (ASF) 的孵化器,改名爲 Apache Paimon (incubating)。
Apache Paimon
是一個流數據湖平臺,具有高速數據攝取、變更日誌跟蹤和高效的實時分析的能力。
發展歷程
Apache Paimon
原名Flink Table Store
,2022年1月在 Apache Flink 社區從零開始研發,Flink 社區希望能夠將 Flink 的 Streaming 實時計算能力和 Lakehouse 新架構優勢進一步結合,促進數據在數據湖上真正實時流動起來,併爲用戶提供實時離線一體化的開發體驗。
-
2023 年 3 月 12 日,
Flink Table Store
項目順利通過投票,正式進入 Apache 軟件基金會 (ASF) 的孵化器,改名爲Apache Paimon
,一個真正面向Streaming
以及Realtime
的數據湖存儲項目。之後在導師 Yu Li、Becket Qin、Stephan Ewen、 Robert Metzger 的指導下,由Apache孵化器管理委員會成員進行輔導和孵化。 -
2024 年 3 月 20 日,
Apache
董事會通過Apache Paimon
畢業決議,結束了爲期一年的孵化,正式確定 Apache Paimon 成爲 Apache 頂 級項目。 -
孵化的一年間,Paimon 社區的貢獻者和關注者都獲得了非常大的提升。
- Paimon 在2023-2024這一年裏發佈了四個大版本,並在大量企業生產實踐中使用,包括 阿里巴巴、字節跳動、同程旅行、螞蟻集團、中國聯通、網易、中原銀行、汽車之家、平安證券、喜馬拉雅等企業。
廣泛應用於實時數據湖的構建,幫助數據庫更好的 CDC 入湖,幫助構建近實時流式湖倉,幫助企業提升數據時效性價值,獲取業務實時化效果。
讀/寫:Paimon 支持多種讀/寫數據和執行 OLAP 查詢的方式
-
(1)對於讀取,它支持以下方式消費數據
從歷史快照(批處理模式)、從最新的偏移量(在流模式下),或以混合方式讀取增量快照。 -
(2)對於寫入,它支持來自數據庫變更日誌(CDC)的流式同步或來自離線數據的批量插入/覆蓋。
生態系統
- 除了Apache Flink之外,Paimon還支持Apache Hive、Apache Spark、Trino等其他計算引擎的讀取。
內部
- 在底層,Paimon 將列式文件存儲在文件系統/對象存儲上,並使用 LSM 樹結構來支持大量數據更新和高性能查詢。
統一存儲
- 對於 Apache Flink 這樣的流引擎,通常有三種類型的連接器:
- 消息隊列:例如 Apache Kafka,在源階段和中間階段都使用它,以保證延遲保持在秒級
- OLAP系統:例如Clickhouse,它以流方式接收處理後的數據併爲用戶的即席查詢提供服務
- 批量存儲:例如Apache Hive,它支持傳統批處理的各種操作,包括INSERT OVERWRITE
- Paimon 提供表抽象。它的使用方式與傳統數據庫沒有什麼區別:
- 在批處理執行模式下,它就像一個Hive表,支持Batch SQL的各種操作。查詢它以查看最新的快照。
- 在流執行模式下,它的作用就像一個消息隊列。查詢它的行爲就像從歷史數據永不過期的消息隊列中查詢流更改日誌。
2 核心特性
-
Apache Paimon 是一個湖格式,結合 Flink 及 Spark 構建流批處理的實時湖倉一體架構。Paimon 創新的結合湖格式與 LSM 技術,給數據湖帶來了實時流更新以及完整的流處理能力。
-
在過去的孵化期間,Paimon 通過技術創新不斷克服挑戰,展現出了以下關鍵特性:
● 實時入湖能力增強:Paimon 提供了一系列的入湖工具,自動同步 Schema 變更,允許快速將包括 MySQL 在內的多種數據庫系統的實時變化同步至數據湖,即便在千萬級數據規模下也能保持高效率與低延遲。
● 湖上批流一體處理:Paimon 結合 Flink 提供完整的流處理能力,結合 Spark 提供完整的批處理能力。基於統一的數據湖存儲,提供數據口徑一致的批流一體處理,提高易用性並降低成本。
● 全面生態集成拓展:Paimon 已經與衆多開源工具和技術棧緊密集成,支持大數據典型計算引擎,包括 Flink、Spark、Hive、Trino、Presto、StarRocks、Doris 等等,統一存儲,計算無邊界。
● 湖倉存儲格式革新:Paimon 持續創新,引入新功能,在流批技術處理的基礎上,提出 Deletion Vectors 和索引來增強查詢性能,在分鐘級時效性基礎上滿足流、批、OLAP 等場景的全方位支持。
- Apache Paimon 的畢業意味着該項目已經在社區治理、代碼質量、文檔完善度以及用戶採用度等方面達到了 Apache 社區嚴格的標準要求,得到了廣泛認可。這將進一步加速項目的普及與應用,推動實時數據湖技術在全球範圍內的廣泛應用。
1)統一批處理和流處理
批量寫入和讀取、流式更新、變更日誌生成,全部支持。
2)數據湖能力
低成本、高可靠性、可擴展的元數據。Apache Paimon 具有作爲數據湖存儲的所有優勢。
3)各種合併引擎
按照您喜歡的方式更新記錄。保留最後一條記錄、進行部分更新或將記錄聚合在一起,由您決定。
4)變更日誌生成
Apache Paimon 可以從任何數據源生成正確且完整的變更日誌,從而簡化您的流分析。
5)豐富的表類型
除了主鍵表之外,Apache Paimon還支持append-only表,提供有序的流式讀取來替代消息隊列。
6)模式演化
Apache Paimon 支持完整的模式演化。您可以重命名列並重新排序。
3 基本概念
Snapshot
快照捕獲表在某個時間點的狀態。用戶可以通過最新的快照來訪問表的最新數據。通過時間旅行,用戶還可以通過較早的快照訪問表的先前狀態。
Partition
-
Paimon 採用與 Apache Hive 相同的分區概念來分離數據。
-
分區是一種可選方法,可根據日期、城市和部門等特定列的值將表劃分爲相關部分。每個表可以有一個或多個分區鍵來標識特定分區。
-
通過分區,用戶可以高效地操作表中的一片記錄。
-
如果定義了主鍵,則分區鍵必須是主鍵的子集。
Bucket
-
未分區表或分區表中的分區被細分爲存儲桶,以便爲可用於更有效查詢的數據提供額外的結構。
-
桶的範圍由記錄中的一列或多列的哈希值確定。用戶可以通過提供bucket-key選項來指定分桶列。如果未指定bucket-key選項,則主鍵(如果已定義)或完整記錄將用作存儲桶鍵。
-
桶是讀寫的最小存儲單元,因此桶的數量限制了最大處理並行度。不過這個數字不應該太大,因爲它會導致大量小文件和低讀取性能。一般來說,建議每個桶的數據大小爲1GB左右。
Consistency Guarantees一致性保證
-
Paimon writer使用兩階段提交協議以原子方式將一批記錄提交到表中。每次提交在提交時最多生成兩個快照。
-
對於任意兩個同時修改表的writer,只要他們不修改同一個存儲桶,他們的提交都是可序列化的。如果他們修改同一個存儲桶,則僅保證快照隔離。也就是說,最終表狀態可能是兩次提交的混合,但不會丟失任何更改。
4 文件佈局
- 一張表的所有文件都存儲在一個基本目錄下。Paimon 文件以分層方式組織。下圖說明了文件佈局。從快照文件開始,Paimon 讀者可以遞歸地訪問表中的所有記錄。
下面簡單介紹文件佈局。
Snapshot Files
- 所有快照文件都存儲在快照目錄中。
- 快照文件是一個 JSON 文件,包含有關此快照的信息,包括:
- 正在使用的Schema文件
- 包含此快照的所有更改的清單列表(manifest list)
Manifest Files
- 所有清單列表(manifest list)和清單文件(manifest file)都存儲在清單(manifest)目錄中。
- 清單列表(manifest list)是清單文件名(manifest file)的列表。
- 清單文件(manifest file)是包含有關 LSM 數據文件和更改日誌文件的文件信息。例如對應快照中創建了哪個LSM數據文件、刪除了哪個文件。
Data Files
- 數據文件按分區和存儲桶分組。每個存儲桶目錄都包含一個 LSM 樹及其變更日誌文件。
- 目前,Paimon 支持使用 orc(默認)、parquet 和 avro 作爲數據文件格式。
LSM Trees
- Paimon 採用 LSM 樹(日誌結構合併樹)作爲文件存儲的數據結構。
Sorted Runs
-
LSM 樹將文件組織成多個Sorted Run。Sorted Run由一個或多個數據文件組成,並且每個數據文件恰好屬於一個Sorted Run。
-
數據文件中的記錄按其主鍵排序。在Sorted Run中,數據文件的主鍵範圍永遠不會重疊。
-
正如您所看到的,不同的Sorted Run可能具有重疊的主鍵範圍,甚至可能包含相同的主鍵。查詢LSM樹時,必須合併所有Sorted Run,並且必須根據用戶指定的合併引擎和每條記錄的時間戳來合併具有相同主鍵的所有記錄。
-
寫入LSM樹的新記錄:將首先緩存在內存中。當內存緩衝區滿時,內存中的所有記錄將被排序並刷新到磁盤。
Compaction
-
當越來越多的記錄寫入LSM樹時,Sorted Run的數量將會增加。由於查詢LSM樹需要將所有Sorted Run合併起來,太多Sorted Run將導致查詢性能較差,甚至內存不足。
-
爲了限制Sorted Run的數量,我們必須偶爾將多個Sorted Run合併爲一個大的Sorted Run。這個過程稱爲Compaction。
-
然而,Compaction是一個資源密集型過程,會消耗一定的CPU時間和磁盤IO,因此過於頻繁的Compaction可能會導致寫入速度變慢。這是查詢和寫入性能之間的權衡。Paimon 目前採用了類似於 Rocksdb 通用壓縮的Compaction策略。
-
默認情況下,當Paimon將記錄追加到LSM樹時,它也會根據需要執行Compaction。用戶還可以選擇在“專用Compaction作業”中獨立執行所有Compaction。
Y 進階: 集成Flink
寫入性能
- Paimon的寫入性能與檢查點密切相關。若需要更大的寫入吞吐量,則:
增加檢查點間隔,或者僅使用批處理模式。
增加寫入緩衝區大小。
啓用寫緩衝區溢出。
如果您使用固定存儲桶模式,請重新調整存儲桶數量。
- 並行度
建議sink的並行度小於等於bucket的數量,最好相等。
- Compaction
當Sorted Run數量較少時,Paimon writer 將在單獨的線程中異步執行壓縮,因此記錄可以連續寫入表中。然而,爲了避免Sorted Runs的無限增長,當Sorted Run的數量達到閾值時,writer將不得不暫停寫入。下表屬性確定閾值。
當 num-sorted-run.stop-trigger 變大時,寫入停頓將變得不那麼頻繁,從而提高寫入性能。但是,如果該值變得太大,則查詢表時將需要更多內存和 CPU 時間。如果您擔心內存 OOM,請配置sort-spill-threshold。它的值取決於你的內存大小。
- 優先考慮寫入吞吐量
如果希望某種模式具有最大寫入吞吐量,則可以緩慢而不是匆忙地進行Compaction。可以對錶使用以下策略
num-sorted-run.stop-trigger = 2147483647
sort-spill-threshold = 10
此配置將在寫入高峯期生成更多文件,並在寫入低谷期逐漸合併到最佳讀取性能。
- 觸發Compaction的Sorted Run數
- Paimon使用LSM樹,支持大量更新。LSM 在多次Sorted Runs中組織文件。從 LSM 樹查詢記錄時,必須組合所有Sorted Runs以生成所有記錄的完整視圖。
- 過多的Sorted Run會導致查詢性能不佳。爲了將Sorted Run的數量保持在合理的範圍內,Paimon writers 將自動執行Compaction。下表屬性確定觸發Compaction的最小Sorted Run數。
- 寫入初始化
- 在write初始化時,bucket的writer需要讀取所有歷史文件。如果這裏出現瓶頸(例如同時寫入大量分區),可以使用write-manifest-cache緩存讀取的manifest數據,以加速初始化。
- 內存
Paimon writer中主要佔用內存的地方有:
- Writer的內存緩衝區,由單個任務的所有Writer共享和搶佔。該內存值可以通過 write-buffer-size 表屬性進行調整。
- 合併多個Sorted Run以進行Compaction時會消耗內存。可以通過 num-sorted-run.compaction-trigger 選項進行調整,以更改要合併的Sorted Run的數量。
- 如果行非常大,在進行Compaction時一次讀取太多行數據可能會消耗大量內存。減少 read.batch-size 選項可以減輕這種情況的影響。
- 寫入列式(ORC、Parquet等)文件所消耗的內存,不可調。
讀取性能
Full Compaction
- 配置full-compaction.delta-commits在Flink寫入中定期執行full-compaction。並且可以確保在寫入結束之前分區被完全Compaction。
注意:Paimon 默認處理小文件並提供良好的讀取性能。請不要在沒有任何要求的情況下配置此Full Compaction選項,因爲它會對性能產生重大影響。
主鍵表
-
對於主鍵表來說,這是一種
MergeOnRead
技術。讀取數據時,會合並多層LSM數據,並行數會受到桶數的限制。雖然Paimon的merge會高效,但是還是趕不上普通的AppendOnly表。 -
如果你想在某些場景下查詢得足夠快,但只能找到較舊的數據,你可以:
- 配置full-compaction.delta-commits,寫入數據時(目前只有Flink)會定期進行full Compaction。
- 配置
scan.mode
爲compacted-full
,讀取數據時,選擇full-compaction的快照。讀取性能良好。
僅追加表
-
小文件會降低讀取速度並影響 DFS 穩定性。默認情況下,當單個存儲桶中的小文件超過“compaction.max.file-num”(默認50個)時,就會觸發compaction。但是當有多個桶時,就會產生很多小文件。
-
您可以使用full-compaction來減少小文件。full-compaction將消除大多數小文件。
格式
- Paimon 對 parquet 讀取進行了一些查詢優化,因此 parquet 會比 orc 稍快一些。
多Writer併發寫入
-
Paimon的快照管理支持向多個writer寫入。
-
默認情況下,Paimon支持對不同分區的併發寫入。推薦方式是
streaming job
將記錄寫入Paimon的最新分區;同時批處理作業
(覆蓋)將記錄寫入歷史分區。
- 如果需要多個Writer寫到同一個分區,事情就會變得有點複雜。例如,不想使用 UNION ALL,那就需要有多個流作業來寫入"partial-update"表。參考如下的"Dedicated Compaction Job"。
Dedicated Compaction Job
- 默認情況下,Paimon writer 在寫入記錄時會根據需要執行Compaction。這對於大多數用例來說已經足夠了,但有兩個缺點:
- 這可能會導致寫入吞吐量不穩定,因爲執行壓縮時吞吐量可能會暫時下降。
- Compaction會將某些數據文件標記爲“已刪除”(並未真正刪除)。如果多個writer標記同一個文件,則在提交更改時會發生衝突。Paimon 會自動解決衝突,但這可能會導致作業重新啓動。
- 爲了避免這些缺點,用戶還可以選擇在writer中跳過Compaction,並僅運行專門的作業來進行Compaction。由於Compaction僅由專用作業執行,因此writer可以連續寫入記錄而無需暫停,並且不會發生衝突。
Flink SQL
目前不支持compaction相關的語句,所以我們必須通過flink run
來提交compaction
作業。
<FLINK_HOME>/bin/flink run \
/path/to/paimon-flink-action-0.5-SNAPSHOT.jar \
compact \
–warehouse \
–database \
–table \
[–partition ] \
[–catalog-conf [–catalog-conf …]] \
- 如果提交一個批處理作業(execution.runtime-mode:batch),當前所有的表文件都會被Compaction。如果您提交一個流作業(execution.runtime-mode: Streaming),該作業將持續監視表的新更改並根據需要執行Compaction。
表管理
管理快照
1)快照過期
-
Paimon Writer每次提交都會生成一個或兩個快照。每個快照可能會添加一些新的數據文件或將一些舊的數據文件標記爲已刪除。然而,標記的數據文件並沒有真正被刪除,因爲Paimon還支持時間旅行到更早的快照。它們僅在快照過期時被刪除。
-
目前,Paimon Writer在提交新更改時會自動執行過期操作。通過使舊快照過期,可以刪除不再使用的舊數據文件和元數據文件,以釋放磁盤空間。
-
設置以下表屬性:
-
注意,保留時間太短或保留數量太少可能會導致如下問題:
- 批量查詢找不到該文件。例如,表比較大,批量查詢需要10分鐘才能讀取,但是10分鐘前的快照過期了,此時批量查詢會讀取到已刪除的快照。
- 表文件上的流式讀取作業(沒有外部日誌系統)無法重新啓動。當作業重新啓動時,它記錄的快照可能已過期。(可以使用Consumer Id來保護快照過期的小保留時間內的流式讀取)。
2)回滾快照
<FLINK_HOME>/bin/flink run \
/path/to/paimon-flink-action-0.5-SNAPSHOT.jar \
rollback-to \
–warehouse \
–database \
–table \
–snapshot \
[–catalog-conf [–catalog-conf …]]
管理分區
-
創建分區表時可以設置partition.expiration-time。Paimon會定期檢查分區的狀態,並根據時間刪除過期的分區。
-
判斷分區是否過期:將分區中提取的時間與當前時間進行比較,看生存時間是否超過partition.expiration-time。比如:
CREATE TABLE T (…) PARTITIONED BY (dt) WITH (
'partition.expiration-time' = '7 d',
'partition.expiration-check-interval' = '1 d',
'partition.timestamp-formatter' = 'yyyyMMdd'
);
管理小文件
小文件可能會導致:
- 穩定性問題:HDFS中小文件過多,NameNode會承受過大的壓力。
- 成本問題:HDFS中的小文件會暫時使用最小1個Block的大小,例如128MB。
- 查詢效率:小文件過多查詢效率會受到影響。
1)Flink Checkpoint的影響
-
使用Flink Writer,每個checkpoint會生成 1-2 個快照,並且checkpoint會強制在 DFS 上生成文件,因此checkpoint間隔越小,會生成越多的小文件。
-
默認情況下,不僅checkpoint會導致文件生成,writer的內存(write-buffer-size)耗盡也會將數據flush到DFS並生成相應的文件。可以啓用 write-buffer-spillable 在 writer 中生成溢出文件,從而在 DFS 中生成更大的文件。
所以,可以設置如下:
增大checkpoint間隔
增加 write-buffer-size 或啓用 write-buffer-spillable
2)快照的影響
-
Paimon維護文件的多個版本,文件的Compaction和刪除是邏輯上的,並沒有真正刪除文件。文件只有在 Snapshot 過期後纔會被真正刪除,因此減少文件的第一個方法就是減少 Snapshot 過期的時間。Flink writer 會自動使快照過期。
-
分區和分桶的影響
表數據會被物理分片到不同的分區,裏面有不同的桶,所以如果整體數據量太小,單個桶中至少有一個文件,建議你配置較少的桶數,否則會出現也有很多小文件。
3)主鍵表LSM的影響
- LSM 樹將文件組織成Sorted Runs的運行。Sorted Runs由一個或多個數據文件組成,並且每個數據文件恰好屬於一個Sorted Runs。
- 默認情況下,Sorted Runs數取決於 num-sorted-run.compaction-trigger,這意味着一個桶中至少有 5 個文件。如果要減少此數量,可以保留更少的文件,但寫入性能可能會受到影響。
4)僅追加表的文件的影響
默認情況下,Append-Only 還會進行自動Compaction以減少小文件的數量
對於分桶的 Append-only 表,爲了排序會對bucket內的文件行Compaction,可能會保留更多的小文件。
5)Full-Compaction的影響
主鍵表是5個文件,但是Append-Only表(桶)可能單個桶裏有50個小文件,這是很難接受的。更糟糕的是,不再活動的分區還保留了如此多的小文件。
建議配置Full-Compaction,在Flink寫入時配置‘full-compaction.delta-commits’定期進行full-compaction。並且可以確保在寫入結束之前分區被full-compaction。
G 進階: 縮放Bucket
1)說明
- 由於總桶數對性能影響很大,Paimon 允許用戶通過 ALTER TABLE 命令調整桶數,並通過 INSERT OVERWRITE 重新組織數據佈局,而無需重新創建表/分區。當執行覆蓋作業時,框架會自動掃描舊桶號的數據,並根據當前桶號對記錄進行哈希處理。
– rescale number of total buckets
ALTER TABLE table_identifier SET (‘bucket’ = ‘…’)
– reorganize data layout of table/partition
INSERT OVERWRITE table_identifier [PARTITION (part_spec)]
SELECT …
FROM table_identifier
[WHERE part_spec]
注意:
ALTER TABLE 僅修改表的元數據,不會重新組織或重新格式化現有數據。重新組織現有數據必須通過INSERT OVERWRITE
來實現。
重新縮放桶數不會影響讀取和正在運行的寫入作業。
一旦存儲桶編號更改,任何新安排的 INSERT INTO
作業寫入未重新組織的現有表/分區將拋出 TableException
,並顯示如下類似異常:
Try to write table/partition … with a new bucket num …,
but the previous bucket num is … Please switch to batch mode,
and perform INSERT OVERWRITE to rescale current data layout first.
- 對於分區表,不同的分區可以有不同的桶號。例如:
ALTER TABLE my_table SET ('bucket' = '4');
INSERT OVERWRITE my_table PARTITION (dt = '2022-01-01')
SELECT * FROM …;
ALTER TABLE my_table SET ('bucket' = '8');
INSERT OVERWRITE my_table PARTITION (dt = '2022-01-02')
SELECT * FROM …;
在覆蓋期間,確保沒有其他作業寫入同一表/分區。
注意:對於啓用日誌系統的表(例如Kafka),請重新調整主題的分區以保持一致性。
重新縮放存儲桶有助於處理吞吐量的突然峯值。假設有一個每日流式ETL任務來同步交易數據。該表的DDL和管道如下所示。
2)官方示例
如下是正在跑的一個作業:
建表
CREATE TABLE verified_orders (
trade_order_id BIGINT,
item_id BIGINT,
item_price DOUBLE,
dt STRING,
PRIMARY KEY (dt, trade_order_id, item_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
'bucket' = '16'
);
kafka表
CREATE temporary TABLE raw_orders(
trade_order_id BIGINT,
item_id BIGINT,
item_price BIGINT,
gmt_create STRING,
order_status STRING
) WITH (
'connector' = 'kafka',
'topic' = '…',
'properties.bootstrap.servers' = '…',
'format' = 'csv'
…
);
流式插入16個分桶
INSERT INTO verified_orders
SELECT trade_order_id,
item_id,
item_price,
DATE_FORMAT(gmt_create, 'yyyy-MM-dd') AS dt
FROM raw_orders
WHERE order_status = 'verified';
過去幾周運行良好。然而,最近數據量增長很快,作業的延遲不斷增加。爲了提高數據新鮮度,用戶可以執行如下操作縮放分桶:
(1)使用保存點暫停流作業
$ ./bin/flink stop \
–savepointPath /tmp/flink-savepoints \
$JOB_ID
(2)增加桶數
ALTER TABLE verified_orders SET ('bucket' = '32');
(3)切換到批處理模式並覆蓋流作業正在寫入的當前分區
SET 'execution.runtime-mode' = 'batch';
– 假設今天是2022-06-22
- 情況1:沒有更新歷史分區的延遲事件,因此覆蓋今天的分區就足夠了
INSERT OVERWRITE verified_orders PARTITION (dt = '2022-06-22')
SELECT trade_order_id,
item_id,
item_price
FROM verified_orders
WHERE dt = '2022-06-22';
- 情況2:有更新歷史分區的延遲事件,但範圍不超過3天
INSERT OVERWRITE verified_orders
SELECT trade_order_id,
item_id,
item_price,
dt
FROM verified_orders
WHERE dt IN ('2022-06-20', '2022-06-21', '2022-06-22');
(4)覆蓋作業完成後,切換回流模式,從保存點恢復(可以增加並行度=新bucket數量)。
SET 'execution.runtime-mode' = 'streaming';
SET 'execution.savepoint.path' = ;
INSERT INTO verified_orders
SELECT trade_order_id,
item_id,
item_price,
DATE_FORMAT(gmt_create, 'yyyy-MM-dd') AS dt
FROM raw_orders
WHERE order_status = 'verified';
X 參考文獻
- Apache Paimon
- Apache Sea Tunnel