拓冰建站拓冰建站
首页 / 资讯中心 / 正文

【Paimon 学习笔记 一】从 Hive 到Paimon: 大数据表发展历程

Flink SQL 系列里订单表、城市维表、商品维表、订单宽表全都建在 Paimon 上——但一直只管用没开过盒。这个系列把盒子打开共四篇前世今生本篇Hive 的工作方式、三个坎、数据湖三剑客Hudi/Iceberg/Delta Lake、Paimon 为什么出现核心架构快照 LSM 树数据在磁盘上到底长什么样工作流程Flink 流写、changelog 流读、Spark 批读怎么配合外部存储怎么落地生产实战订单表选型、分区与分桶、快照过期与回滚、常见坑第一篇先解决为什么。要理解 Paimon 出现的意义得先回到它出现之前——Hive 是怎么干活的。零 Paimon 出现之前Hive 是怎么干活的Hive 的本质一句话给 HDFS 上的文件套一层表的壳。先把壳下面的两个地基名词拆开HDFSHadoop 的分布式文件系统——把集群里一堆机器的磁盘拼成一块大硬盘。文件被切成块block分散存储、多副本容错NameNode 统一管理文件 → 块 → 机器的映射。对上层它就是一棵普通的路径树但每个文件、每个块都占 NameNode 的内存——文件一多它先扛不住Parquet / ORC数据文件本身的格式都是列式存储——同一列的数据连续存放查询只读用到的列配合压缩编码比 CSV/JSON 这类行式格式省空间、读得快。从 Hive 到三剑客到 Paimon底层数据文件几乎都是它俩Paimon 默认 Parquet。顺带分清Presto / Trino 是查询引擎不是文件格式然后才是 Hive 的壳一张 Hive 表 HDFS 上的一个目录数据 目录下的文件Parquet/ORC分区 子目录/warehouse/orders/dt2026-08-29/hour10/——分区值就是路径名dt、hour 甚至不是真正的字段表结构、分区清单存在 metastore元数据服务里查询引擎先问 metastore 要分区清单再去目录读文件批时代的经典节奏T1凌晨上游把昨天的数据写成文件放进dt昨天的目录然后执行ADD PARTITION把分区注册到 metastore——这个动作等于宣布这批数据齐了下游任务看到分区出现才敢开跑。关键在于Hive 自己并不知道数据齐没齐。目录就是个目录文件随时可以增删覆盖没有版本、没有提交记录。分区注册了才算齐不是 Hive 提供的机制而是上下游之间的一条人为约定。约定之所以落在分区上是因为批表本来就离不开分区查询按分区裁剪、重跑按分区覆盖、过期按分区删除——分区目录就是一批数据的天然边界而 metastore未注册的分区不参与查询这个特性正好被借来当就绪开关。Hive 并不强制分区但不分区反而更糟文件一出现在表目录就立刻被读到写了一半的数据直接暴露连隔离写中数据的地方都没有。用后来的概念说这条约定的软肋是Hive 表没有快照。快照指的不是表结构schema而是数据内容的版本metastore 只记录表现在有哪些分区不记录某个时刻表由哪些文件组成、内容是什么。所以没有任何办法指认我要读 10:00 那一刻的表——目录永远只呈现现在的样子写一半的文件会被读到被覆盖的历史找不回。批时代一天交接一次约定够用等流来了这条约定就开始吃紧。一 流来了三个绕不开的坎沿用 Flink 系列的订单链路订单从 Kafka 实时进来Flink SQL 算出分钟级结果下游 Spark 做小时级汇总。链路前两环都是分钟级——如果存储用 Hive最后一公里会撞上三个坎。1 可见性小时级齐没齐靠分区注册约定。T1 时一天注册一次没问题流式想分钟级可见就得每分钟注册一个分钟级分区——分区数量爆炸metastore 先扛不住。妥协的结果分区粒度放大到小时订单 10:00 到、11:00 才可见实时链路在这一环退化成小时级。2 更新代价过大订单状态会变待支付 → 已支付 → 已退款。先说清楚这里要解决的是数据层面的改——纯文件操作metastore 不参与、也不知情它只记分区目录在哪、不管目录里文件的内容表结构变更则是另一条独立的路ALTER TABLE原地改元数据不动任何文件两条路都没有版本。文件目录里改一条记录不是不能做是代价太大找到对应文件、重写整个文件甚至整个分区——成本跟着分区大小走而不是跟着改动量走偶尔做一次可以每分钟做就是 I/O 灾难。Hive 3 后来也补了 ACID 表base delta 文件、后台合并支持 UPDATE——说明湖上更新确实是刚需但它绑定 Hive 引擎Spark、Flink 不支持出了 Hive 生态就用不上。工程上只能绕开同一个 order_id 再写一条、下游自己去重或者 T1 拉链表——都跟实时没关系了。3 小文件越积越多每次写入都产生新文件。批时代一天几个文件无所谓流式每分钟落一批一天上千个小文件。NameNode 元数据压力、查询 split 开销全都来了只能再跑合并任务救场。三个坎指向同一件事Hive 是批时代的文件目录——没有快照、没有 ACID、没有流读概念。它不是不好是设计的年代还没有流这个需求。二 数据湖三剑客补上版本这一层2016–2019 年Hudi、Iceberg、Delta Lake 先后出现——三者后来被社区合称数据湖三剑客“数据湖格式”Table Format这个品类也就此成形。它们做的第一件事就是补上 Hive 缺的版本。这里先分清两种版本后文会反复用到数据版本快照表的内容在某个时刻的样子——这个版本包含哪些文件、内容是什么schema 版本表结构在某个时刻的样子——有哪些字段、什么类型Hive 是两者都没有只有当前一份三剑客两者都补但力度不同schema 版本 Iceberg 做得最完整。还有一个维度要和版本管理分开看——行级更新upsert。版本管理是表级的时光倒流把整张表拨回某个时刻upsert 是行级的改一条记录。四家都有表级版本管理快照差别在 upsert 的落地方式——这才是分水岭。后面每个剑客都会说清版本管理靠什么、upsert 靠什么。围绕数据版本还要认识一个新动作——提交commit写入方把一批新文件作为一个整体登记成一个新版本要么全登记、要么不登记原子性。每次提交产生一个版本号快照指向一份文件清单。读者指认版本不再靠目录约定——写一半的数据永远不会被登记、也就永远不会被读到ACID 和时间旅行都由此而来。1 Hudi2016Uber为更新而生Uber 的场景打车行程的状态不停变化接单 → 进行中 → 完成订单类数据要分钟级入湖、还要能改。Hive 改不了Hudi 就为此而生——upsert 是一等公民。版本管理靠timeline 时间线每次提交在 timeline 上记一个 instant能做时间旅行和回滚——但版本管理不是 Hudi 的卖点它的设计重点在 upsert。核心是两种组织更新的方式这套读写权衡思路后来影响了所有湖格式COWCopy On Write写时复制更新时把涉及的数据文件整个重写一遍。写放大、读轻快——适合读多写少MORMerge On Read读时合并更新先追加到增量日志文件查询时再把日志和基础文件合并。写轻快、读有开销——适合写多读少还要定时跑 compaction 把合并做掉留下的坑在流读——准确说是流读的用途不同。增量拉取incremental pull拿到的是两个提交之间变化的行更新只有新状态没有操作类型和旧值。这套设计服务的是增量同步下游按主键重新 upsert 进自己的表Uber行程表 → 派生表的 ETL 这么用完全够。但 Flink 流计算要的不是变化的行而是计算 changelogI 新增 / -U 撤回旧值 / U 新值 / -D 删除——差别很实在下游按城市求和订单从北京改到上海只给新值就是上海加了、北京没扣的双计给动作-U 北京、U 上海才能算对。需要说明的是Hudi 0.132023补了 CDC 模式可选记录操作类型和旧值但属于主写路径之外要额外开启的增强不是默认产物。此外 compaction、cleaner 一堆后台任务要配要管概念多、门槛高。四种读写模式拉出来看模式支持靠什么批写✓ 原生Spark 批作业写 COW / MOR批读✓ 原生快照读MOR 有读优化视图流写△DeltaStreamer / Flink writer微批攒增量文件timeline 提交流读△增量拉取默认只有新状态CDC 模式可选0.132 Iceberg2017Netflix为可靠批查询而生Netflix 的场景PB 级日志数据Hive 表的痛点在查询侧——列分区目录慢、查询必须手写分区条件、改 schema 心惊胆战。Iceberg 的思路是把表的定义从目录变成快照清单快照 manifest 清单每次提交产生一个快照快照指向一组 manifest 清单文件清单再指向数据文件。查询时读清单拿文件不再列目录——快且可靠隐藏分区分区规则比如按天取 order_time记在元数据里查询直接写where order_time 2026-08-29 00:00:00引擎自动跳过无关分区——不用关心分区字段叫什么、目录长什么样。对比 Hive 的硬分区想按天分区得自己建一个dt字段、写入时手动提取日期填进去、查询时还得记住写where dt 2026-08-29——分区字段和原始字段是两个东西用户要记。Iceberg 把这层翻译藏进了元数据所以叫隐藏schema 演进每次变更记一个 schema 版本——加列改列直接生效老文件按写入时的 schema 读不用重写历史数据对比 HiveALTER TABLE 是原地改 metastore旧表结构直接消失、不能回滚不带 CASCADE 还会造成新老分区结构不一致多引擎支持是三剑客里最广的今天已是批式湖仓的事实标准。这一点是湖格式的核心价值值得展开一份数据多种引擎。表格式是开放规范——元数据快照 manifest就放在存储上、格式公开任何引擎实现自己的 reader 就能读同一张表数据不用搬家重量级批处理用Spark分析师 ad-hoc 取数用Trino / Presto交互式查询引擎写 SQL 秒级出结果流式读写用Flink。Iceberg 规范清晰、社区中立、不绑引擎厂商各家 connector 往往优先支持它对比之下Hudi 早期绑 Spark 较重Delta 绑 Databricks 生态。留下的坑流式更新和流读不是设计重点——CDC 数据入湖后下游想以 changelog 形式接着流很困难。upsert 虽然能做MERGE INTO但机制是写独立的delete file标记旧文件里哪几行作废新行写新文件——读取时合并 base delete类似 Hudi MOR 的读时合并。能做但不是为高频更新设计的四种读写模式拉出来看模式支持靠什么批写✓ 原生Spark / Flink 批写、MERGE INTO批读✓ 最强manifest 清单 隐藏分区多引擎最广流写△Flink sink 官方支持compaction 等维护动作要自己跑流读✗只有快照间差异更新、删除不产生 -U/U3 Delta Lake2019DatabricksSpark 生态的批流一体Databricks 是 Spark 背后的公司Delta Lake 是它在 Spark 生态里交出的答案表的根目录放一个事务日志_delta_log每次提交追加一条记录——“这次新增了哪些文件、删除了哪些文件”。ACID、快照、时间旅行都从这个日志推导出来。版本管理的机制很明确_delta_log 里每个 JSON 文件就是一个版本v0、v1、v2…表在第 N 版长什么样 把 v0 到 vN 的 add/remove 重放一遍的净结果时间旅行 重放到旧版本停回滚 把当前指针拨回旧版本。和 Iceberg 的区别是Iceberg 的快照直接指向一组 manifest拿现成清单Delta 的快照要重放日志推导出文件列表每 10 个提交做一次 checkpoint 压缩状态不用每次从头放。upsertMERGE INTO的机制是找到包含目标行的文件整个重写日志里记一笔旧文件 remove 新文件 add——和 Iceberg 的 delete file 不同Delta 不做行级标记粒度是文件级。最大卖点是和 Structured Streaming 无缝同一张表既可以批读批写也可以直接当流式 source/sink流批同一套 API。但注意这套流的成色Structured Streaming 是微批执行——按触发间隔跑一次小批量作业攒批攒在执行层它的连续处理模式一直是实验特性支持的算子很少没成熟过不是 Flink 那种逐条到达、逐条处理的原生流。留下的坑也在这里深度绑定 Spark——Flink 读写要靠社区项目功能总是慢半拍不在 Spark 生态里的团队用不顺。四种读写模式拉出来看模式支持靠什么批写✓ 原生Spark 批写、MERGE INTO批读✓Spark 原生其他引擎靠 connector流写✓ 仅 SparkStructured Streaming sink——执行是微批流读△ 仅 Sparkstreaming source / Change Data Feed出 Spark 就没了共性结论三者都补上了快照但骨子里仍是批优先的设计——流数据进来攒成批再落盘更新、流读是后来补的能力。对Flink 已经算出了 changelog存储能不能直接接住这个问题三剑客的答案都不算顺。三 Paimon把设计起点反过来2022 年Flink 社区启动 Flink Table StoreFTS子项目目标很直接给 Flink SQL 配一个原生的流式存储。2023 年进入 Apache 孵化器并更名 Paimon2024 年毕业成为顶级项目。它的设计起点与三剑客相反——先解决流再兼顾批。Paimon 站在三剑客的肩膀上把他们验证过的好设计各取所长从 Iceberg 学来快照 manifest 的批视角。每次提交生成一个快照快照直接指向 manifest 清单不用像 Delta 那样重放日志推导——Spark 批读、时间旅行、回滚都基于它。schema 演进、隐藏分区也一并继承。从 Hudi 学来读写权衡的思路但不用二选一。Hudi 的 COW/MOR 是代价放写端还是读端的选择题Paimon 用LSM 树HBase/RocksDB 同款思想把这道题变成了架构题——写入先进内存、批量刷成有序文件后台自动合并。同一个主键写 100 次就有 100 个版本散在不同层级的文件里查询时按主键 merge 取最新compaction 后台清理旧版本。追加本身就是更新写端天然轻只追加读端靠 merge不用像 Hudi 那样 COW/MOR 二选一也不用像 Iceberg 那样维护独立的 delete file。从 Delta 学来事务日志的原子性但实现更直接。Delta 的原子提交靠重放日志推导这次 add 了哪些文件Paimon 的快照本身就是原子单位——提交成功快照可见提交失败快照不存在中间态读者永远看不到。自己补的两块changelog 流读主键表的每次更新都能以 changelog 形式I/-U/U/-D流出——这正是 Flink 系列里 Temporal Join 维表按事件时间翻版本的来源。Hudi 要额外开 CDC 模式才有的能力在 Paimon 是存储层的默认产物与 Flink 同一个心跳写入按 Checkpoint 提交Flink 第四篇的伏笔在这里回收故障恢复天然 exactly-once三剑客和 Flink 的集成都是后补的 connectorPaimon 是从设计起点就长在 Flink 里的同样把四种模式拉出来模式支持靠什么批写✓insert overwrite / 批式写入批读✓快照读、时间旅行、回滚流写✓ 原生Flink sink写入按 Checkpoint 提交流读✓ 原生changelogI/-U/U/-D——Temporal Join 维表靠它翻版本三剑客是批 ✓✓、流 ✗/△这里四个格子全 ✓——设计起点反过来在读写模式上的直接体现。一句话总结三剑客与 Paimon 的区别三剑客是批存储上补流的能力Paimon 是流存储上补批的能力——设计起点相反顺手的场景也就相反。把五者放在一起对比起点的差别一目了然✓ 支持好 △ 有但弱 ✗ 没有能力HiveHudiIcebergDelta LakePaimon快照数据版本✗ 靠分区约定✓ timeline 时间线✓ manifest 清单✓ _delta_log 日志✓ snapshot 文件schema 版本✗ 只有当前 DDL✓ 随提交记录✓ 最完整✓ 随日志记录✓ schema 带版本行级更新upsert✗✓ 一等公民△ 有但非重点✓ MERGE INTO✓ 原生LSMchangelog 流读✗△ 默认差异文件CDC 可选✗△ 仅 Spark 内✓ 原生I/-U/U/-DFlink 集成△ 弱△ 后补✓ 较好✗ 靠社区✓ 原生多引擎批读✓ 最广✓✓ 最广△ Spark 为主△ 完善中设计起点批批后补更新批后补可靠性批Spark 流批流兼顾批四 回到订单链路Paimon 补上了哪三块三个坎对应三个解法Hive 的坎Paimon 的解法小时级可见写入按 Checkpoint 提交分钟级可见更新代价过大主键表原生 upsert订单状态直接改小文件堆积LSM 后台 compaction 自动合并数据文件本身落在外部存储HDFS / OSSFlink 负责流写、Spark 负责批处理共用同一份数据——这正是湖仓的协作方式第三篇会完整展开这条链路。边界也要说清不神化分钟级 ≠ 毫秒级可见性仍受 Checkpoint 间隔限制毫秒级点查是 Doris/StarRocks 的活Paimon 不抢compaction 有代价后台合并带来写放大极端写入压力下要盯第四篇展开生态年轻Spark/Trino/Hive 的支持在完善中引擎覆盖面还不如 Iceberg五 小结Hive 表 目录 文件 metastore数据齐了靠分区注册这条人为约定——没有快照是三个坎的总根子流来了之后可见性退化成小时级、更新代价过大只能追加绕开、小文件堆积三剑客补上了快照版本层但都是批优先设计流读和更新是后补的Paimon 反过来LSM 树 快照 changelog为流而生兼顾批它的定位是湖仓存储层流写、更新、快照归它毫秒查询不归它下一篇打开黑盒看内部快照 LSM 树Paimon 的数据在磁盘上到底长什么样——主键表和 append 表的区别、bucket 是什么、compaction 什么时候干活。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门