Flink Paimon 流批一体数仓架构实践
聚焦 Flink+Paimon 从 CDC 实时入湖到增量查询的端到端实战,提供 Schema 设计与 Compaction 调优最佳实践
Apache Flink 与 Apache Paimon 的组合正在成为流批一体数仓的事实标准方案。但"知道要用"和"用好"之间隔着大量工程细节——Schema 设计不当会导致 Compaction 膨胀,Changelog 配置错误会让下游消费不到变更事件,增量查询的性能调优更需要理解底层存储机制。本文从端到端的视角,梳理从实时入湖到增量查询的关键实践。
Paimon 在数仓中的定位
Paimon 本质上是一个流批一体的表格式存储层。它不是计算引擎,而是为 Flink 提供了高效的存储抽象。写入层由 Flink 作业驱动,查询层可以是 Flink(流查询和批查询),也可以是 Spark、Trino 等其他引擎。
这种定位使得 Paimon 天然适合构建实时数仓的中间层和汇总层。CDC 数据通过 Flink CDC 连接器流入 Paimon 表,下游的实时报表和批处理作业都可以直接查询同一份数据,不需要维护两套存储。
Paimon 底层使用 LSM-Tree 结构组织数据。新写入的数据先进入内存缓冲区,达到阈值后刷写为磁盘上的有序文件(Sorted Run)。当 Sorted Run 数量增多时,后台 Compaction 任务将它们合并,保持查询性能。这种机制使得 Paimon 既能承受高吞吐写入,又能保持查询效率。
Schema 设计:主键表 vs 追加表
Paimon 表分为两种基本类型,选错了会直接影响整个链路的性能和功能。
主键表(Primary Key Table)需要在建表时指定主键。写入时,Paimon 根据主键判断是插入新记录还是更新已有记录。底层使用 LSM-Tree 结构,支持高频 Upsert。适用于订单表、用户信息表等需要更新的维度表和事实表。主键表还支持多种 MergeEngine,包括 deduplicate(去重)、partial-update(部分字段更新)和 aggregation(聚合),可以根据业务语义选择最合适的合并策略。
追加表(Append-only Table)不指定主键,每条写入的数据都是新记录。适用于日志流、事件流等只追加不更新的场景。追加表的写入性能最高,因为不需要 Compaction。
实际建表时的一个常见错误是将所有表都建成主键表。对于日志类数据,强行指定主键不仅浪费 Compaction 资源,还会降低写入吞吐量。判断标准很简单:如果你的下游需要根据某个键做去重或更新,用主键表;如果数据天然是追加的,用追加表。
Changelog Producer:让下游感知数据变更
流批一体数仓中,下游作业通常需要感知源表的变更事件(插入、更新、删除)。Paimon 通过 changelog-producer 属性控制变更日志的生成方式。
none 模式不生成 Changelog,适用于追加表或下游不需要变更通知的主键表。
input 模式直接将输入数据作为 Changelog 输出。这要求输入数据本身就是完整的变更流(如 CDC 源),如果输入有重复数据,下游会收到重复的变更事件。
full-compaction 模式在每次全量 Compaction 后生成 Changelog。无论输入是否完整,都能保证生成正确的变更序列。代价是变更的延迟取决于 Compaction 的触发频率。可以通过 full-compaction.delta-commits 参数控制触发间隔。
lookup 模式通过在 Compaction 时查询已有数据来生成精确的 Changelog。它能保证每个 UPDATE_AFTER 都有对应的 UPDATE_BEFORE,但需要额外的查询开销。
选择建议:CDC 数据直接入湖且输入流质量可控时用 input 模式;需要精确 Changelog 但可以接受一定延迟时用 full-compaction 模式;对 Changelog 质量要求最高时用 lookup 模式,但要注意适当增加 Flink 的 execution.checkpointing.max-concurrent-checkpoints 配置来提升性能。
Compaction 调优:写入性能与查询性能的平衡
Compaction 是 Paimon 最关键的运维环节。不合理的 Compaction 配置会导致两种极端:Sorted Run 数量过多导致查询变慢,或者 Compaction 过于频繁导致写入作业被反压。
Paimon 提供两种 Compaction 策略。Universal 策略适合写入量不大的场景,Compaction 频率低,文件数量可能较多。Leveled 策略维护多层文件结构,查询性能更稳定,但写放大较高。
生产环境的调优要点:首先,对写入量大的表启用异步 Compaction,避免 Compaction 阻塞主写入任务。可以配置 write-only = true 让写入作业只写不 Compaction,再启动独立的 Compaction 作业。其次,合理设置 num-sorted-run.compaction-trigger——默认值在大多数场景下是合理的,但如果查询延迟敏感,可以适当降低触发阈值。最后,监控每个分区的 Sorted Run 数量,如果持续增长说明 Compaction 跟不上写入速度,需要增加 Compaction 并发或调整资源配置。
需要特别注意的是,同一分区同一时刻只能有一个 Compaction 任务运行,否则会产生冲突导致一方失败。在规划 Compaction 资源时要预留足够的并行度。
端到端架构设计
一个典型的 Flink + Paimon 流批一体数仓架构包含三层。
ODS 层:Flink CDC 从 MySQL、Oracle 等数据库实时捕获变更,写入 Paimon 的主键表。Changelog-producer 设为 input 或 lookup,确保下游能感知每条变更。这一层的核心要求是低延迟和高可靠性。
DWD 层:Flink SQL 从 ODS 层读取增量数据,进行清洗、关联和轻度汇总,结果写入新的 Paimon 表。这一层的表通常是主键表,Changelog-producer 设为 full-compaction 或 lookup。维表关联可以使用 Paimon 的 Lookup Join,利用 Paimon 的 LSM-Tree 结构实现高效的点查。
DWS 层:汇总层同样用 Flink SQL 构建,支持实时聚合查询。如果需要历史回算,同一套 Flink SQL 切换到批模式直接读取 Paimon 表的历史快照即可。
整个链路中,Flink 既是写入引擎也是查询引擎,Paimon 是统一存储层。流模式下处理实时增量,批模式下处理历史数据,代码完全复用。
增量查询与 Materialized Table
Paimon 的增量查询能力是它与 Iceberg、Hudi 的重要差异。下游的 Flink 流作业可以直接订阅 Paimon 表的增量数据,不需要额外的 CDC 工具。这种增量消费是基于 Paimon 的快照机制实现的——每个快照记录了相对于上一个快照的变化。
对于需要实时感知上游变更的下游作业(如实时报表、实时特征计算),这种增量查询能力大幅简化了架构。你不需要在上游额外部署 Kafka 来传递变更事件,Paimon 表本身就承担了这个角色。
Flink 2.1 引入的 Materialized Table 功能进一步简化了流批一体的使用。用户在 Flink SQL 中定义 Materialized Table,指定数据的新鲜度要求(如"5 分钟"),引擎会自动决定是启动流作业还是批作业来满足这个要求。这消除了手动配置和维护作业类型的负担。


