Apache Paimon 湖仓一体架构深度拆解:存储、表格式与计算引擎如何协作

发布时间:2026/10/22 13:38:22
Apache Paimon 湖仓一体架构深度拆解:存储、表格式与计算引擎如何协作 Apache Paimon 的湖仓一体架构图最近在数据工程圈里出现的频率越来越高。很多人把图画得像科技展板一样漂亮但问到“为什么这个框放在这里那条线为什么会存在”时很少有人能讲清楚。我自己过去一年在某数据团队围绕 Paimon 搭架构踩了不少坑也想趁这个机会把一张典型的架构图彻底拆开底层存储、表格式服务、计算引擎、数据管道和下游应用之间到底是怎么协作的。看完你会明白这张图不是技术栈的简单拼凑每一层都在解决一个明确问题也都能对应到具体的参数配置和 SQL 操作。1. 为什么是 Paimon而不是其他表格式1.1 数仓与数据湖的边界痛点传统数仓做报表和 BI 很好用但扩展成本高数据模型僵化想接半结构化数据、想支持机器学习特征探索都很痛苦。数据湖反过来存储成本低、格式开放可早期的数据湖基本没有事务能力不支持主键更新多份任务同时写一张表很容易读到中间状态。更麻烦的是流批分离实时链路写一套消息或状态存储离线链路写一套 Hive 分区表两套数据口径经常对不上。湖仓一体的核心诉求就是在类似数据湖的低成本存储上提供数仓级的一致性、更新能力和查询性能。Apache Paimon 正是奔着这个诉求来的。它把文件格式、元数据管理和快照机制做成一个统一的“表格式服务”让同一份数据既能支撑流式写入和流式读取也能支撑批量覆盖和普通 SQL 查询。实际上 Paimon 并不是一套需要单独部署的存储服务更像是一个标准化的表格实现。你建一张 Paimon 表数据最终落在对象存储或 HDFS 上但表的 schema、分桶、快照、变更日志都由 Paimon 管理。这样一来Flink、Spark、Hive 都只是这张表的客户端大家读写同一份文件却各取所需。1.2 Paimon 和其他表格式的取舍很多人会拿 Paimon 和 Iceberg、Hudi 做对比。其实架构选型不需要追求“最强”只需要在具体场景下最顺手。Paimon 比较有特点的地方有三个一是底层用 LSM 结构组织主键表高频更新和删除效率更稳定二是天生和 Flink 深度绑定流式读写、CDC 入湖、changelog 流读都做得非常顺三是文件合并、快照过期、小文件治理这些日常运维机制内置得比较完整。能力维度PaimonIcebergHudi主键表更新内置 LSM 合并需要借助 MOR 或外部处理内置但配置偏多流式读取变更日志原生支持不擅长支持但依赖表类型Flink 集成深度非常紧密一般可用小文件自动治理有 compaction 机制需要额外处理有但运维复杂学习成本中等中等偏高所以在我画的架构图里Paimon 不是“其中一个组件”而是整个湖仓底座的位置。只要主键更新和流批同源是硬需求Paimon 比其他格式更适合放在这个位置上。2. 一张典型架构图的逐层拆解2.1 存储层数据落在哪里文件怎么摆从下往上看架构图最底部永远是存储层。Paimon 支持本地文件系统、HDFS 和各类 S3 协议的对象存储实际生产里大部分团队选对象存储因为成本低、扩容简单。建一个 catalog 时指定的 warehouse 路径就是这个湖仓的根目录。在存储层上Paimon 表的目录结构大致是warehouse 下按库名、表名分目录表目录下再按分区、bucket 分目录。每个 bucket 内部是一系列列式数据文件比如 Parquet 或 ORC。快照清单文件manifest会记录每个 snapshot 对应哪些数据文件以及文件的一些统计信息比如分区范围、值域范围。这个设计的好处是读任务不需要扫描整个仓库只要拿到某个 snapshot 的 manifest就能只读取涉及的文件。架构图画存储层时通常会在对象存储上方标一个“Paimon 表目录”的框强调这里不是简单的文件堆放而是有结构、有索引、有快照语义的。2.2 表格式核心层Catalog、快照、Manifest架构图中间偏下会有一个比较大的框写着“Paimon 表格式服务”。墙面上的听众往往忽略这个框但它恰恰是最关键的部分。Paimon 的表能力由三部分支撑Catalog、Snapshot、Manifest。Catalog 负责库表元数据可以是文件系统 Catalog也可以放到 JDBC 里做统一管理。它会记录库表清单、表结构、表参数位置。Snapshot 是每次写入提交后产生的一个不可变视图类似数据湖的时间旅行版本。读者只要指定 snapshot-id就能读到某个历史时刻的数据不会看到后续未提交的写入。Manifest 是整个文件组织的地图。每次提交都会生成新的 manifest 列表记录本次提交涉及的数据文件、变更类型、统计信息。查询引擎通过读取 manifest 来确定扫描范围。这一层是 Paimon 能提供 ACID、流读、增量消费的根基也是架构图中“湖仓一体”四个字的重要支撑。2.3 计算引擎层Flink、Spark、Hive 各司其职计算引擎层在架构图里通常是中间一整排。最常见的组合是 Flink 负责实时写入和流式读取Spark 负责批量处理和离线分析Hive 或 Presto/Trino 负责即席查询和报表。箭头通常分两类一类是数据写入一类是数据读取。Flink 到 Paimon 的写入路径特别顺因为 Paimon 的 Flink connector 很成熟。上游 CDC 数据、Kafka 消息、业务日志都可以通过 Flink SQL 直接写入 Paimon 主键表。流读时Flink 可以像读消息队列一样消费 Paimon 表的新增和变更记录。Spark 则更偏向于跑大规模批任务比如全量回算历史数据、做复杂 join或者执行 schema 变更和 compaction。Hive 在一些存量团队里保留着查询入口Paimon 也支持 Hive 外表避免一次性迁移所有查询。理解这个分层的关键是所有这些引擎都不是常驻服务而是 Paimon 表的客户端。架构图上画的“三个引擎指向同一张表”本质是为了说明流批读写可以复用同一份数据文件。2.4 上游数据接入与下游应用架构图的左右两侧分别画上游和下游。上游通常是业务库、消息队列和 CDC 工具。以订单场景为例业务库的变更通过 CDC 接入 KafkaFlink 再从 Kafka 消费并写入 Paimon。这个路径既支持全量初始化也支持增量实时同步数据延迟基本可以做到秒级。下游的消费方就比较多样。最常见的是 OLAP 加速引擎和指标平台因为 Paimon 虽然在湖存储上做了一层表格式优化但要支撑亚秒级前端报表还是有点吃力。另一种是特征平台需要把主键表的最新状态变成一个可查询的特征快照Paimon 的查询能力正好胜任。还有一种是数据服务层通过统一 SQL 接口对外提供数据接口。我在画图时还会强调下游的 OLAP 引擎不是必选组件而是按查询延迟需求决定的。如果团队只有离线报表Spark 直接查 Paimon 就够了要是前端有交互式大屏加一层 OLAP 缓存会舒服很多。2.5 横向支撑层调度、权限、质量和监控一张成熟的架构图不能只有上下层还必须有横向贯穿的支撑体系。调度平台负责周期性任务比如定时合并小文件、刷新分区、清理过期快照。权限管理负责表级和库级访问控制在实际生产里通常通过计算引擎的权限体系实现。数据质量模块用来做主键唯一性检查、空值率监控、产出时延告警。这些在架构图上有时只是底部一条带子但上线后你会发现真正花时间维护的不是“写入链路”而是这些横向服务。Paimon 的小文件数量、snapshot 总量、compaction 作业健康度都需要纳入监控否则表会越跑越差。3. 架构图里不会明说但你必须懂的核心设计3.1 建表语句示例与表类型选型看架构图只能知道模块关系真正决定效果的是建表参数。我给一个典型的订单主键表做例子CREATE TABLE orders ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, ts TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( bucket 16, bucket-key order_id, file.format parquet, changelog-producer lookup, sequence.field ts, snapshot.time-retained 24 h );这里有几个参数非常关键。bucket决定数据分片数直接影响写入并行度和文件粒度。bucket-key一般和主键保持一致保证相同主键的更新落在同一个 bucket。file.format用 parquet 或 orc列式存储适合 OLAP 查询。changelog-producer决定 Paimon 是否能为流读生成完整的变更记录lookup是比较常见的选择。sequence.field则用来处理乱序数据让较新的记录覆盖较旧的记录。Paimon 表类型不只有主键表还有聚合表和变更日志表。聚合表适合做实时指标预聚合比如把点击流按维度汇总变更日志表适合需要完整历史变更记录的场景比如广播维表。选型时不要一上来就建主键表先想清楚下游到底需要“最新状态”还是“每一条变更”。表类型适用场景核心特点主键表维表、事实表更新、CDC 同步按主键合并支持更新删除聚合表实时汇总、指标累计按聚合函数合并不需要保留明细变更日志表维表广播、事件回溯保留完整 insert/update/delete 流3.2 bucket 数量怎么定bucket 数量是架构设计里最容易被拍脑袋决定的参数。定少了写入并发上不去单个文件容易过大定多了会产生大量小文件查询和 compaction 的压力都会增加。我的经验是bucket 数量建议先按 Flink 写入并行度来配比如写入并行度 16bucket 可以设 16 或 32让每个写入子任务尽量只写少数几个 bucket。更朴素的判断标准是看单 bucket 的数据量。一个 bucket 数据量控制在 128MB 到 1GB 之间比较合理。如果日增数据量很大可以适当增加 bucket但要同步调高写并行度。这里要特别提醒bucket-key 一旦上线后修改通常需要重写整张表不是改个参数就能解决。所以前期建表时要基于业务 key 的分布和写入流量做保守设计。3.3 分区、排序与文件裁剪架构图里很少画出分区细节但分区策略直接影响查询效果。Paimon 支持常规的分区表最常用的是按日期或小时分区。分区字段粒度不能太细比如一张订单表按小时分区很容易一小时只写几十 MB文件碎片就会很多。所以通常分区到天就够了必要时再用小时分区。Paimon 在处理数据时manifest 会记录每个文件的统计信息包括分区字段和主键范围。查询条件能命中分区裁剪时读取的文件数量会大幅减少。为了让裁剪更精准写入端尽量保证数据按分区有序进入不要让同一个分区的数据分散在大量小文件里。排序和分桶的设计本质都是为了让查询少扫描文件。3.4 流批一体读写路径怎么实现Paimon 的流批一体体现在同一张表可以有完全不同的读取方式。批式查询就是普通 SQLSELECT user_id, SUM(amount) FROM orders WHERE dt 2025-04-10 GROUP BY user_id;流式读取则更像消费消息队列SELECT order_id, status, amount FROM orders /* OPTIONS(scan.mode latest) */;指定scan.modelatest时Flink 会从当前最新快照开始消费之后产生的变更会持续输出。如果想从某个历史快照开始消费可以指定scan.snapshot-id。这个能力让下游流应用、实时大屏和离线报表都能直接用同一张表不再需要复制一份实时存储。写入端也一样Flink 可以连续插入Spark 也可以批量覆盖。用户不需要关心底层是 LSM 合并还是文件追加Paimon 会通过快照和 manifest 保证读写隔离。这就是架构图里“流批一体”这条标注的真正含义一套文件系统两种读写语义。3.5 小文件与快照生命周期管理生产环境里Paimon 最需要关注的是文件膨胀和快照膨胀。每次写入提交都会产生新的数据文件和 manifest如果任务频繁提交小文件会快速堆积。Paimon 提供了 compaction 机制可以在后台将小文件合并成大文件也可以设置write-only模式让写入任务不主动做合并由独立压缩作业来负责。快照也不能无限保留。每个快照都会占用一部分目录和 manifest历史快照过多会影响读取性能。一般建议设置snapshot.time-retained为 24 小时或 48 小时snapshot.num-retained也可以给一个最低限制。不过这里的取舍是如果下游有长时间运行的流式读取任务快照过期太激进会导致任务报错读不到数据。所以快照保留时间和流读任务的重启周期要一起考虑。4. 照着图落地从零搭建一个可运行的最小架构4.1 依赖准备与 Catalog 配置理论讲完还是要落到实际操作。我以 Flink Paimon Spark 这套组合为例跑通一个最小湖仓。首先版本要选对我当时用的是 Flink 1.17 和 Paimon 0.8兼容性比较稳。把对应版本的 paimon-bundle 和 flink 依赖放进 Flink lib 目录后启动 SQL Client。在 SQL Client 里创建 CatalogCREATE CATALOG my_paimon WITH ( type paimon, warehouse s3://my-bucket/warehouse, metastore filesystem ); USE CATALOG my_paimon;这段配置应该很好理解warehouse 就是整个湖仓的根路径。metastore我选了filesystem也就是把元数据以文件方式管理适合中小规模快速验证。如果团队有统一元数据中心也可以换成 JDBC Catalog把库表元数据放到关系型库里。然后开始建库建表CREATE DATABASE IF NOT EXISTS dwd; USE dwd; CREATE TABLE IF NOT EXISTS orders ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, ts TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( bucket 8, bucket-key order_id, file.format parquet );这一步是架构图中“存储层 表格式核心层”的实际落地。表建好后对象存储里就会出现对应的目录结构你甚至可以直接到文件系统里看数据文件和 manifest 列表对理解 Paimon 的存储模型非常有帮助。4.2 用 Flink CDC 持续把业务库数据写入 Paimon有了表之后就需要接数据管道。假设业务库有一张订单表我们用 Flink CDC 把变更实时同步到 Paimon。先定义上游 CDC 表CREATE TEMPORARY TABLE orders_cdc ( order_id BIGINT, user_id BIGINT, amount DECIMAL(10, 2), status STRING, ts TIMESTAMP(3), PRIMARY KEY (order_id) NOT ENFORCED ) WITH ( connector mysql-cdc, hostname 10.0.0.12, port 3306, username sync_user, password xxxx, database-name app, table-name orders );然后一条 SQL 把数据写入 PaimonINSERT INTO orders SELECT order_id, user_id, amount, status, ts FROM orders_ccd;实际执行时需要注意CDC 表的主键和 Paimon 表的主键要一致。Flink CDC 会输出 insert、update、delete 三类消息Paimon 主键表能正确处理这些消息相同主键的记录会按 sequence 字段决定覆盖顺序。首次运行还会自动做全量初始化和增量衔接对建湖阶段非常友好。Kafka 路径也是类似的无非是把上游 CDC 表换成 Kafka 消息表。写 Paimon 时尽量保持按主键分发否则重分区会带来额外开销。架构图里那条“业务库 - CDC - Kafka - Flink - Paimon”的链路到这一步就真实跑通了。4.3 让下游消费同一张表数据落湖之后下游可以按不同方式读。Flink 流读变更可以这样SELECT user_id, status, amount FROM orders /* OPTIONS(scan.mode latest) */;这个查询会持续输出新增和变更数据适合实时数仓下游或触发式逻辑。如果要批量查历史数据直接用 Spark 读 Paimon 更方便。在 Spark 里注册 CatalogCREATE DATABASE IF NOT EXISTS my_paimon; USE my_paimon;看下建表Spark 的 Catalog 配置方式略有不同但思路一样。注册完成之后就可以执行SELECT status, COUNT(*), SUM(amount) FROM my_paimon.dwd.orders WHERE dt 2025-04-10 GROUP BY status;这一条链路下来就实现了架构图中“Flink 写、Spark 读、同一份数据”的湖仓一体效果。不需要把数据拷贝两份也不需要做复杂的实时离线对账。4.4 定时维护任务怎么调度数据管道跑起来后一定要把维护任务排上。Paimon 的表不能在长时间运行后放任不管。最基础的是定时执行 compaction合并小文件CALL sys.compact(my_paimon.dwd.orders);这一步可以通过调度平台每天在业务低谷跑一次。另一个任务是快照清理如果建表时设置了合理的snapshot.time-retainedPaimon 会自动过期旧快照但我们要在监控里关注 snapshot 数量是否持续增长。如果某张表快照数量异常大概率是 compaction 或过期任务没有正常执行。另外还要关注存储量趋势。湖仓空间突然膨胀通常是单 bucket 小文件过多需要查写入并行度和 bucket 配置。这个维护流程在架构图里虽然看不到但它是稳定性的底座。5. 常见问题排查与避坑指南5.1 小文件风暴怎么处理表现是表目录下文件数量非常多单个文件经常只有几百 KB查询变慢compaction 一直追不上。最常见的原因是 bucket 数量设置过大但写入数据量不大每个 bucket 分到的数据太少。另一个原因是写入任务提交频率太高每次 commit 都产生新文件。我的处理顺序是先看表目录文件数量再查写入频率和 bucket 配置。如果 bucket 确实大了建议重建表而不是仅仅调大 compaction 频率。如果提交频率高可以把多条小事务合并成一个大事务或者用独立 compaction 作业持续清理。记住一个原则小文件问题不是靠一次性合并能根治的要从并发和分桶上同时控制。5.2 snapshot 无限膨胀snapshot 膨胀的典型现象是 manifest 目录很多List 文件时非常慢读取偶尔超时。Paimon 虽然会自动过期快照但如果你设置的snapshot.time-retained太长或者长期没有执行过期逻辑snapshot 就会堆积。排查时先查看目录下 snapshot 数量和保留策略对比。如果是流读整个周期很长不能缩短保留时间就需要调整下游重启策略让流读从最新快照开始而不是从很老的快照开始。如果快照清理已经开始表目录会周期性出现删除文件操作属正常现象。5.3 主键数据重复或乱序覆盖Paimon 主键表理论上不会重复但如果你没有配置sequence.field多个任务写同一张表时后写入的任务可能用旧数据覆盖新数据。处理办法是给表指定一个严格递增的 sequence 字段通常用业务时间戳。另外一个容易踩的问题是 CDC 同步任务重启后未提交的 reader 可能会导致重复读取这些重复数据进入 Paimon 后因为有主键可以合并最终不会出现主键重复但中间状态可能不符合下游预期。所以在流读下游也要做幂等设计至少要有唯一标识和时延监控。5.4 查询慢的定位路径Paimon 查询慢先不要怀疑引擎绝大多数情况下是扫描的数据文件太多。第一步看 SQL 有没有命中分区裁剪第二步看 bucket 的文件数量第三步看 manifest 的大小。很多团队忽略了分区字段的设计导致查询虽然带了过滤条件但裁剪效果很差。如果数据总量已经上去了Paimon 作为湖存储再怎么优化也难以替代真正的 MPP 数仓。架构图里加一层 OLAP 加速引擎不是多此一举而是对不同延迟需求的自然分流。Paimon 更适合做统一源数据和流批底座亚秒级查询交给专门引擎去扛。5.5 常见问题速查表症状可能原因快速处理文件数量暴增bucket 太多、提交频繁调小 bucket 或合并小文件快照目录巨大保留时间过长过期任务没跑调整 snapshot 保留参数数据覆盖旧值没有配置 sequence.field增加写数据时间戳字段查询扫描行数大分区字段设计不合理调整分区粒度、用复合分区流读启动报快照不存在快照被过期清理缩短流读重启延迟写入反压严重写并行度和 bucket 不匹配调整并发或 bucket 数量5.6 实操体会收尾我最想提醒的一点是Paimon 的架构图看着清晰但真正理解它还是要亲手建一张带主键的表用 Flink 往里写几百万条数据再观察目录结构、快照和小文件的变化。很多东西在文档里是抽象的只有亲手处理一次快照膨胀或乱序覆盖你才知道一个参数配错会带来多大影响。另外不要一开始就上复杂的流批链路先把最简单的“一张表、一个写任务、一个读任务”跑稳再逐渐增加 CDC、多引擎查询和 OLAP 加速。湖仓一体不是一蹴而就的先让数据在一张表里流动起来架构图的价值才会真正体现。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询