據(jù)表發(fā)展歷程)
Flink SQL 系列里訂單表、城市維表、商品維表、訂單寬表全都建在 Paimon 上——但一直只管用沒開過盒。這個系列把盒子打開共四篇前世今生本篇Hive 的工作方式、三個坎、數(shù)據(jù)湖三劍客Hudi/Iceberg/Delta Lake、Paimon 為什么出現(xiàn)核心架構(gòu)快照 LSM 樹數(shù)據(jù)在磁盤上到底長什么樣工作流程Flink 流寫、changelog 流讀、Spark 批讀怎么配合外部存儲怎么落地生產(chǎn)實戰(zhàn)訂單表選型、分區(qū)與分桶、快照過期與回滾、常見坑第一篇先解決為什么。要理解 Paimon 出現(xiàn)的意義得先回到它出現(xiàn)之前——Hive 是怎么干活的。零 Paimon 出現(xiàn)之前Hive 是怎么干活的Hive 的本質(zhì)一句話給 HDFS 上的文件套一層表的殼。先把殼下面的兩個地基名詞拆開HDFSHadoop 的分布式文件系統(tǒng)——把集群里一堆機器的磁盤拼成一塊大硬盤。文件被切成塊block分散存儲、多副本容錯NameNode 統(tǒng)一管理文件 → 塊 → 機器的映射。對上層它就是一棵普通的路徑樹但每個文件、每個塊都占 NameNode 的內(nèi)存——文件一多它先扛不住Parquet / ORC數(shù)據(jù)文件本身的格式都是列式存儲——同一列的數(shù)據(jù)連續(xù)存放查詢只讀用到的列配合壓縮編碼比 CSV/JSON 這類行式格式省空間、讀得快。從 Hive 到三劍客到 Paimon底層數(shù)據(jù)文件幾乎都是它倆Paimon 默認 Parquet。順帶分清Presto / Trino 是查詢引擎不是文件格式然后才是 Hive 的殼一張 Hive 表 HDFS 上的一個目錄數(shù)據(jù) 目錄下的文件Parquet/ORC分區(qū) 子目錄/warehouse/orders/dt2026-08-29/hour10/——分區(qū)值就是路徑名dt、hour 甚至不是真正的字段表結(jié)構(gòu)、分區(qū)清單存在 metastore元數(shù)據(jù)服務(wù)里查詢引擎先問 metastore 要分區(qū)清單再去目錄讀文件批時代的經(jīng)典節(jié)奏T1凌晨上游把昨天的數(shù)據(jù)寫成文件放進dt昨天的目錄然后執(zhí)行ADD PARTITION把分區(qū)注冊到 metastore——這個動作等于宣布這批數(shù)據(jù)齊了下游任務(wù)看到分區(qū)出現(xiàn)才敢開跑。關(guān)鍵在于Hive 自己并不知道數(shù)據(jù)齊沒齊。目錄就是個目錄文件隨時可以增刪覆蓋沒有版本、沒有提交記錄。分區(qū)注冊了才算齊不是 Hive 提供的機制而是上下游之間的一條人為約定。約定之所以落在分區(qū)上是因為批表本來就離不開分區(qū)查詢按分區(qū)裁剪、重跑按分區(qū)覆蓋、過期按分區(qū)刪除——分區(qū)目錄就是一批數(shù)據(jù)的天然邊界而 metastore未注冊的分區(qū)不參與查詢這個特性正好被借來當就緒開關(guān)。Hive 并不強制分區(qū)但不分區(qū)反而更糟文件一出現(xiàn)在表目錄就立刻被讀到寫了一半的數(shù)據(jù)直接暴露連隔離寫中數(shù)據(jù)的地方都沒有。用后來的概念說這條約定的軟肋是Hive 表沒有快照??煺罩傅牟皇潜斫Y(jié)構(gòu)schema而是數(shù)據(jù)內(nèi)容的版本metastore 只記錄表現(xiàn)在有哪些分區(qū)不記錄某個時刻表由哪些文件組成、內(nèi)容是什么。所以沒有任何辦法指認我要讀 10:00 那一刻的表——目錄永遠只呈現(xiàn)現(xiàn)在的樣子寫一半的文件會被讀到被覆蓋的歷史找不回。批時代一天交接一次約定夠用等流來了這條約定就開始吃緊。一 流來了三個繞不開的坎沿用 Flink 系列的訂單鏈路訂單從 Kafka 實時進來Flink SQL 算出分鐘級結(jié)果下游 Spark 做小時級匯總。鏈路前兩環(huán)都是分鐘級——如果存儲用 Hive最后一公里會撞上三個坎。1 可見性小時級齊沒齊靠分區(qū)注冊約定。T1 時一天注冊一次沒問題流式想分鐘級可見就得每分鐘注冊一個分鐘級分區(qū)——分區(qū)數(shù)量爆炸metastore 先扛不住。妥協(xié)的結(jié)果分區(qū)粒度放大到小時訂單 10:00 到、11:00 才可見實時鏈路在這一環(huán)退化成小時級。2 更新代價過大訂單狀態(tài)會變待支付 → 已支付 → 已退款。先說清楚這里要解決的是數(shù)據(jù)層面的改——純文件操作metastore 不參與、也不知情它只記分區(qū)目錄在哪、不管目錄里文件的內(nèi)容表結(jié)構(gòu)變更則是另一條獨立的路ALTER TABLE原地改元數(shù)據(jù)不動任何文件兩條路都沒有版本。文件目錄里改一條記錄不是不能做是代價太大找到對應(yīng)文件、重寫整個文件甚至整個分區(qū)——成本跟著分區(qū)大小走而不是跟著改動量走偶爾做一次可以每分鐘做就是 I/O 災(zāi)難。Hive 3 后來也補了 ACID 表base delta 文件、后臺合并支持 UPDATE——說明湖上更新確實是剛需但它綁定 Hive 引擎Spark、Flink 不支持出了 Hive 生態(tài)就用不上。工程上只能繞開同一個 order_id 再寫一條、下游自己去重或者 T1 拉鏈表——都跟實時沒關(guān)系了。3 小文件越積越多每次寫入都產(chǎn)生新文件。批時代一天幾個文件無所謂流式每分鐘落一批一天上千個小文件。NameNode 元數(shù)據(jù)壓力、查詢 split 開銷全都來了只能再跑合并任務(wù)救場。三個坎指向同一件事Hive 是批時代的文件目錄——沒有快照、沒有 ACID、沒有流讀概念。它不是不好是設(shè)計的年代還沒有流這個需求。二 數(shù)據(jù)湖三劍客補上版本這一層2016–2019 年Hudi、Iceberg、Delta Lake 先后出現(xiàn)——三者后來被社區(qū)合稱數(shù)據(jù)湖三劍客“數(shù)據(jù)湖格式”Table Format這個品類也就此成形。它們做的第一件事就是補上 Hive 缺的版本。這里先分清兩種版本后文會反復(fù)用到數(shù)據(jù)版本快照表的內(nèi)容在某個時刻的樣子——這個版本包含哪些文件、內(nèi)容是什么schema 版本表結(jié)構(gòu)在某個時刻的樣子——有哪些字段、什么類型Hive 是兩者都沒有只有當前一份三劍客兩者都補但力度不同schema 版本 Iceberg 做得最完整。還有一個維度要和版本管理分開看——行級更新upsert。版本管理是表級的時光倒流把整張表撥回某個時刻upsert 是行級的改一條記錄。四家都有表級版本管理快照差別在 upsert 的落地方式——這才是分水嶺。后面每個劍客都會說清版本管理靠什么、upsert 靠什么。圍繞數(shù)據(jù)版本還要認識一個新動作——提交commit寫入方把一批新文件作為一個整體登記成一個新版本要么全登記、要么不登記原子性。每次提交產(chǎn)生一個版本號快照指向一份文件清單。讀者指認版本不再靠目錄約定——寫一半的數(shù)據(jù)永遠不會被登記、也就永遠不會被讀到ACID 和時間旅行都由此而來。1 Hudi2016Uber為更新而生Uber 的場景打車行程的狀態(tài)不停變化接單 → 進行中 → 完成訂單類數(shù)據(jù)要分鐘級入湖、還要能改。Hive 改不了Hudi 就為此而生——upsert 是一等公民。版本管理靠timeline 時間線每次提交在 timeline 上記一個 instant能做時間旅行和回滾——但版本管理不是 Hudi 的賣點它的設(shè)計重點在 upsert。核心是兩種組織更新的方式這套讀寫權(quán)衡思路后來影響了所有湖格式COWCopy On Write寫時復(fù)制更新時把涉及的數(shù)據(jù)文件整個重寫一遍。寫放大、讀輕快——適合讀多寫少MORMerge On Read讀時合并更新先追加到增量日志文件查詢時再把日志和基礎(chǔ)文件合并。寫輕快、讀有開銷——適合寫多讀少還要定時跑 compaction 把合并做掉留下的坑在流讀——準確說是流讀的用途不同。增量拉取incremental pull拿到的是兩個提交之間變化的行更新只有新狀態(tài)沒有操作類型和舊值。這套設(shè)計服務(wù)的是增量同步下游按主鍵重新 upsert 進自己的表Uber行程表 → 派生表的 ETL 這么用完全夠。但 Flink 流計算要的不是變化的行而是計算 changelogI 新增 / -U 撤回舊值 / U 新值 / -D 刪除——差別很實在下游按城市求和訂單從北京改到上海只給新值就是上海加了、北京沒扣的雙計給動作-U 北京、U 上海才能算對。需要說明的是Hudi 0.132023補了 CDC 模式可選記錄操作類型和舊值但屬于主寫路徑之外要額外開啟的增強不是默認產(chǎn)物。此外 compaction、cleaner 一堆后臺任務(wù)要配要管概念多、門檻高。四種讀寫模式拉出來看模式支持靠什么批寫? 原生Spark 批作業(yè)寫 COW / MOR批讀? 原生快照讀MOR 有讀優(yōu)化視圖流寫△DeltaStreamer / Flink writer微批攢增量文件timeline 提交流讀△增量拉取默認只有新狀態(tài)CDC 模式可選0.132 Iceberg2017Netflix為可靠批查詢而生Netflix 的場景PB 級日志數(shù)據(jù)Hive 表的痛點在查詢側(cè)——列分區(qū)目錄慢、查詢必須手寫分區(qū)條件、改 schema 心驚膽戰(zhàn)。Iceberg 的思路是把表的定義從目錄變成快照清單快照 manifest 清單每次提交產(chǎn)生一個快照快照指向一組 manifest 清單文件清單再指向數(shù)據(jù)文件。查詢時讀清單拿文件不再列目錄——快且可靠隱藏分區(qū)分區(qū)規(guī)則比如按天取 order_time記在元數(shù)據(jù)里查詢直接寫where order_time 2026-08-29 00:00:00引擎自動跳過無關(guān)分區(qū)——不用關(guān)心分區(qū)字段叫什么、目錄長什么樣。對比 Hive 的硬分區(qū)想按天分區(qū)得自己建一個dt字段、寫入時手動提取日期填進去、查詢時還得記住寫where dt 2026-08-29——分區(qū)字段和原始字段是兩個東西用戶要記。Iceberg 把這層翻譯藏進了元數(shù)據(jù)所以叫隱藏schema 演進每次變更記一個 schema 版本——加列改列直接生效老文件按寫入時的 schema 讀不用重寫歷史數(shù)據(jù)對比 HiveALTER TABLE 是原地改 metastore舊表結(jié)構(gòu)直接消失、不能回滾不帶 CASCADE 還會造成新老分區(qū)結(jié)構(gòu)不一致多引擎支持是三劍客里最廣的今天已是批式湖倉的事實標準。這一點是湖格式的核心價值值得展開一份數(shù)據(jù)多種引擎。表格式是開放規(guī)范——元數(shù)據(jù)快照 manifest就放在存儲上、格式公開任何引擎實現(xiàn)自己的 reader 就能讀同一張表數(shù)據(jù)不用搬家重量級批處理用Spark分析師 ad-hoc 取數(shù)用Trino / Presto交互式查詢引擎寫 SQL 秒級出結(jié)果流式讀寫用Flink。Iceberg 規(guī)范清晰、社區(qū)中立、不綁引擎廠商各家 connector 往往優(yōu)先支持它對比之下Hudi 早期綁 Spark 較重Delta 綁 Databricks 生態(tài)。留下的坑流式更新和流讀不是設(shè)計重點——CDC 數(shù)據(jù)入湖后下游想以 changelog 形式接著流很困難。upsert 雖然能做MERGE INTO但機制是寫?yīng)毩⒌膁elete file標記舊文件里哪幾行作廢新行寫新文件——讀取時合并 base delete類似 Hudi MOR 的讀時合并。能做但不是為高頻更新設(shè)計的四種讀寫模式拉出來看模式支持靠什么批寫? 原生Spark / Flink 批寫、MERGE INTO批讀? 最強manifest 清單 隱藏分區(qū)多引擎最廣流寫△Flink sink 官方支持compaction 等維護動作要自己跑流讀?只有快照間差異更新、刪除不產(chǎn)生 -U/U3 Delta Lake2019DatabricksSpark 生態(tài)的批流一體Databricks 是 Spark 背后的公司Delta Lake 是它在 Spark 生態(tài)里交出的答案表的根目錄放一個事務(wù)日志_delta_log每次提交追加一條記錄——“這次新增了哪些文件、刪除了哪些文件”。ACID、快照、時間旅行都從這個日志推導(dǎo)出來。版本管理的機制很明確_delta_log 里每個 JSON 文件就是一個版本v0、v1、v2…表在第 N 版長什么樣 把 v0 到 vN 的 add/remove 重放一遍的凈結(jié)果時間旅行 重放到舊版本停回滾 把當前指針撥回舊版本。和 Iceberg 的區(qū)別是Iceberg 的快照直接指向一組 manifest拿現(xiàn)成清單Delta 的快照要重放日志推導(dǎo)出文件列表每 10 個提交做一次 checkpoint 壓縮狀態(tài)不用每次從頭放。upsertMERGE INTO的機制是找到包含目標行的文件整個重寫日志里記一筆舊文件 remove 新文件 add——和 Iceberg 的 delete file 不同Delta 不做行級標記粒度是文件級。最大賣點是和 Structured Streaming 無縫同一張表既可以批讀批寫也可以直接當流式 source/sink流批同一套 API。但注意這套流的成色Structured Streaming 是微批執(zhí)行——按觸發(fā)間隔跑一次小批量作業(yè)攢批攢在執(zhí)行層它的連續(xù)處理模式一直是實驗特性支持的算子很少沒成熟過不是 Flink 那種逐條到達、逐條處理的原生流。留下的坑也在這里深度綁定 Spark——Flink 讀寫要靠社區(qū)項目功能總是慢半拍不在 Spark 生態(tài)里的團隊用不順。四種讀寫模式拉出來看模式支持靠什么批寫? 原生Spark 批寫、MERGE INTO批讀?Spark 原生其他引擎靠 connector流寫? 僅 SparkStructured Streaming sink——執(zhí)行是微批流讀△ 僅 Sparkstreaming source / Change Data Feed出 Spark 就沒了共性結(jié)論三者都補上了快照但骨子里仍是批優(yōu)先的設(shè)計——流數(shù)據(jù)進來攢成批再落盤更新、流讀是后來補的能力。對Flink 已經(jīng)算出了 changelog存儲能不能直接接住這個問題三劍客的答案都不算順。三 Paimon把設(shè)計起點反過來2022 年Flink 社區(qū)啟動 Flink Table StoreFTS子項目目標很直接給 Flink SQL 配一個原生的流式存儲。2023 年進入 Apache 孵化器并更名 Paimon2024 年畢業(yè)成為頂級項目。它的設(shè)計起點與三劍客相反——先解決流再兼顧批。Paimon 站在三劍客的肩膀上把他們驗證過的好設(shè)計各取所長從 Iceberg 學來快照 manifest 的批視角。每次提交生成一個快照快照直接指向 manifest 清單不用像 Delta 那樣重放日志推導(dǎo)——Spark 批讀、時間旅行、回滾都基于它。schema 演進、隱藏分區(qū)也一并繼承。從 Hudi 學來讀寫權(quán)衡的思路但不用二選一。Hudi 的 COW/MOR 是代價放寫端還是讀端的選擇題Paimon 用LSM 樹HBase/RocksDB 同款思想把這道題變成了架構(gòu)題——寫入先進內(nèi)存、批量刷成有序文件后臺自動合并。同一個主鍵寫 100 次就有 100 個版本散在不同層級的文件里查詢時按主鍵 merge 取最新compaction 后臺清理舊版本。追加本身就是更新寫端天然輕只追加讀端靠 merge不用像 Hudi 那樣 COW/MOR 二選一也不用像 Iceberg 那樣維護獨立的 delete file。從 Delta 學來事務(wù)日志的原子性但實現(xiàn)更直接。Delta 的原子提交靠重放日志推導(dǎo)這次 add 了哪些文件Paimon 的快照本身就是原子單位——提交成功快照可見提交失敗快照不存在中間態(tài)讀者永遠看不到。自己補的兩塊changelog 流讀主鍵表的每次更新都能以 changelog 形式I/-U/U/-D流出——這正是 Flink 系列里 Temporal Join 維表按事件時間翻版本的來源。Hudi 要額外開 CDC 模式才有的能力在 Paimon 是存儲層的默認產(chǎn)物與 Flink 同一個心跳寫入按 Checkpoint 提交Flink 第四篇的伏筆在這里回收故障恢復(fù)天然 exactly-once三劍客和 Flink 的集成都是后補的 connectorPaimon 是從設(shè)計起點就長在 Flink 里的同樣把四種模式拉出來模式支持靠什么批寫?insert overwrite / 批式寫入批讀?快照讀、時間旅行、回滾流寫? 原生Flink sink寫入按 Checkpoint 提交流讀? 原生changelogI/-U/U/-D——Temporal Join 維表靠它翻版本三劍客是批 ??、流 ?/△這里四個格子全 ?——設(shè)計起點反過來在讀寫模式上的直接體現(xiàn)。一句話總結(jié)三劍客與 Paimon 的區(qū)別三劍客是批存儲上補流的能力Paimon 是流存儲上補批的能力——設(shè)計起點相反順手的場景也就相反。把五者放在一起對比起點的差別一目了然? 支持好 △ 有但弱 ? 沒有能力HiveHudiIcebergDelta LakePaimon快照數(shù)據(jù)版本? 靠分區(qū)約定? timeline 時間線? manifest 清單? _delta_log 日志? snapshot 文件schema 版本? 只有當前 DDL? 隨提交記錄? 最完整? 隨日志記錄? schema 帶版本行級更新upsert?? 一等公民△ 有但非重點? MERGE INTO? 原生LSMchangelog 流讀?△ 默認差異文件CDC 可選?△ 僅 Spark 內(nèi)? 原生I/-U/U/-DFlink 集成△ 弱△ 后補? 較好? 靠社區(qū)? 原生多引擎批讀? 最廣?? 最廣△ Spark 為主△ 完善中設(shè)計起點批批后補更新批后補可靠性批Spark 流批流兼顧批四 回到訂單鏈路Paimon 補上了哪三塊三個坎對應(yīng)三個解法Hive 的坎Paimon 的解法小時級可見寫入按 Checkpoint 提交分鐘級可見更新代價過大主鍵表原生 upsert訂單狀態(tài)直接改小文件堆積LSM 后臺 compaction 自動合并數(shù)據(jù)文件本身落在外部存儲HDFS / OSSFlink 負責流寫、Spark 負責批處理共用同一份數(shù)據(jù)——這正是湖倉的協(xié)作方式第三篇會完整展開這條鏈路。邊界也要說清不神化分鐘級 ≠ 毫秒級可見性仍受 Checkpoint 間隔限制毫秒級點查是 Doris/StarRocks 的活Paimon 不搶compaction 有代價后臺合并帶來寫放大極端寫入壓力下要盯第四篇展開生態(tài)年輕Spark/Trino/Hive 的支持在完善中引擎覆蓋面還不如 Iceberg五 小結(jié)Hive 表 目錄 文件 metastore數(shù)據(jù)齊了靠分區(qū)注冊這條人為約定——沒有快照是三個坎的總根子流來了之后可見性退化成小時級、更新代價過大只能追加繞開、小文件堆積三劍客補上了快照版本層但都是批優(yōu)先設(shè)計流讀和更新是后補的Paimon 反過來LSM 樹 快照 changelog為流而生兼顧批它的定位是湖倉存儲層流寫、更新、快照歸它毫秒查詢不歸它下一篇打開黑盒看內(nèi)部快照 LSM 樹Paimon 的數(shù)據(jù)在磁盤上到底長什么樣——主鍵表和 append 表的區(qū)別、bucket 是什么、compaction 什么時候干活。