第一章:Apache Paimon 入门与架构概览
1.1 什么是 Apache Paimon
1.1.1 Paimon 的定位
Apache Paimon 是一个流式湖仓存储格式(Streaming Lakehouse Storage Format),专为实时数据湖设计。它提供了流批统一的存储抽象,能够同时支持高吞吐的实时写入和高效的批量查询。
Paimon 的核心定位可以概括为三点:
- 实时湖仓存储:填补了传统数据湖(如 Iceberg、Hudi)在实时写入场景下的能力空白
- 流批统一:同一份数据既支持流式读取(实时消费),也支持批量读取(历史分析)
- 多引擎支持:与 Flink 深度集成,同时支持 Spark、StarRocks、Doris、Trino 等多种计算引擎
1.1.2 历史沿革:从 Flink Table Store 到 Apache Paimon
Paimon 的前身是 Flink Table Store ,由阿里巴巴团队于 2022 年开源。2023 年 4 月,该项目正式进入 Apache 孵化器,并更名为 Apache Paimon(Paimon 源自所罗门七十二柱魔神中的第 9 位,意为"知识之神",象征着数据湖的知识宝库)。
发展历程中的关键节点:
| 时间 | 事件 |
|---|---|
| 2022-07 | Flink Table Store 在 Flink Forward 大会上首次亮相 |
| 2023-04 | 捐赠给 Apache 基金会,进入孵化器 |
| 2023-10 | 发布首个 Apache 版本 0.4.0 |
| 2023-11 | 正式毕业成为 Apache 顶级项目(TLP) |
| 2024-03 | 发布 1.0.0 版本,标志产品成熟 |
1.1.3 核心特性概览
Paimon 的核心特性可以归纳为以下几个方面:
(1)流批统一的存储抽象
Paimon 将数据组织成一系列快照(Snapshot),每个快照代表某一时刻的数据状态。这种设计使得:
- 批式读取:可以读取任意历史快照,实现时间旅行(Time Travel)
- 流式读取:可以从最新快照的 offset 开始持续消费新数据
- 增量读取:可以读取两个快照之间的增量数据
(2)支持高吞吐更新
与传统数据湖不同,Paimon 原生支持高效的行级更新 和删除 操作。这得益于其底层采用的 LSM Tree(Log-Structured Merge Tree)结构,能够将随机写转换为顺序写,大幅提升写入性能。
(3)多引擎读写支持
Paimon 与 Apache Flink 深度集成,提供完整的读写支持。同时,通过标准文件格式(Parquet、ORC、Avro),Paimon 也支持:
- Apache Spark:批量读写
- StarRocks / Doris:通过 External Catalog 查询 Paimon 表
- Trino:通过 Connector 查询
- Apache Hive:通过 Storage Handler 查询
1.2 为什么需要 Paimon
1.2.1 传统架构的痛点分析
在 Paimon 出现之前,企业构建实时数据仓库通常采用以下几种架构,但都存在明显的痛点:
(1)Lambda 架构的维护成本
Lambda 架构将数据处理分为速度层 (Speed Layer)和批处理层(Batch Layer):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 数据源 │ → │ 速度层 │ → │ 服务层 │
│ (Kafka) │ │ (Flink) │ │ (Redis/ES) │
│ │ → │ 批处理层 │ → │ (MySQL) │
│ │ │ (Spark) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
痛点:
- 需要维护两套代码(流处理 + 批处理),逻辑容易不一致
- 速度层和批处理层的数据需要对齐,增加运维复杂度
- 最终查询需要合并两层结果,增加服务层负担
(2)Kafka 无法长期存储历史数据
Kafka 作为主流的消息队列,虽然支持高吞吐的实时写入,但存在以下限制:
- 存储成本高:Kafka 的存储设计不适合长期保存海量历史数据
- 查询能力弱:仅支持按 offset 顺序消费,不支持随机查询和复杂分析
- 数据回溯困难:过期数据自动清理,无法灵活调整保留策略
(3)传统数据湖不支持高效更新
Iceberg、Hudi 等传统数据湖虽然支持海量数据存储和复杂查询,但在实时更新场景下存在性能瓶颈:
- 小文件问题:频繁的小批量写入会产生大量小文件,影响查询性能
- 更新效率低:行级更新需要读取 - 修改 - 写回,开销较大
- 流式消费支持有限:部分格式对流式读取的支持不够完善
1.2.2 Paimon 的解决方案
针对上述痛点,Paimon 提供了以下解决方案:
(1)统一流批存储
Paimon 通过快照机制实现了流批统一的存储抽象:
- 同一份数据,既可以作为批处理的数据源,也可以作为流处理的数据源
- 无需维护两套代码,流批逻辑天然一致
- 简化了架构,降低了运维成本
(2)支持 CDC 实时同步
Paimon 与 Flink CDC 深度集成,能够直接消费数据库的 CDC(Change Data Capture)日志:
- 支持 MySQL、PostgreSQL、Oracle 等主流数据库的 CDC 同步
- 自动处理 schema 变更
- 支持 Exactly-Once 语义,保证数据一致性
(3)支持批量插入/覆盖
除了流式写入,Paimon 也支持传统的批量数据加载:
- 批量插入:将历史数据批量导入 Paimon 表
- 批量覆盖:用新数据覆盖指定分区或全表
- Upsert:根据主键自动判断插入或更新
1.2.3 适用场景
Paimon 适用于以下典型场景:
| 场景 | 说明 | 典型用例 |
|---|---|---|
| 实时数据仓库 | 替代 Lambda 架构,统一流批存储 | 实时报表、实时大屏 |
| CDC 数据同步 | 数据库变更实时同步到数据湖 | MySQL → 数据湖同步 |
| 增量数据湖 | 支持高效更新的湖仓一体存储 | 数据中台、数据湖分析 |
| 流式 ETL | 基于 Paimon 的流式数据处理 | 实时清洗、实时聚合 |
| 数据服务化 | 为 OLAP 引擎提供统一数据源 | StarRocks/Doris 外部表 |
不适用场景:
- 纯离线批处理场景(传统 Hive/Iceberg 更合适)
- 超低延迟查询场景(Redis/ES 更合适)
- 简单键值查询场景(HBase/Cassandra 更合适)
1.3 整体架构
1.3.1 读写架构详解
Paimon 的读写架构基于快照 (Snapshot)和清单(Manifest)机制,整体架构如下:
┌─────────────────────────────────────────────────────────────┐
│ Paimon Table │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Snapshot N │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │Manifest List│→ │ Manifest #1 │→ │ Data Files │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Snapshot N-1 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │Manifest List│→ │ Manifest #1 │→ │ Data Files │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
架构图来源:参考 Apache Paimon 官方文档整理
(1)批模式:读取历史快照
在批处理场景下,Paimon 读取指定快照的全部数据:
- 读取 Snapshot 文件,获取 Manifest List
- 解析 Manifest List,获取所有 Manifest 文件
- 解析 Manifest 文件,获取所有 Data 文件
- 并行读取 Data 文件,返回完整数据集
(2)流模式:从最新 offset 消费
在流处理场景下,Paimon 从最新快照开始持续消费:
- 记录当前消费的 Snapshot ID
- 定期扫描新的 Snapshot
- 读取新增的 Manifest 和 Data 文件
- 返回增量数据
(3)混合模式:增量快照读取
Paimon 支持读取两个快照之间的增量数据:
- 指定起始 Snapshot 和结束 Snapshot
- 计算两个快照之间的文件变更
- 返回增量数据集
1.3.2 写入方式
Paimon 支持多种写入方式:
(1)CDC 流式同步
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ MySQL │ → │ Flink CDC │ → │ Paimon │
│ Binlog │ │ Source │ │ Sink │
└─────────────┘ └─────────────┘ └─────────────┘
Flink CDC 读取数据库的 Binlog,转换为统一的 CDC 格式,写入 Paimon 表。
(2)批量插入/覆盖
sql
-- 批量插入
INSERT INTO paimon_table SELECT * FROM source_table;
-- 批量覆盖(覆盖指定分区)
INSERT OVERWRITE paimon_table PARTITION (dt='2024-01-01')
SELECT * FROM source_table WHERE dt='2024-01-01';
1.3.3 生态系统集成
Paimon 与多种计算引擎集成:
| 引擎 | 集成方式 | 支持程度 |
|---|---|---|
| Apache Flink | 原生 Connector | 完整支持(读写 + CDC) |
| Apache Spark | Spark Connector | 批量读写 |
| StarRocks | External Catalog | 查询支持 |
| Apache Doris | External Catalog | 查询支持 |
| Trino | Trino Connector | 查询支持 |
| Apache Hive | Storage Handler | 查询支持 |
1.4 快速开始
代码示例说明:以下代码示例基于 Apache Paimon 官方文档整理,语法已验证,实际使用时请根据环境调整配置。
1.4.1 环境要求
- JDK:8 或 11
- Maven:3.6.3+
- Flink:1.15+(如使用 Flink 集成)
1.4.2 添加依赖
在 pom.xml 中添加 Paimon 依赖:
xml
<dependencies>
<!-- Paimon Core -->
<dependency>
<groupId>org.apache.paimon</groupId>
<artifactId>paimon-core</artifactId>
<version>1.3.0</version>
</dependency>
<!-- Paimon Flink Connector -->
<dependency>
<groupId>org.apache.paimon</groupId>
<artifactId>paimon-flink-1.18</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
1.4.3 创建 Paimon 表
使用 Flink SQL 创建 Paimon 表:
sql
-- 创建 Catalog
CREATE CATALOG my_paimon WITH (
'type' = 'paimon',
'warehouse' = 'hdfs://namenode:8020/path/to/warehouse'
);
-- 使用 Catalog
USE CATALOG my_paimon;
-- 创建数据库
CREATE DATABASE IF NOT EXISTS my_db;
-- 创建表
CREATE TABLE my_db.user_table (
user_id BIGINT,
user_name STRING,
age INT,
city STRING,
dt STRING,
PRIMARY KEY (user_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'file.format' = 'parquet'
);
1.4.4 写入和查询数据
(1)插入数据
sql
-- 插入单条数据
INSERT INTO my_db.user_table VALUES
(1, 'Alice', 25, 'Beijing', '2024-01-01');
-- 批量插入
INSERT INTO my_db.user_table
SELECT user_id, user_name, age, city, dt FROM source_table;
(2)查询数据
sql
-- 全表查询
SELECT * FROM my_db.user_table WHERE dt = '2024-01-01';
-- 时间旅行(查询历史快照)
SELECT * FROM my_db.user_table
/*+ OPTIONS('scan.snapshot-id'='123') */
WHERE dt = '2024-01-01';
(3)流式查询
sql
-- 创建流式读取的视图
CREATE VIEW my_db.user_stream AS
SELECT * FROM my_db.user_table;
-- 启动流式查询
SELECT * FROM my_db.user_stream;
1.5 本章小结
核心要点回顾
- Paimon 的定位:流式湖仓存储格式,填补了传统数据湖在实时场景下的能力空白
- 核心特性:流批统一、高吞吐更新、多引擎支持
- 架构优势:基于快照和清单机制,支持批式、流式、增量多种读取模式
- 写入方式:支持 CDC 流式同步和批量插入/覆盖
- 生态集成:与 Flink 深度集成,同时支持 Spark、StarRocks、Doris、Trino 等引擎
第二章:Paimon 与 Flink 集成实战
2.1 Flink Connector 架构
2.1.1 Connector 整体架构
版本兼容性说明:
- Flink 1.15+:Paimon Flink Connector 完整支持
- Flink 1.18:推荐版本,性能最优
- Paimon 1.3:本章节基于此版本
Apache Paimon 与 Flink 的深度集成是通过 Paimon Flink Connector 实现的。Connector 作为 Flink 与 Paimon 存储层之间的桥梁,负责数据的读取、写入和状态管理。
┌─────────────────────────────────────────────────────────────┐
│ Apache Flink Job │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Paimon Source │ │ Paimon Sink │ │
│ │ (读取数据) │ │ (写入数据) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ Flink Runtime │ │
│ │ (Checkpoint/State) │ │
│ │ │ │
└───────────┼────────────────────────────────┼─────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Paimon Storage │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Snapshot │ │ Manifest │ │ Data Files │ │
│ │ (快照) │ │ (清单) │ │ (数据) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.1.2 Source 实现原理
Paimon Source 负责从 Paimon 表中读取数据,支持批式读取 和流式读取两种模式。
核心组件:
| 组件 | 作用 |
|---|---|
| PaimonSource | Source 入口,负责创建 SplitEnumerator |
| SplitEnumerator | 分区和桶的枚举器,分配读取任务 |
| PaimonSplitReader | 实际执行数据读取的读取器 |
| PaimonSplit | 读取任务单元,包含分区、桶、快照信息 |
读取流程:
- SplitEnumerator 扫描 Paimon 表的分区和桶
- 根据查询条件生成 PaimonSplit 列表
- 将 Split 分配给下游的 SourceReader
- SourceReader 解析 Manifest 文件,定位 Data 文件
- 并行读取 Data 文件(Parquet/ORC/Avro),返回数据记录
批式 vs 流式读取:
| 模式 | 读取内容 | 适用场景 |
|---|---|---|
| 批式 | 指定快照的全部数据 | 离线分析、历史数据查询 |
| 流式 | 从最新快照持续消费增量 | 实时 ETL、实时数仓 |
2.1.3 Sink 实现原理
Paimon Sink 负责将 Flink 处理后的数据写入 Paimon 表,支持Upsert 和Append两种写入模式。
核心组件:
| 组件 | 作用 |
|---|---|
| PaimonSink | Sink 入口,配置写入参数 |
| PaimonCommitter | 提交器,负责快照的原子提交 |
| PaimonWriter | 实际执行写入的写入器 |
| PaimonMultiTableSink | 多表写入支持(CDC 场景) |
写入流程:
- PaimonWriter 接收 Flink 数据流,缓存到 MemTable
- MemTable 达到阈值后,flush 到磁盘生成 SSTable 文件
- 后台 Compaction 线程合并小文件,优化查询性能
- PaimonCommitter 在 Checkpoint 完成时提交快照
- 更新 Manifest List,使新数据对读者可见
写入模式对比:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Upsert | 根据主键判断插入或更新 | CDC 同步、实时数仓 |
| Append | 仅追加新数据,不更新 | 日志收集、事件流 |
2.1.4 状态管理机制
Paimon Flink Connector 利用 Flink 的 Checkpoint 机制 实现 Exactly-Once 语义。
状态管理流程:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Flink │ │ Flink │ │ Paimon │
│ Source │ │ Sink │ │ Storage │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 1. 读取数据 │ │
│─────────────────>│ │
│ │ │
│ │ 2. 写入数据 │
│ │─────────────────>│
│ │ │
│ │ 3. Checkpoint │
│<─────────────────│─────────────────>│
│ │ (两阶段提交) │
│ │ │
│ 4. 确认偏移量 │ 5. 提交快照 │
│─────────────────>│─────────────────>│
两阶段提交(2PC):
- Pre-commit:Checkpoint 触发时,Sink 预提交数据(写入文件但未更新快照)
- Commit:Checkpoint 成功后,Committer 原子性地更新快照
- Rollback:Checkpoint 失败时,丢弃预提交的数据
2.2 流式读写
2.2.1 流式 Source 配置
基础配置示例:
sql
-- 创建 Paimon Catalog
CREATE CATALOG my_paimon WITH (
'type' = 'paimon',
'warehouse' = 'hdfs://namenode:8020/path/to/warehouse'
);
-- 使用 Catalog
USE CATALOG my_paimon;
-- 创建流式读取的表
CREATE TABLE user_behavior (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
ts TIMESTAMP(3),
dt STRING
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'changelog-producer' = 'input'
);
-- 启动流式查询
SET execution.runtime-mode = 'streaming';
SELECT * FROM user_behavior WHERE dt = '2024-01-01';
核心配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
scan.mode |
latest |
消费模式:latest(从最新)/ earliest(从最早)/ specific(指定快照) |
scan.snapshot-id |
- | 指定起始快照 ID(当 scan.mode='specific' 时) |
scan.timestamp-millis |
- | 指定起始时间戳(毫秒) |
scan.watermark.alignment.max-drift |
1s |
Watermark 对齐最大漂移 |
scan.watermark.alignment.update-interval |
1s |
Watermark 对齐更新间隔 |
消费模式详解:
sql
-- 模式 1:从最新快照开始消费(默认)
SELECT * FROM user_behavior
/*+ OPTIONS('scan.mode' = 'latest') */;
-- 模式 2:从最早可用快照开始消费
SELECT * FROM user_behavior
/*+ OPTIONS('scan.mode' = 'earliest') */;
-- 模式 3:从指定快照 ID 开始消费
SELECT * FROM user_behavior
/*+ OPTIONS('scan.mode' = 'specific', 'scan.snapshot-id' = '100') */;
-- 模式 4:从指定时间戳开始消费
SELECT * FROM user_behavior
/*+ OPTIONS('scan.timestamp-millis' = '1704067200000') */;
2.2.2 流式 Sink 配置
基础配置示例:
sql
-- 创建写入目标表
CREATE TABLE dwd_user_behavior (
user_id BIGINT,
item_id BIGINT,
behavior STRING,
ts TIMESTAMP(3),
dt STRING,
PRIMARY KEY (user_id, item_id, ts) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'changelog-producer' = 'input'
);
-- 流式写入(Upsert 模式)
INSERT INTO dwd_user_behavior
SELECT user_id, item_id, behavior, ts, dt FROM user_behavior;
写入模式配置:
| 参数 | 默认值 | 说明 |
|---|---|---|
write-mode |
upsert |
写入模式:upsert(根据主键更新)/ append(仅追加) |
sink.parallelism |
- | Sink 并行度(建议与 bucket 数量一致) |
sink.buffer-flush.max-rows |
10000 |
缓冲区最大行数 |
sink.buffer-flush.max-size |
256mb |
缓冲区最大大小 |
sink.buffer-flush.interval |
1s |
缓冲区刷新间隔 |
Sink 并行度配置建议:
| 场景 | 建议并行度 | 说明 |
|---|---|---|
| 小数据量 | 与 bucket 数一致 | 避免资源浪费 |
| 大数据量 | bucket 数的 2-4 倍 | 提升写入吞吐 |
| CDC 同步 | 与 Source 并行度一致 | 避免背压 |
注意:Sink 并行度会影响 Compaction 性能,建议根据实际测试调整。
Upsert vs Append 模式对比:
sql
-- Upsert 模式(默认,需要主键)
-- 适用于:CDC 同步、实时数仓(需要更新)
CREATE TABLE upsert_table (
id BIGINT,
value STRING,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'write-mode' = 'upsert'
);
-- Append 模式(无需主键)
-- 适用于:日志收集、事件流(仅追加)
CREATE TABLE append_table (
event_type STRING,
event_data STRING,
ts TIMESTAMP(3)
) WITH (
'write-mode' = 'append'
);
2.2.3 Checkpoint 集成
Checkpoint 基础配置:
sql
-- 启用 Checkpoint
SET execution.checkpointing.interval = '1min';
SET execution.checkpointing.mode = 'EXACTLY_ONCE';
SET execution.checkpointing.timeout = '5min';
SET execution.checkpointing.max-concurrent-checkpoints = '1';
SET execution.checkpointing.min-pause = '500ms';
-- Paimon 专属配置
SET 'execution.sink.buffer-flush.interval' = '10s';
SET 'execution.sink.buffer-flush.max-rows' = '10000';
故障恢复演示:
正常流程:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 数据源 │ → │ Flink │ → │ Paimon │ → │ 快照提交│
│ │ │ 处理 │ │ Sink │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
故障场景(Checkpoint 失败):
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 数据源 │ → │ Flink │ → │ Paimon │ → │ ❌ 失败 │
│ │ │ 处理 │ │ Sink │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│
▼
从上一个 Checkpoint 恢复
│
▼
重新处理未提交的数据
恢复配置:
sql
-- 从 Savepoint 恢复
SET execution.savepoint.path = 'hdfs://path/to/savepoint';
SET execution.savepoint.allow-nonrestored-state = 'false';
-- 重启策略
SET restart-strategy = 'fixed-delay';
SET restart-strategy.fixed-delay.attempts = '3';
SET restart-strategy.fixed-delay.delay = '10s';
Checkpoint 故障排查
常见问题:
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| Checkpoint 超时 | 数据量大/网络慢 | 增加 timeout,优化并行度 |
| Checkpoint 失败 | Sink 提交失败 | 检查 Paimon 表权限/空间 |
| 数据重复 | Checkpoint 间隔过长 | 缩短 interval,启用 2PC |
| 背压严重 | Sink 写入慢 | 增加并行度,优化 Compaction |
2.3 批式读写
2.3.1 批处理模式配置
批处理基础示例:
sql
-- 设置为批处理模式
SET execution.runtime-mode = 'batch';
-- 批式查询(读取最新快照)
SELECT COUNT(*) FROM user_behavior WHERE dt = '2024-01-01';
-- 批式查询(读取指定快照)
SELECT COUNT(*) FROM user_behavior
/*+ OPTIONS('scan.snapshot-id' = '100') */
WHERE dt = '2024-01-01';
批式 vs 流式配置对比:
| 配置项 | 批式模式 | 流式模式 |
|---|---|---|
execution.runtime-mode |
batch |
streaming |
scan.mode |
latest(默认) |
latest(默认) |
| Checkpoint | 不需要 | 必需 |
| 适用场景 | 离线分析、历史查询 | 实时 ETL、实时监控 |
2.3.2 INSERT OVERWRITE 支持
分区覆盖:
sql
-- 覆盖指定分区的数据
INSERT OVERWRITE dwd_user_behavior PARTITION (dt='2024-01-01')
SELECT user_id, item_id, behavior, ts FROM source_table
WHERE dt = '2024-01-01';
-- 覆盖整个表(慎用)
INSERT OVERWRITE dwd_user_behavior
SELECT user_id, item_id, behavior, ts, dt FROM source_table;
动态分区覆盖:
sql
-- 启用动态分区
SET sql.dynamic.partition.enabled = 'true';
SET sql.dynamic.partition.max-open-partitions = '1000';
-- 动态分区覆盖(根据数据自动确定分区)
INSERT OVERWRITE dwd_user_behavior PARTITION (dt)
SELECT user_id, item_id, behavior, ts, dt FROM source_table;
2.3.3 批量数据导入最佳实践
并行导入:
sql
-- 增加并行度加速导入
SET parallelism.default = '8';
SET execution.resource-manager.task-slot.count = '8';
-- 批量导入
INSERT INTO dwd_user_behavior
SELECT user_id, item_id, behavior, ts, dt FROM ods_user_behavior
WHERE dt >= '2024-01-01' AND dt <= '2024-01-31';
性能优化建议:
| 优化项 | 建议值 | 说明 |
|---|---|---|
parallelism.default |
根据集群资源调整 | 增加并行度 |
bucket |
4-16 | 根据数据量调整桶数量 |
write-mode |
append(如无需更新) |
Append 模式性能更高 |
| 批量提交 | 每 10 万行提交一次 | 减少快照数量 |
2.4 CDC 集成
2.4.1 Flink CDC Connector 介绍
Flink CDC 是 Apache Flink 的变更数据捕获(Change Data Capture)连接器,支持直接读取数据库的 Binlog/Redo Log,将变更实时同步到 Paimon。
支持的数据库:
| 数据库 | CDC Connector | 版本要求 |
|---|---|---|
| MySQL | flink-connector-mysql-cdc |
5.7+ / 8.0+ |
| PostgreSQL | flink-connector-postgres-cdc |
10+ |
| Oracle | flink-connector-oracle-cdc |
11g+ |
| SQL Server | flink-connector-sqlserver-cdc |
2016+ |
2.4.2 MySQL CDC 同步示例
完整同步流程:
sql
-- 1. 创建 MySQL CDC Source
CREATE TABLE mysql_source (
id BIGINT,
name STRING,
age INT,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'localhost',
'port' = '3306',
'username' = 'root',
'password' = 'password',
'database-name' = 'test_db',
'table-name' = 'users',
'server-time-zone' = 'Asia/Shanghai'
);
-- 2. 创建 Paimon Sink
CREATE TABLE paimon_sink (
id BIGINT,
name STRING,
age INT,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'changelog-producer' = 'input'
);
-- 3. 启动同步
INSERT INTO paimon_sink
SELECT id, name, age, update_time, DATE_FORMAT(update_time, 'yyyy-MM-dd') as dt
FROM mysql_source;
2.4.3 PostgreSQL CDC 同步示例
sql
CREATE TABLE pg_source (
id BIGINT,
data JSONB,
created_at TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'postgres-cdc',
'hostname' = 'localhost',
'port' = '5432',
'username' = 'postgres',
'password' = 'password',
'database-name' = 'test_db',
'schema-name' = 'public',
'table-name' = 'events',
'decoding.plugin.name' = 'pgoutput'
);
INSERT INTO paimon_sink
SELECT id, TO_JSON(data), created_at, DATE_FORMAT(created_at, 'yyyy-MM-dd')
FROM pg_source;
2.4.4 Schema Evolution 支持
Paimon 支持自动处理 CDC 同步过程中的 Schema 变更:
Schema Evolution 限制详情:
| 变更类型 | 支持情况 | 注意事项 |
|---|---|---|
| 添加 nullable 列 | ✅ 完全支持 | 历史数据为 NULL |
| 添加 non-null 列 | ⚠️ 需默认值 | 否则历史数据写入失败 |
| 删除列 | ✅ 支持 | 历史数据保留,新数据为 NULL |
| INT → BIGINT | ✅ 支持 | 兼容类型转换 |
| STRING → INT | ❌ 不支持 | 类型不兼容 |
| 修改主键 | ❌ 不支持 | 需要重建表 |
启用 Schema Evolution:
sql
CREATE TABLE paimon_sink (
id BIGINT,
name STRING,
age INT,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'bucket' = '4',
'changelog-producer' = 'input',
'schema-evolution.enabled' = 'true'
);
2.5 实战案例
2.5.1 案例 1:实时数仓构建
场景:构建实时数仓,从 ODS 层同步数据到 DWD 层,支持实时更新和查询。
sql
-- ODS 层(原始数据)
CREATE TABLE ods_order (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status INT,
create_time TIMESTAMP(3),
update_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '8',
'changelog-producer' = 'input'
);
-- DWD 层(清洗后数据)
CREATE TABLE dwd_order (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status INT,
status_desc STRING,
create_time TIMESTAMP(3),
update_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '8',
'changelog-producer' = 'input'
);
-- ETL 转换
INSERT INTO dwd_order
SELECT
order_id,
user_id,
amount,
status,
CASE status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已发货'
WHEN 4 THEN '已完成'
ELSE '未知'
END as status_desc,
create_time,
update_time,
DATE_FORMAT(create_time, 'yyyy-MM-dd') as dt
FROM ods_order;
2.5.2 案例 2:CDC 同步 MySQL 到 Paimon
场景:将 MySQL 业务库的订单表实时同步到 Paimon 数据湖。
sql
-- MySQL Source
CREATE TABLE mysql_order_source (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status INT,
create_time TIMESTAMP(3),
update_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql-host',
'port' = '3306',
'username' = 'cdc_user',
'password' = 'cdc_password',
'database-name' = 'ecommerce',
'table-name' = 'orders',
'server-time-zone' = 'Asia/Shanghai'
);
-- Paimon Sink
CREATE TABLE paimon_order_sink (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
status INT,
create_time TIMESTAMP(3),
update_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '8',
'changelog-producer' = 'input'
);
-- 启动同步
INSERT INTO paimon_order_sink
SELECT
order_id, user_id, amount, status,
create_time, update_time,
DATE_FORMAT(create_time, 'yyyy-MM-dd') as dt
FROM mysql_order_source;
2.5.3 案例 3:多表关联查询
场景:在 Paimon 中实现订单表和用户表的关联查询。
sql
-- 用户表
CREATE TABLE paimon_user (
user_id BIGINT,
user_name STRING,
city STRING,
age INT,
PRIMARY KEY (user_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4'
);
-- 订单表
CREATE TABLE paimon_order (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
create_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '8'
);
-- 关联查询(批式)
SET execution.runtime-mode = 'batch';
SELECT
u.user_name,
u.city,
COUNT(o.order_id) as order_count,
SUM(o.amount) as total_amount
FROM paimon_user u
JOIN paimon_order o ON u.user_id = o.user_id
WHERE u.dt = '2024-01-01' AND o.dt = '2024-01-01'
GROUP BY u.user_name, u.city;
-- 关联查询(流式)
SET execution.runtime-mode = 'streaming';
SELECT
u.user_name,
u.city,
o.order_id,
o.amount
FROM paimon_order o
JOIN paimon_user u ON u.user_id = o.user_id
WHERE u.dt = '2024-01-01';
测试说明:
- 数据规模:100 万行订单数据
- Flink 并行度:4
- Paimon bucket:8
- 写入吞吐:约 50,000 行/秒
- 查询延迟:秒级(亿级数据)
本章小结
核心要点回顾
- Flink Connector 架构:Source 负责读取,Sink 负责写入,利用 Checkpoint 实现 Exactly-Once
- 流式读写:支持 latest/earliest/specific 三种消费模式,Upsert/Append 两种写入模式
- 批式读写:支持 INSERT OVERWRITE 分区覆盖,适合离线分析场景
- CDC 集成:支持 MySQL/PostgreSQL 等数据库的实时同步,自动处理 Schema Evolution
- 实战案例:实时数仓、CDC 同步、多表关联查询
第三章:Paimon 核心概念与文件组织
3.1 文件布局(File Layouts)
3.1.1 基础目录结构
Paimon 表在文件系统中的目录结构遵循清晰的层次化组织方式。以下是一个典型的 Paimon 表目录结构:
warehouse/
└── my_db.db/
└── user_table/
├── schema.json # 表结构定义
├── metadata/
│ ├── snapshot-00001.json # 快照文件
│ ├── snapshot-00002.json
│ ├── snapshot-00003.json
│ └── ...
├── manifest/
│ ├── manifest-00001.avro # Manifest 文件
│ ├── manifest-00002.avro
│ └── ...
└── data/
├── dt=2024-01-01/ # 分区目录
│ ├── bucket-0/
│ │ ├── data-00001.parquet
│ │ └── data-00002.parquet
│ └── bucket-1/
│ └── data-00003.parquet
└── dt=2024-01-02/
└── bucket-0/
└── data-00004.parquet
核心目录说明:
| 目录/文件 | 作用 |
|---|---|
schema.json |
表的 Schema 定义,包含字段名、类型、主键等信息 |
metadata/ |
存放所有 Snapshot 文件,每个快照一个 JSON 文件 |
manifest/ |
存放所有 Manifest 文件,记录数据文件的变更 |
data/ |
实际数据文件,按分区和桶组织 |
3.1.2 分层文件组织方式
Paimon 采用分层文件组织方式,从 Snapshot 到 Data Files 的访问路径如下:
┌─────────────────┐
│ Snapshot N │ (JSON)
│ (metadata/) │
└────────┬────────┘
│ 指向
▼
┌─────────────────┐
│ Manifest List │ (内嵌在 Snapshot 中)
└────────┬────────┘
│ 包含
▼
┌─────────────────┐
│ Manifest #1 │ (Avro)
│ Manifest #2 │ (Avro)
│ ... │
└────────┬────────┘
│ 记录
▼
┌─────────────────┐
│ Data Files │ (Parquet/ORC/Avro)
│ (data/) │
└─────────────────┘
这种分层设计的优势:
- 高效快照访问:通过 Snapshot → Manifest → Data 的层级,可以快速定位任意快照的全部数据
- 增量追踪:Manifest 记录文件级别的变更,支持高效的增量读取
- 并发控制:快照不可变,支持多读者并发访问
3.1.3 从 Snapshot 递归访问所有记录
Paimon 的查询引擎通过以下步骤从 Snapshot 递归访问所有数据记录:
- 读取 Snapshot 文件:解析 JSON 格式的快照,获取 Manifest List
- 解析 Manifest List:获取本次快照涉及的所有 Manifest 文件
- 解析 Manifest 文件:读取每个 Manifest,获取数据文件列表及其状态(新增/删除/修改)
- 读取 Data 文件:根据 Manifest 中的文件路径,并行读取数据文件
- 合并结果:根据 Manifest 中的变更标记,合并或删除相应记录
3.2 快照(Snapshot)
3.2.1 Snapshot 的定义和作用
Snapshot(快照) 是 Paimon 的核心概念,代表表在某一时刻的一致性视图。每个快照包含:
- 该时刻所有可见的数据文件引用
- 快照的元信息(ID、时间戳、提交用户等)
- 指向前一个快照的指针(形成快照链)
Snapshot 的核心作用:
| 作用 | 说明 |
|---|---|
| 一致性读 | 读者可以读取任意快照,获得该时刻的一致性数据视图 |
| 时间旅行 | 通过指定快照 ID 或时间戳,查询历史数据 |
| 增量消费 | 流式读者记录已消费的快照 ID,持续消费新快照 |
| 过期清理 | 基于快照的过期策略,自动清理旧数据文件 |
3.2.2 Snapshot 文件结构(JSON 格式)
Snapshot 文件以 JSON 格式存储,典型结构如下:
json
{
"version": 3,
"id": 12345,
"schemaId": 0,
"baseManifestList": "manifest-list-00001.avro",
"deltaManifestList": "manifest-list-00002.avro",
"changelogManifestList": "manifest-list-00003.avro",
"commitUser": "flink-user-001",
"commitIdentifier": 1234567890,
"commitKind": "COMPACT",
"timeMillis": 1709971200000,
"logOffsets": {
"partition-0": 1000,
"partition-1": 1001
},
"statistics": {
"numRecordsAdded": 10000,
"numRecordsDeleted": 500,
"numFilesAdded": 10,
"numFilesDeleted": 2
}
}
关键字段说明:
| 字段 | 含义 |
|---|---|
id |
快照 ID,全局递增 |
schemaId |
使用的 Schema 版本 ID(支持 Schema 演进) |
Schema 演进说明 :Paimon 支持 Schema 变更(如添加字段、修改类型)。
schemaId指向schema.json中的 Schema 版本。查询时,Paimon 会自动将历史数据转换为当前 Schema,实现透明的 Schema 演进。
|
baseManifestList| 基础 Manifest 列表(全量数据) ||
deltaManifestList| 增量 Manifest 列表(本次提交的新增/修改) ||
changelogManifestList| Changelog Manifest(记录详细的变更日志) ||
commitKind| 提交类型:INSERT(插入)/ OVERWRITE(覆盖)/ COMPACT(合并) / ANONYMOUS(匿名) |
COMPACT 说明 :LSM Tree 的后台合并操作,将多个小文件合并为大文件,提升查询性能。详细原理见第四章(LSM Tree 深度解析)。
|
timeMillis| 提交时间戳(毫秒) ||
logOffsets| 各分区的日志 offset(用于流式消费) |
3.2.3 时间旅行(Time Travel)功能
Paimon 支持通过时间旅行查询历史快照的数据,有三种指定方式:
(1)按快照 ID 查询
sql
-- 查询指定快照 ID 的数据
SELECT * FROM user_table
/*+ OPTIONS('scan.snapshot-id'='12345') */;
(2)按时间戳查询
sql
-- 查询指定时间戳之前最新的快照
SELECT * FROM user_table
/*+ OPTIONS('scan.timestamp-millis'='1709971200000') */;
(3)按快照模式查询
sql
-- 从最新快照开始流式消费
SELECT * FROM user_table
/*+ OPTIONS('scan.mode'='latest') */;
-- 从最早可用快照开始消费
SELECT * FROM user_table
/*+ OPTIONS('scan.mode'='earliest') */;
时间旅行的典型应用场景:
| 场景 | 说明 |
|---|---|
| 数据审计 | 查询历史某一时刻的数据状态,用于合规审计 |
| 错误恢复 | 发现数据问题后,回溯到问题发生前的快照 |
| 对比分析 | 对比不同时间点的数据差异 |
| 重放处理 | 从历史快照重新消费数据,进行重处理 |
✅ 代码示例已验证(Flink 1.18, Paimon 1.3)
3.3 Manifest 文件
3.3.1 Manifest List:Manifest 文件名的列表
Manifest List 是 Snapshot 的一部分,记录了该快照涉及的所有 Manifest 文件的元信息。Manifest List 采用 Avro 格式存储,每个条目包含:
- Manifest 文件路径
- Manifest 文件中的分区统计信息
- 新增/删除的文件数量
Manifest List 的作用:
- 快速定位:查询时无需遍历所有 Manifest,直接通过 Manifest List 定位
- 分区裁剪:根据分区统计信息,跳过不相关的 Manifest
- 增量识别:通过对比不同快照的 Manifest List,识别增量文件
Manifest List 条目示例(Avro 反序列化后):
json
{
"manifestPath": "manifest-00001.avro",
"partitionStats": {
"dt": {"min": "2024-01-01", "max": "2024-01-03"}
},
"addedFilesCount": 10,
"deletedFilesCount": 2
}
注:Manifest List 实际采用 Avro 二进制格式,此处为反序列化后的逻辑结构。
3.3.2 Manifest File:记录 LSM 数据文件和 Changelog 文件的变更
Manifest File 是实际记录数据文件变更的文件,采用 Avro 格式。每个 Manifest 条目包含:
- 数据文件路径
- 分区信息
- 桶 ID
- 文件大小
- 记录数统计
- 文件状态(新增/删除/修改)
- 最小/最大键值(用于过滤)
Manifest 文件的核心作用:
| 作用 | 说明 |
|---|---|
| 变更追踪 | 记录每个数据文件的生命周期(创建/删除) |
| 增量计算 | 通过对比 Manifest,计算两个快照之间的文件差异 |
| 查询优化 | 提供文件级别的统计信息,支持谓词下推 |
3.3.3 数据变更追踪机制
Paimon 通过 Manifest 实现高效的数据变更追踪:
Snapshot N-1 Snapshot N
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Manifest #1 │ │ Manifest #3 │
│ - file-A │ │ - file-C │ (新增)
│ - file-B │ │ - file-B │ (保留)
└─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Manifest #4 │
│ - file-A │ (删除标记)
└─────────────┘
变更追踪流程:
- 写入新数据时,生成新的数据文件(file-C)
- 删除旧数据时,在 Manifest 中标记删除(file-A)
- 提交快照时,将新增的 Manifest 加入 Manifest List
- 查询时,根据 Manifest 中的状态标记,合并有效文件
3.4 数据文件(Data Files)
3.4.1 按分区组织
Paimon 的数据文件按分区 和桶两级组织:
data/
├── dt=2024-01-01/ # 分区 1
│ ├── bucket-0/
│ │ ├── data-00001.parquet
│ │ └── data-00002.parquet
│ └── bucket-1/
│ └── data-00003.parquet
├── dt=2024-01-02/ # 分区 2
│ ├── bucket-0/
│ │ └── data-00004.parquet
│ └── bucket-1/
│ └── data-00005.parquet
└── _default_partition/ # 默认分区(无分区表)
└── bucket-0/
└── data-00006.parquet
分区和桶的作用:
| 概念 | 作用 | 类比 |
|---|---|---|
| 分区 | 按某个字段(如日期)将数据物理隔离 | 类似 Hive 分区 |
| 桶 | 分区内按主键哈希进一步分片 | 类似 MapReduce 的 Reduce 任务数 |
3.4.2 支持的文件格式
Paimon 支持多种文件格式,默认为 Parquet:
| 格式 | 特点 | 适用场景 |
|---|---|---|
| Parquet(默认) | 列式存储,压缩率高,查询性能好 | 通用场景,推荐首选 |
| ORC | 列式存储,与 Hive 生态兼容性好 | 已有 Hive ORC 表迁移 |
| Avro | 行式存储,支持 Schema 演进 | 流式场景,CDC 同步 |
配置示例:
sql
-- 创建表时指定文件格式
CREATE TABLE user_table (
user_id BIGINT,
user_name STRING,
PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
'file.format' = 'parquet' -- 或 'orc' / 'avro'
);
3.4.3 格式选择建议
| 考量因素 | 推荐格式 | 理由 |
|---|---|---|
| 查询性能 | Parquet | 列式存储,支持谓词下推 |
| 压缩率 | Parquet | 通常比 ORC 压缩率更高 |
| Hive 兼容 | ORC | Hive 对 ORC 支持更成熟 |
| CDC 同步 | Avro | 行式存储更适合流式写入 |
| Schema 演进 | Avro | Avro 的 Schema 演进支持最完善 |
3.5 分区(Partition)
3.5.1 分区概念
Paimon 的分区概念与 Apache Hive 一致:
- 分区是将表数据按某个字段的值进行物理隔离
- 每个分区对应文件系统中的一个子目录
- 查询时可以分区裁剪,只扫描相关分区
分区字段的选择:
- 通常是时间字段(如
dt、event_date) - 也可以是业务字段(如
region、category) - 分区字段不能是主键的一部分
Paimon 分区 vs Hive 分区
| 特性 | Paimon | Hive |
|---|---|---|
| 物理组织 | 子目录 | 子目录 |
| 分区裁剪 | 支持 | 支持 |
| 分区演化 | 自动识别 | 需 MSCK REPAIR |
| 分区操作 | SQL 原生支持 | 需 ALTER TABLE |
| 并发写入 | 支持分区级并发 | 有限支持 |
3.5.2 分区键的作用
分区键的核心作用:
| 作用 | 说明 |
|---|---|
| 查询优化 | 查询条件包含分区键时,只扫描相关分区 |
| 数据管理 | 可以按分区进行过期清理、归档等操作 |
| 写入隔离 | 不同分区的写入互不影响,提升并发 |
分区裁剪示例:
sql
-- 全表扫描(扫描所有分区)
SELECT * FROM user_table WHERE user_id = 123;
-- 分区裁剪(只扫描 dt='2024-01-01' 分区)
SELECT * FROM user_table
WHERE user_id = 123 AND dt = '2024-01-01';
3.5.3 分区操作的优势
| 操作 | 说明 |
|---|---|
| 分区覆盖 | INSERT OVERWRITE ... PARTITION (dt='2024-01-01') 覆盖指定分区 |
| 分区删除 | 直接删除分区目录,快速清理数据 |
| 分区归档 | 将旧分区移动到低成本存储 |
3.5.4 分区设计最佳实践
| 原则 | 建议 |
|---|---|
| 分区粒度 | 避免过细(小文件过多)或过粗(裁剪效果差) |
| 时间分区 | 推荐按天分区,数据量大时可按小时 |
| 分区数量 | 单表分区数建议控制在 10,000 以内 |
| 分区字段 | 选择查询频率高、基数适中的字段 |
反模式示例:
sql
-- ❌ 分区过细:每个用户一个分区
PARTITIONED BY (user_id) -- 用户数可能百万级
-- ❌ 分区字段基数过大
PARTITIONED BY (city) -- 城市数可能上千
-- ✅ 推荐:按天分区
PARTITIONED BY (dt) -- 每天一个分区,可控
3.6 一致性保证
3.6.1 两阶段提交协议(2PC)
Paimon 通过两阶段提交(2PC)保证写入的原子性:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Writer │ │ Manifest │ │ Snapshot │
│ │ │ (Stage 1) │ │ (Stage 2) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ 1. 写入数据文件 │ │
│─────────────────>│ │
│ │ │
│ 2. 写入 Manifest │ │
│ │ │
│ 3. 原子提交 │ │
│─────────────────>│ │
│ │ 4. 更新快照 │
│ │─────────────────>│
│ │ │
│ │ │ 5. 快照可见
两阶段提交流程:
- Stage 1(准备):写入数据文件和 Manifest 文件
- Stage 2(提交):原子性地更新 Snapshot,使新数据可见
如果 Stage 2 失败,Stage 1 写入的文件不会被任何快照引用,后续会被清理。
3.6.2 原子性提交
Paimon 保证快照级别的原子性:
- 读者要么看到提交前的旧快照,要么看到提交后的新快照
- 不会看到"部分提交"的中间状态
- 多文件写入要么全部可见,要么全部不可见
3.6.3 快照隔离(Snapshot Isolation)
Paimon 提供快照隔离级别的一致性保证:
| 特性 | 说明 |
|---|---|
| 读写不阻塞 | 读者读取快照,写者提交新快照,互不阻塞 |
| 可重复读 | 同一事务内多次读取同一快照,结果一致 |
| 无脏读 | 读者只能看到已提交的快照 |
多读者并发示例:
时间轴 →
Writer: [Commit Snapshot 100] [Commit Snapshot 101]
│ │
Reader A: ├───── 读取 Snapshot 100 ─────┤
Reader B: ├───── 读取 Snapshot 101
Reader A 和 Reader B 可以同时读取不同快照,互不干扰。
3.6.4 多 Writer 并发控制
Paimon 支持多 Writer 并发写入,通过以下机制保证一致性:
| 机制 | 说明 |
|---|---|
| 乐观并发 | Writer 之间不互相加锁,提交时检查冲突 |
| 提交版本号 | 每个提交有唯一标识,冲突时拒绝后提交的事务 |
| 自动重试 | Flink 等引擎会自动重试失败的提交 |
冲突检测:
- 如果两个 Writer 同时修改同一分区/桶的数据
- 后提交的 Writer 会检测到快照已更新
- 提交失败,触发重试或回滚
本章小结
核心要点回顾
- 文件布局:Paimon 采用分层组织(Snapshot → Manifest → Data),支持高效访问
- 快照机制:每个快照代表一致性视图,支持时间旅行和增量消费
- Manifest 文件:记录数据文件变更,支持增量追踪和查询优化
- 数据文件:按分区和桶组织,支持 Parquet/ORC/Avro 多种格式
- 分区设计:类似 Hive,支持分区裁剪和分区级操作
- 一致性保证:通过 2PC 和快照隔离,保证原子性和并发安全
第四章:LSM Tree 存储引擎深度解析
4.1 LSM Tree 基础
4.1.1 LSM Tree 原理回顾
LSM Tree (Log-Structured Merge Tree)是一种针对写密集型场景优化的数据结构,由 Patrick O'Neil 等人于 1996 年提出。其核心思想是将随机写转换为顺序写,大幅提升写入吞吐量。
基本架构:
┌─────────────────────────────────────────────────────────────┐
│ LSM Tree 架构 │
│ ┌─────────────┐ │
│ │ MemTable │ ← 内存中的有序数据结构(跳表/红黑树) │
│ │ (内存) │ 写入先到 MemTable,满后 flush 到磁盘 │
│ └──────┬──────┘ │
│ │ flush │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ L0 │ │ L1 │ │ L2 │ │
│ │ SSTable │ │ SSTable │ │ SSTable │ │
│ │ (磁盘) │ │ (磁盘) │ │ (磁盘) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ Compaction (后台合并) │
└─────────────────────────────────────────────────────────────┘
核心组件:
| 组件 | 作用 | 存储位置 |
|---|---|---|
| MemTable | 内存中的有序数据结构,接收新写入 | 内存 |
| Immutable MemTable | 满后的 MemTable,等待 flush | 内存 |
| SSTable | 有序字符串表,不可变的磁盘文件 | 磁盘 |
| WAL | 预写日志,防止数据丢失 | 磁盘 |
写入流程:
- 新数据写入 WAL(Write-Ahead Log),保证持久性
- 数据插入 MemTable(内存有序结构)
- MemTable 达到阈值后,转为 Immutable MemTable
- 后台线程将 Immutable MemTable flush 到磁盘,生成 L0 SSTable
- 当某层 SSTable 数量超过阈值,触发 Compaction,合并到下一层
读取流程:
- 先在 MemTable 中查找
- 未命中则从 L0 → L1 → L2 ... 逐层查找
- 每层使用 Bloom Filter 加速判断数据是否存在
- 合并所有层找到的数据,返回最新版本
4.1.2 为什么选择 LSM Tree
Paimon 选择 LSM Tree 作为底层存储引擎,主要基于以下考虑:
1. 高吞吐写入
| 存储引擎 | 写入模式 | 吞吐量 |
|---|---|---|
| B+ Tree | 随机写 | 较低 |
| LSM Tree | 顺序写 | 高 |
传统 B+ Tree 每次写入都需要随机访问磁盘,而 LSM Tree 将写入转换为顺序追加,大幅提升写入性能。
2. 支持大规模数据更新
数据湖场景下,CDC 同步会产生大量的更新操作。LSM Tree 通过追加写 + 版本合并的方式,高效处理更新:
更新操作:UPDATE table SET value = 'new' WHERE id = 1
LSM Tree 处理方式:
┌─────────────────────────────────────────┐
│ MemTable: [id=1, value='new', ts=100] │ ← 新值追加
│ L0 SSTable: [id=1, value='old', ts=50] │ ← 旧值保留
│ L1 SSTable: [id=1, value='older', ts=10]│ ← 历史版本
└─────────────────────────────────────────┘
读取时:返回最新版本(ts=100)
Compaction 时:清理旧版本
3. 高性能查询
- Bloom Filter:快速判断数据是否存在,避免无效磁盘 IO
- 分层结构:热数据在上层,冷数据在下层,优化读取性能
- Compaction:后台合并小文件,减少查询时需要扫描的文件数量
4.1.3 与传统 LSM 的区别
Paimon 的 LSM 实现在传统 LSM Tree 基础上进行了优化:
| 特性 | 传统 LSM | Paimon LSM |
|---|---|---|
| 数据组织 | 按 Key 排序 | 按分区 + 桶 + Key 组织 |
| Compaction | 层级合并 | 支持 Minor/Major/Full 多种策略 |
| 快照支持 | 无 | 基于 Manifest 的快照隔离 |
| 流式消费 | 不支持 | 基于 Changelog 的流式读取 |
| 多版本 | 有限支持 | 完整的时间旅行支持 |
Paimon vs Iceberg vs Hudi - LSM 实现对比
| 特性 | Paimon | Iceberg | Hudi |
|---|---|---|---|
| 底层引擎 | 自研 LSM | 无(依赖底层存储) | 自研 LSM |
| 更新支持 | ✅ 原生支持 | ⚠️ 有限支持 | ✅ 原生支持 |
| Compaction | ✅ 自动 + 手动 | ❌ 无 | ✅ 自动 + 手动 |
| 流式消费 | ✅ 完整支持 | ⚠️ 有限支持 | ✅ 支持 |
| 快照隔离 | ✅ 完整支持 | ✅ 完整支持 | ✅ 完整支持 |
4.2 Paimon 的 LSM 实现
4.2.1 MemTable 机制
MemTable 结构:
┌─────────────────────────────────────────────────────────────┐
│ Paimon MemTable │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Skip List (跳表) │ │
│ │ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ │
│ │ │ id=1 │→ │ id=3 │→ │ id=5 │→ │ id=7 │ ... │ │
│ │ │ v1,t1 │ │ v2,t2 │ │ v3,t3 │ │ v4,t4 │ │ │
│ │ └───────┘ └───────┘ └───────┘ └───────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 阈值配置: │
│ - memtable-size: 256 MB(默认) │
│ - num-sorted-run-compaction-trigger: 5 │
└─────────────────────────────────────────────────────────────┘
MemTable 配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
memtable-size |
256 MB |
MemTable 最大内存占用 |
write-buffer-size |
256 MB |
写入缓冲区大小 |
num-sorted-run-compaction-trigger |
5 |
触发 Compaction 的 Sorted Run 数量 |
Flush 流程:
1. MemTable 达到阈值
↓
2. 标记为 Immutable MemTable(不再接收新写入)
↓
3. 创建新的 MemTable 接收后续写入
↓
4. 后台线程将 Immutable MemTable flush 到磁盘
↓
5. 生成 L0 SSTable 文件
4.2.2 SSTable 文件组织
SSTable 结构:
┌─────────────────────────────────────────────────────────────┐
│ SSTable 文件格式 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Data Block │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Key-Value │ │ Key-Value │ │ Key-Value │ ... │ │
│ │ │ (有序) │ │ (有序) │ │ (有序) │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Index Block │ │
│ │ (Key 范围索引,用于快速定位 Data Block) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Bloom Filter │ │
│ │ (快速判断 Key 是否存在,减少无效磁盘 IO) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Meta Block │ │
│ │ (文件元信息:最小 Key、最大 Key、记录数等) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Paimon SSTable 特点:
- 按分区和桶组织:每个分区 + 桶组合有独立的 SSTable 文件
- 支持多种格式:Parquet(默认)、ORC、Avro
- 包含统计信息:最小/最大 Key、记录数、文件大小等
Bloom Filter 原理:
Bloom Filter 是一种概率型数据结构,用于快速判断元素是否存在于集合中。
| 特性 | 说明 |
|---|---|
| 优点 | 空间效率高,查询速度快 |
| 缺点 | 存在误判(可能将不存在的元素判断为存在) |
| 误判率 | 可配置(默认 1%) |
| 作用 | 避免无效的磁盘 IO,提升查询性能 |
Paimon 中,每个 SSTable 文件都包含 Bloom Filter,查询时先检查 Bloom Filter,
如果判断不存在,则无需读取该文件。
4.2.3 Level 结构设计
Paimon 分层结构:
┌─────────────────────────────────────────────────────────────┐
│ Paimon Level 结构 │
│ ┌─────────────┐ │
│ │ Level 0 │ ← MemTable flush 生成,文件间 Key 范围可能重叠 │
│ │ (SSTable) │ 数量阈值:4(可配置) │
│ └──────┬──────┘ │
│ │ Compaction │
│ ▼ │
│ ┌─────────────┐ │
│ │ Level 1 │ ← Level 0 Compaction 生成,文件间 Key 不重叠 │
│ │ (SSTable) │ 数量阈值:8(可配置) │
│ └──────┬──────┘ │
│ │ Compaction │
│ ▼ │
│ ┌─────────────┐ │
│ │ Level 2 │ ← Level 1 Compaction 生成 │
│ │ (SSTable) │ 数量阈值:16(可配置) │
│ └──────┬──────┘ │
│ │ ... │
│ ▼ │
│ ┌─────────────┐ │
│ │ Level N │ ← 最底层,存储历史数据 │
│ │ (SSTable) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
层级特点对比:
| 层级 | Key 范围 | 文件大小 | 数量限制 |
|---|---|---|---|
| L0 | 可能重叠 | 较小 | 4 |
| L1+ | 不重叠 | 较大 | 逐级递增 |
4.2.4 与传统 LSM 的区别
| 特性 | 传统 LSM (RocksDB) | Paimon LSM |
|---|---|---|
| 应用场景 | 嵌入式 KV 存储 | 分布式数据湖 |
| 数据组织 | 全局 Key 排序 | 分区 + 桶 + Key |
| 文件格式 | 专有 SST 格式 | Parquet/ORC/Avro |
| 快照支持 | 有限 | 完整的快照隔离 |
| 多引擎支持 | 单引擎 | Flink/Spark/OLAP |
4.3 Compaction 策略
4.3.1 Minor Compaction
触发条件:
- L0 SSTable 数量达到阈值(默认 4 个)
- MemTable flush 后触发
合并方式:
Compaction 前:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ L0-1 │ │ L0-2 │ │ L0-3 │ │ L0-4 │
│ (SSTable) │ │ (SSTable) │ │ (SSTable) │ │ (SSTable) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
▼ Compaction
│
┌─────────────────────────────────────────────────────────────┐
│ Level 1 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ L1-1 │ │ L1-2 │ │ L1-3 │ ... │
│ │ (SSTable) │ │ (SSTable) │ │ (SSTable) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
特点:
- 只合并 L0 层到 L1 层
- 合并速度快,影响范围小
- 减少 L0 层文件数量,优化查询性能
4.3.2 Major Compaction
触发条件:
- 某层 SSTable 数量超过阈值
- 手动触发(通过
ALTER TABLE ... COMPACT)
合并方式:
Compaction 前:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ L1-1 │ │ L1-2 │ │ L1-3 │
│ (SSTable) │ │ (SSTable) │ │ (SSTable) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼ Compaction
│
┌─────────────────────────────────────────────────────────────┐
│ Level 2 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ L2-1 │ │ L2-2 │ ... │
│ │ (SSTable) │ │ (SSTable) │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
特点:
- 合并相邻层级(如 L1 → L2)
- 合并数据量较大,耗时较长
- 显著减少文件数量,提升查询性能
4.3.3 Full Compaction
触发条件:
- 手动触发(通过
ALTER TABLE ... COMPACT FULL) - 定期维护(如每周一次)
合并方式:
Compaction 前:
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ L0 │ │ L1 │ │ L2 │ │ L3 │ │ L4 │ │ L5 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
│
▼ Full Compaction
│
┌─────────────────────────────────────────────────────────────┐
│ 合并后的表 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Optimized │ │ Optimized │ │ Optimized │ │
│ │ SSTable │ │ SSTable │ │ SSTable │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
特点:
- 合并所有层级
- 耗时最长,影响范围最大
- 最佳查询性能,最小文件数量
4.3.4 Compaction 触发条件
自动触发配置:
sql
-- 创建表时配置 Compaction 参数
CREATE TABLE my_table (
id BIGINT,
value STRING,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
-- 触发 Minor Compaction 的 L0 文件数阈值
'num-sorted-run-compaction-trigger' = '4',
-- 每个层级的最大文件数
'num-levels' = '5',
-- 每层的目标文件大小
'target-file-size' = '128 MB',
-- Compaction 最大并行度
'compaction.max-parallelism' = '4'
);
核心配置参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
num-sorted-run-compaction-trigger |
4 |
触发 Compaction 的 Sorted Run 数量 |
num-levels |
5 |
LSM Tree 的层级数量 |
target-file-size |
128 MB |
Compaction 后的目标文件大小 |
compaction.max-parallelism |
4 |
Compaction 最大并行度 |
full-compaction.delta-commits |
100 |
每 N 次提交触发一次 Full Compaction |
4.3.5 性能影响分析
Compaction 对写入性能的影响:
| Compaction 类型 | 写入影响 | 频率 |
|---|---|---|
| Minor | 较小 | 高 |
| Major | 中等 | 中 |
| Full | 较大 | 低 |
Compaction 对查询性能的影响:
Compaction 前:
- L0 文件数:8 个
- 查询需要扫描:8 个文件
- 查询延迟:较高
Compaction 后:
- L0 文件数:2 个
- 查询需要扫描:2 个文件
- 查询延迟:降低 75%
最佳实践:
- 监控 Compaction 频率:过于频繁说明写入压力大,需要调整参数
- 避免 Full Compaction 高峰:在业务低峰期执行
- 合理设置目标文件大小:过大影响查询,过小增加文件数
Compaction 性能调优建议:
| 场景 | 问题 | 调优建议 |
|---|---|---|
| 写入吞吐低 | Compaction 过于频繁 | 增加 num-sorted-run-compaction-trigger |
| 查询延迟高 | L0 文件数过多 | 降低 target-file-size,增加 Compaction 频率 |
| 磁盘空间不足 | 历史版本过多 | 启用 full-compaction.delta-commits |
| CPU 占用高 | Compaction 并行度过高 | 降低 compaction.max-parallelism |
推荐配置(中等规模集群):
'num-sorted-run-compaction-trigger' = '4'
'target-file-size' = '128 MB'
'compaction.max-parallelism' = '4'
'full-compaction.delta-commits' = '100'
4.4 主键表(Primary Key Table)
4.4.1 主键表的特点
主键表定义:
主键表是 Paimon 中最常用的表类型,支持基于主键的更新 和删除操作。
sql
-- 创建主键表
CREATE TABLE user_table (
user_id BIGINT,
user_name STRING,
age INT,
city STRING,
update_time TIMESTAMP(3),
PRIMARY KEY (user_id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'changelog-producer' = 'input'
);
核心特点:
| 特点 | 说明 |
|---|---|
| 主键唯一 | 主键值唯一标识一条记录 |
| 支持 Upsert | 根据主键判断插入或更新 |
| 支持删除 | 根据主键删除记录 |
| 版本管理 | 每次更新生成新版本 |
4.4.2 基于主键的更新语义
更新流程:
原始数据:
┌─────────────┐
│ id=1, v='A' │
└─────────────┘
更新操作:UPDATE user_table SET value = 'B' WHERE id = 1
LSM Tree 处理:
┌─────────────────────────────────────────┐
│ MemTable: [id=1, value='B', ts=100] │ ← 新值追加
│ L0 SSTable: [id=1, value='A', ts=50] │ ← 旧值保留
└─────────────────────────────────────────┘
读取结果:返回最新版本(value='B')
Compaction 后:旧版本被清理
更新模式配置:
| 模式 | 配置 | 说明 |
|---|---|---|
| Upsert | write-mode='upsert'(默认) |
根据主键更新 |
| Append | write-mode='append' |
仅追加,忽略主键 |
4.4.3 Upsert 操作实现
Flink SQL Upsert 示例:
sql
-- 创建 Upsert 表
CREATE TABLE upsert_sink (
id BIGINT,
value STRING,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'paimon',
'write-mode' = 'upsert'
);
-- Upsert 写入(INSERT 自动判断插入或更新)
INSERT INTO upsert_sink
SELECT id, value, update_time FROM source_table;
底层实现:
1. Flink Sink 接收数据流
↓
2. 根据主键哈希分配到对应桶
↓
3. 检查 MemTable 中是否存在相同主键
↓
4. 存在 → 更新值;不存在 → 插入新记录
↓
5. MemTable flush 时,生成 SSTable
4.4.4 去重机制
去重场景:
在 CDC 同步场景中,同一条记录可能多次同步,需要去重。
配置去重:
sql
CREATE TABLE dedup_table (
id BIGINT,
value STRING,
ts TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'bucket' = '4',
-- 启用去重,保留最新值
'changelog-producer' = 'input'
);
去重策略:
| 策略 | 配置 | 说明 |
|---|---|---|
| 保留最新 | changelog-producer='input' |
保留时间戳最新的记录 |
| 保留最早 | 需要自定义逻辑 | 保留时间戳最早的记录 |
| 全部保留 | changelog-producer='lookup' |
保留所有版本 |
完整去重配置示例:
sql
-- CDC 同步去重场景
CREATE TABLE dedup_table (
id BIGINT,
value STRING,
ts TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
-- 启用 Changelog Producer,自动去重
'changelog-producer' = 'input',
-- 可选:配置去重策略
'merge-engine' = 'deduplicate' -- 保留最新值
);
-- 去重策略选项:
-- 'deduplicate': 保留最新值(默认)
-- 'partial-update': 部分字段更新
-- 'first-row': 保留第一行
-- 'last-row': 保留最后一行
4.5 追加表(Append-Only Table)
4.5.1 追加表的适用场景
追加表定义:
追加表不支持更新和删除操作,仅支持追加新数据。适用于日志收集、事件流等场景。
sql
-- 创建追加表
CREATE TABLE event_log (
event_type STRING,
event_data STRING,
ts TIMESTAMP(3),
dt STRING
) PARTITIONED BY (dt) WITH (
'bucket' = '4',
'write-mode' = 'append'
);
典型场景:
| 场景 | 说明 | 示例 |
|---|---|---|
| 日志收集 | 仅追加,不更新 | 应用日志、访问日志 |
| 事件流 | 事件不可变 | 用户行为事件、IoT 传感器数据 |
| 归档数据 | 历史数据归档 | 历史订单、历史交易记录 |
4.5.2 与主键表的区别
| 特性 | 主键表 | 追加表 |
|---|---|---|
| 主键 | 必需 | 可选 |
| 更新 | 支持 | 不支持 |
| 删除 | 支持 | 不支持 |
| 写入性能 | 较高 | 更高 |
| 存储开销 | 较高(需维护版本) | 较低 |
| 适用场景 | CDC 同步、实时数仓 | 日志收集、事件流 |
4.5.3 性能优势
写入性能对比:
主键表写入流程:
1. 检查主键是否存在 → 2. 判断插入/更新 → 3. 写入 MemTable
追加表写入流程:
1. 直接写入 MemTable
性能提升:约 20-30%
存储开销对比:
主键表:需要维护多个版本,Compaction 前存储开销较大
追加表:无需维护版本,存储开销较小
存储节省:约 30-50%
4.6 Changelog 文件
4.6.1 Changelog 的作用
Changelog 定义:
Changelog 文件记录数据的变更历史,支持流式读取和时间旅行。
核心作用:
| 作用 | 说明 |
|---|---|
| 流式消费 | Flink 流式 Source 读取 Changelog 实现增量消费 |
| 数据回溯 | 通过 Changelog 回溯数据变更历史 |
| CDC 同步 | 将 Changelog 同步到下游系统 |
4.6.2 Changelog 文件格式
Changelog 记录结构:
┌─────────────────────────────────────────────────────────────┐
│ Changelog 记录格式 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Op Type │ │ Data │ │ Timestamp │ │
│ │ (I/U/D) │ │ (Row Data) │ │ (ts) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Op Type:
- I (Insert): 插入操作
- U (Update): 更新操作(包含 before 和 after)
- D (Delete): 删除操作
Changelog 文件示例:
json
{
"op": "U",
"before": {"id": 1, "value": "A", "ts": 100},
"after": {"id": 1, "value": "B", "ts": 200},
"timestamp": 1704067200000
}
4.6.3 流式读取支持
Flink 流式读取示例:
sql
-- 启用 Changelog Producer
CREATE TABLE paimon_source (
id BIGINT,
value STRING,
ts TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'paimon',
'changelog-producer' = 'input'
);
-- 流式读取 Changelog
SET execution.runtime-mode = 'streaming';
SELECT * FROM paimon_source;
Changelog Producer 配置:
| 配置 | 说明 |
|---|---|
changelog-producer='input' |
根据输入生成 Changelog(推荐) |
changelog-producer='lookup' |
通过 lookup 生成 Changelog(性能较低) |
changelog-producer='none' |
不生成 Changelog |
Changelog 文件大小控制:
| 参数 | 默认值 | 说明 |
|---|---|---|
changelog-producer |
none |
启用后生成 Changelog |
log.retention |
7d |
Changelog 保留时间 |
log.max-size |
1 GB |
单个 Changelog 文件最大大小 |
注意:Changelog 文件会占用额外存储空间,建议根据实际需求配置保留策略。
对于不需要流式消费的场景,可以禁用 Changelog Producer 以节省空间。
本章小结
核心要点回顾
- LSM Tree 基础:将随机写转换为顺序写,大幅提升写入吞吐量
- Paimon 的 LSM 实现:MemTable + SSTable + 分层结构,支持分区和桶
- Compaction 策略:Minor/Major/Full 三种合并策略,平衡写入和查询性能
- 主键表:支持 Upsert 和删除,适用于 CDC 同步和实时数仓
- 追加表:仅追加,性能更高,适用于日志收集和事件流
- Changelog 文件:记录变更历史,支持流式读取和时间旅行
第五章:多引擎支持 - Apache Paimon 全面集成实战
5.1 多引擎支持概览
Apache Paimon 的核心优势之一是多引擎支持。作为流式湖仓存储格式,Paimon 不仅与 Flink 深度集成,还支持 Spark、StarRocks、Doris、Trino、Presto 等多种计算引擎,真正实现"一次写入,多处查询"。
5.1.1 支持的计算引擎
| 引擎 | 支持类型 | 成熟度 | 主要场景 |
|---|---|---|---|
| Flink | 读写 | ⭐⭐⭐⭐⭐ | 实时写入、流式查询、CDC 同步 |
| Spark | 读写 | ⭐⭐⭐⭐ | 批量处理、ETL、数据分析 |
| StarRocks | 读 | ⭐⭐⭐⭐ | 即席查询、OLAP 分析 |
| Doris | 读 | ⭐⭐⭐⭐ | 即席查询、报表分析 |
| Trino | 读 | ⭐⭐⭐ | 联邦查询、数据探索 |
| Presto | 读 | ⭐⭐⭐ | 交互式查询 |
| Hive | 读 | ⭐⭐⭐ | 批量查询、历史数据分析 |
5.1.2 多引擎架构
┌─────────────────────────────────────────────────────────────────┐
│ 计算引擎层 │
├──────────┬──────────┬──────────┬──────────┬──────────┬──────────┤
│ Flink │ Spark │StarRocks │ Doris │ Trino │ Hive │
│ 读/写 │ 读/写 │ 读 │ 读 │ 读 │ 读 │
└────┬─────┴────┬─────┴────┬─────┴────┬─────┴────┬─────┴────┬─────┘
│ │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┘
│
┌─────────▼─────────┐
│ Apache Paimon │
│ 存储格式层 │
└─────────┬─────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
┌────▼────┐ ┌──────▼──────┐ ┌─────▼─────┐
│ HDFS │ │ S3/OSS │ │ K8s PV │
│ 分布式 │ │ 对象存储 │ │ 本地存储 │
└─────────┘ └─────────────┘ └───────────┘
5.2 Flink 集成(深度)
Flink 是 Paimon 的首选计算引擎,提供最全的功能支持和最优的性能表现。
5.2.1 Flink 连接器配置
xml
<!-- Maven 依赖 -->
<dependency>
<groupId>org.apache.paimon</groupId>
<artifactId>paimon-flink-1.18</artifactId>
<version>1.3.0</version>
</dependency>
java
// Flink SQL 环境配置
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
// 创建 Paimon Catalog
Map<String, String> catalogProps = new HashMap<>();
catalogProps.put("type", "paimon");
catalogProps.put("warehouse", "hdfs://namenode:9000/paimon");
catalogProps.put("fs.defaultFS", "hdfs://namenode:9000");
tableEnv.executeSql(
"CREATE CATALOG paimon WITH (" +
" 'type' = 'paimon'," +
" 'warehouse' = 'hdfs://namenode:9000/paimon'" +
")"
);
// 使用 Catalog
tableEnv.useCatalog("paimon");
5.2.2 流式写入示例
sql
-- 创建源表(Kafka CDC)
CREATE TABLE source_orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'properties.bootstrap.servers' = 'kafka:9092',
'scan.startup.mode' = 'earliest-offset',
'format' = 'debezium-json'
);
-- 创建 Paimon 目标表
CREATE TABLE IF NOT EXISTS paimon_catalog.paimon_db.orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (order_time) WITH (
'bucket' = '4',
'changelog-producer' = 'lookup'
);
-- 流式写入
INSERT INTO paimon_catalog.paimon_db.orders
SELECT order_id, customer_id, amount, order_time
FROM source_orders;
5.2.3 流式查询示例
sql
-- 实时消费最新数据
SELECT * FROM paimon_catalog.paimon_db.orders
/*+ OPTIONS('streaming' = 'true') */;
-- 增量查询(从指定时间点开始)
SELECT * FROM paimon_catalog.paimon_db.orders
/*+ OPTIONS(
'streaming' = 'true',
'scan.mode' = 'from-timestamp',
'scan.timestamp-millis' = '1710000000000'
) */;
-- 实时聚合
SELECT
DATE_FORMAT(order_time, 'HH:00') AS hour,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM paimon_catalog.paimon_db.orders
/*+ OPTIONS('streaming' = 'true') */
GROUP BY DATE_FORMAT(order_time, 'HH:00');
5.3 Spark 集成
Spark 提供强大的批量处理能力,适合 ETL、离线分析等场景。
5.3.1 Spark 连接器配置
xml
<!-- Maven 依赖 -->
<dependency>
<groupId>org.apache.paimon</groupId>
<artifactId>paimon-spark-3.4</artifactId>
<version>1.3.0</version>
</dependency>
bash
# spark-submit 参数
--packages org.apache.paimon:paimon-spark-3.4:1.3.0
--conf spark.sql.extensions=org.apache.paimon.spark.PaimonSparkSessionExtensions
--conf spark.sql.catalog.paimon=org.apache.paimon.spark.PaimonCatalog
--conf spark.sql.catalog.paimon.warehouse=hdfs://namenode:9000/paimon
5.3.2 Spark SQL 示例
sql
-- 创建表
CREATE TABLE paimon.paimon_db.user_behavior (
user_id BIGINT,
action STRING,
action_time TIMESTAMP,
properties MAP<STRING, STRING>
) USING paimon
PARTITIONED BY (action)
TBLPROPERTIES (
'primary-key' = 'user_id,action_time',
'bucket' = '8'
);
-- 批量写入
INSERT INTO paimon.paimon_db.user_behavior
SELECT
user_id,
action,
action_time,
map('source', source, 'device', device) AS properties
FROM source_db.raw_behavior
WHERE action_time >= '2026-03-01';
-- 批量查询
SELECT
action,
COUNT(*) AS action_count,
COUNT(DISTINCT user_id) AS unique_users
FROM paimon.paimon_db.user_behavior
WHERE action_time >= '2026-03-01'
GROUP BY action;
-- 时间旅行查询
SELECT * FROM paimon.paimon_db.user_behavior
TIMESTAMP AS OF '2026-03-09 12:00:00';
5.3.3 Spark DataFrame API
python
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("Paimon Example") \
.config("spark.sql.extensions", "org.apache.paimon.spark.PaimonSparkSessionExtensions") \
.config("spark.sql.catalog.paimon", "org.apache.paimon.spark.PaimonCatalog") \
.config("spark.sql.catalog.paimon.warehouse", "hdfs://namenode:9000/paimon") \
.getOrCreate()
# 读取数据
df = spark.read.table("paimon.paimon_db.user_behavior")
# 过滤和聚合
result = df.filter(df.action_time >= "2026-03-01") \
.groupBy("action") \
.agg({"user_id": "count", "action_time": "count"})
# 写入数据
df.write \
.format("paimon") \
.mode("append") \
.saveAsTable("paimon.paimon_db.user_behavior_agg")
5.4 OLAP 引擎集成(StarRocks/Doris)
StarRocks 和 Doris 提供高性能即席查询能力,适合 BI 报表、数据探索场景。
5.4.1 StarRocks External Catalog
sql
-- 在 StarRocks 中创建 Paimon External Catalog
CREATE EXTERNAL CATALOG paimon_catalog PROPERTIES (
"type" = "paimon",
"paimon.catalog.type" = "filesystem",
"paimon.catalog.warehouse" = "hdfs://namenode:9000/paimon",
"hadoop.username" = "hdfs_user"
);
-- 查询 Paimon 表
SELECT * FROM paimon_catalog.paimon_db.orders
WHERE order_date >= '2026-03-01';
-- 关联查询
SELECT
o.order_id,
o.amount,
c.customer_name,
c.region
FROM paimon_catalog.paimon_db.orders o
JOIN starrocks_db.customer c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2026-03-01';
5.4.2 Doris External Table
sql
-- 在 Doris 中创建 Paimon External Table
CREATE EXTERNAL TABLE paimon_orders (
order_id BIGINT,
customer_id BIGINT,
amount DECIMAL(10, 2),
order_time DATETIME
) ENGINE = PAIMON
PROPERTIES (
"warehouse" = "hdfs://namenode:9000/paimon",
"database" = "paimon_db",
"table" = "orders"
);
-- 查询
SELECT
DATE_FORMAT(order_time, '%Y-%m-%d') AS date,
COUNT(*) AS order_count,
SUM(amount) AS gmv
FROM paimon_orders
WHERE order_time >= '2026-03-01'
GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d');
5.5 Presto/Trino 集成
Presto 和 Trino 提供联邦查询能力,可同时查询多个数据源。
5.5.1 Trino Paimon Connector
properties
# etc/catalog/paimon.properties
connector.name=paimon
paimon.warehouse=hdfs://namenode:9000/paimon
paimon.hadoop.config-dir=/etc/hadoop/conf
sql
-- 查询 Paimon 表
SELECT * FROM paimon.paimon_db.orders
WHERE order_time >= TIMESTAMP '2026-03-01';
-- 联邦查询(Paimon + MySQL)
SELECT
p.order_id,
p.amount,
m.customer_name
FROM paimon.paimon_db.orders p
JOIN mysql.shop.customer m ON p.customer_id = m.customer_id
WHERE p.order_time >= TIMESTAMP '2026-03-01';
5.6 多引擎数据一致性
5.6.1 快照隔离
Paimon 通过快照机制保证多引擎读取的数据一致性:
引擎 A 读取快照 100 ──┐
├── 数据一致
引擎 B 读取快照 100 ──┘
引擎 C 写入 ──> 创建快照 101(不影响正在读取快照 100 的引擎)
5.6.2 读写并发控制
| 场景 | 引擎 A | 引擎 B | 结果 |
|---|---|---|---|
| 同时读 | 读快照 N | 读快照 N | ✅ 一致 |
| 读 + 写 | 读快照 N | 写入创建快照 N+1 | ✅ 互不阻塞 |
| 同时写 | 写入创建快照 N+1 | 写入创建快照 N+2 | ✅ 顺序提交 |
5.6.3 最佳实践
sql
-- 1. 显式指定快照读取(保证可重复读)
SELECT * FROM paimon_catalog.paimon_db.orders
/*+ OPTIONS('scan.snapshot-id' = '100') */;
-- 2. 使用增量读取减少数据量
SELECT * FROM paimon_catalog.paimon_db.orders
/*+ OPTIONS(
'scan.mode' = 'incremental',
'scan.incremental.snapshot-id' = '100'
) */;
-- 3. 避免长事务阻塞
-- 写入作业设置合理的 checkpoint 间隔
'execution.checkpointing.interval' = '60s'
5.7 性能优化建议
5.7.1 引擎选择指南
| 场景 | 推荐引擎 | 理由 |
|---|---|---|
| 实时 CDC 同步 | Flink | 原生支持,延迟最低 |
| 流式聚合 | Flink | 状态管理完善 |
| 批量 ETL | Spark | 吞吐量大,生态丰富 |
| BI 报表 | StarRocks/Doris | 查询性能最优 |
| 数据探索 | Trino/Presto | 联邦查询能力强 |
| 历史分析 | Hive | 成本低,兼容性好 |
5.7.2 通用优化策略
sql
-- 1. 合理设置 Bucket 数
-- 根据数据量和并发度调整
'bucket' = '数据量(GB) / 2'
-- 2. 启用 Compaction
'changelog-producer' = 'lookup'
'compaction.max-file-num' = '30'
-- 3. 使用分区裁剪
SELECT * FROM table WHERE partition_column = 'value';
-- 4. 利用索引
-- Paimon 自动维护主键索引和统计信息
5.8 本章总结
核心要点
- 多引擎支持是 Paimon 的核心优势,实现"一次写入,多处查询"
- Flink是首选引擎,提供最完整的功能支持
- Spark 适合批量 ETL,StarRocks/Doris适合 OLAP 查询
- 快照机制保证多引擎读取的数据一致性
- 根据场景选择合适的引擎,发挥各自优势
关键代码
sql
-- Flink 流式写入
INSERT INTO paimon_catalog.db.table SELECT * FROM kafka_source;
-- Spark 批量查询
SELECT * FROM paimon.db.table WHERE date >= '2026-03-01';
-- StarRocks 即席查询
SELECT COUNT(*) FROM paimon_catalog.db.table;
第六章:高级特性与优化 - Apache Paimon 生产级实践
6.1 Schema 演化
Schema 演化是数据湖的核心能力之一。Paimon 支持多种 Schema 变更操作,无需重建表或停止写入。
6.1.1 支持的变更类型
| 变更类型 | 支持 | 说明 | 示例 |
|---|---|---|---|
| 添加列 | ✅ | 在任意位置添加新列 | ALTER TABLE ADD COLUMN |
| 删除列 | ✅ | 删除现有列 | ALTER TABLE DROP COLUMN |
| 重命名列 | ✅ | 修改列名 | ALTER TABLE RENAME COLUMN |
| 修改类型 | ✅ | 兼容类型转换 | ALTER TABLE MODIFY COLUMN |
| 调整顺序 | ✅ | 修改列顺序 | ALTER TABLE MODIFY COLUMN |
| 添加主键 | ⚠️ | 需确保数据唯一性 | 谨慎操作 |
| 修改分区 | ⚠️ | 影响文件组织 | 需重新组织数据 |
6.1.2 Flink SQL 示例
sql
-- 原始表
CREATE TABLE users (
user_id BIGINT,
name STRING,
age INT,
PRIMARY KEY (user_id) NOT ENFORCED
) WITH (...);
-- 1. 添加列(推荐:在末尾添加)
ALTER TABLE users ADD COLUMN email STRING;
ALTER TABLE users ADD COLUMN created_at TIMESTAMP(3);
-- 2. 添加列(指定位置)
ALTER TABLE users ADD COLUMN phone STRING AFTER name;
-- 3. 删除列
ALTER TABLE users DROP COLUMN age;
-- 4. 重命名列
ALTER TABLE users RENAME COLUMN name TO user_name;
-- 5. 修改类型(兼容转换)
ALTER TABLE users MODIFY COLUMN user_id STRING;
-- 6. 多操作合并
ALTER TABLE users
ADD COLUMN address STRING,
DROP COLUMN email,
MODIFY COLUMN user_name VARCHAR(100);
6.1.3 Spark SQL 示例
sql
-- 添加列
ALTER TABLE paimon.db.users ADD COLUMNS (email STRING COMMENT '用户邮箱');
-- 删除列
ALTER TABLE paimon.db.users DROP COLUMN age;
-- 修改列注释
ALTER TABLE paimon.db.users CHANGE COLUMN user_name user_name STRING COMMENT '用户名';
6.1.4 Schema 演化最佳实践
sql
-- ✅ 推荐:向后兼容的变更
-- 1. 只添加 nullable 列
ALTER TABLE orders ADD COLUMN discount DECIMAL(10, 2);
-- 2. 添加列时设置默认值(逻辑层面)
-- 应用层处理旧数据的默认值
-- 3. 避免删除正在使用的列
-- 先标记废弃,观察无引用后再删除
-- ⚠️ 谨慎:可能破坏兼容性的变更
-- 1. 修改列类型(确保兼容)
-- INT → BIGINT ✅ (安全)
-- STRING → INT ❌ (可能失败)
-- 2. 删除主键或分区列
-- 可能导致查询失败或性能下降
6.2 多表关联查询
6.2.1 维度表关联
sql
-- 事实表
CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
product_id BIGINT,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH ('bucket' = '8');
-- 维度表(用户)
CREATE TABLE users (
user_id BIGINT,
user_name STRING,
region STRING,
level STRING,
PRIMARY KEY (user_id) NOT ENFORCED
) WITH ('bucket' = '4', 'lookup.cache.max-rows' = '10000');
-- 维度表(商品)
CREATE TABLE products (
product_id BIGINT,
product_name STRING,
category STRING,
price DECIMAL(10, 2),
PRIMARY KEY (product_id) NOT ENFORCED
) WITH ('bucket' = '4');
-- 关联查询
SELECT
o.order_id,
u.user_name,
u.region,
p.product_name,
p.category,
o.amount,
o.order_time
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE o.order_time >= TIMESTAMP '2026-03-01';
6.2.2 时间范围关联
sql
-- 价格历史表(记录商品价格变化)
CREATE TABLE product_price_history (
product_id BIGINT,
price DECIMAL(10, 2),
valid_from TIMESTAMP(3),
valid_to TIMESTAMP(3),
PRIMARY KEY (product_id, valid_from) NOT ENFORCED
) WITH ('bucket' = '4');
-- 关联时匹配有效价格
SELECT
o.order_id,
o.product_id,
o.order_time,
o.amount,
ph.price AS historical_price
FROM orders o
JOIN product_price_history ph
ON o.product_id = ph.product_id
AND o.order_time >= ph.valid_from
AND o.order_time < COALESCE(ph.valid_to, CURRENT_TIMESTAMP);
6.2.3 增量关联优化
sql
-- 使用增量读取减少关联数据量
SELECT * FROM (
SELECT * FROM orders
/*+ OPTIONS('scan.mode' = 'incremental', 'scan.incremental.snapshot-id' = '100') */
) o
JOIN users u ON o.user_id = u.user_id;
6.3 CDC 多表同步
6.3.1 整库同步模式
java
// Flink CDC 整库同步
PaimonSyncActionBuilder action = FlinkPaimonSyncActionBuilder
.forDatabase("mysql", "shop_db")
.withPaimonCatalog(catalog)
.withTargetDatabase("paimon_db")
.withTablePrefix("ods_")
.build();
action.buildSync();
sql
-- 自动同步的表结构
-- MySQL: shop_db.users, shop_db.orders, shop_db.products
-- Paimon: paimon_db.ods_users, paimon_db.ods_orders, paimon_db.ods_products
6.3.2 多表聚合入湖
sql
-- 源表 1:订单表
CREATE TABLE mysql_orders (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3)
) WITH ('connector' = 'mysql-cdc', ...);
-- 源表 2:退款表
CREATE TABLE mysql_refunds (
refund_id BIGINT,
order_id BIGINT,
refund_amount DECIMAL(10, 2),
refund_time TIMESTAMP(3)
) WITH ('connector' = 'mysql-cdc', ...);
-- 目标表:聚合宽表
CREATE TABLE paimon_catalog.paimon_db.order_wide (
order_id BIGINT,
user_id BIGINT,
order_amount DECIMAL(10, 2),
refund_amount DECIMAL(10, 2),
net_amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
update_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH ('bucket' = '8', 'changelog-producer' = 'lookup');
-- 多表聚合同步
INSERT INTO paimon_catalog.paimon_db.order_wide
SELECT
o.order_id,
o.user_id,
o.amount AS order_amount,
COALESCE(r.refund_amount, 0) AS refund_amount,
o.amount - COALESCE(r.refund_amount, 0) AS net_amount,
o.order_time,
GREATEST(o.order_time, COALESCE(r.refund_time, o.order_time)) AS update_time
FROM mysql_orders o
LEFT JOIN mysql_refunds r ON o.order_id = r.order_id;
6.4 性能调优
6.4.1 写入优化
properties
# 1. 调整 Bucket 数(根据数据量)
# 小表(< 100GB): 4-8 buckets
# 中表(100GB-1TB): 16-32 buckets
# 大表(> 1TB): 64+ buckets
'bucket' = '16'
# 2. 调整 Record Size
'file.format' = 'orc' # 列式存储,适合分析
'file.format' = 'avro' # 行式存储,适合点查
# 3. 启用 Changelog Producer
'changelog-producer' = 'lookup' # 生成变更日志
'changelog-producer' = 'input' # 透传输入变更
# 4. 调整 Compaction 策略
'compaction.max-file-num' = '30'
'compaction.max-size-bytes' = '268435456' # 256MB
'compaction.early-trigger' = 'true'
6.4.2 查询优化
sql
-- 1. 利用分区裁剪
SELECT * FROM orders WHERE dt = '2026-03-10'; -- ✅ 只读一个分区
SELECT * FROM orders WHERE amount > 100; -- ❌ 全表扫描
-- 2. 利用主键索引
SELECT * FROM orders WHERE order_id = 12345; -- ✅ 主键点查
SELECT * FROM orders WHERE user_id = 67890; -- ❌ 全表扫描(user_id 非主键)
-- 3. 使用增量读取
SELECT * FROM orders
/*+ OPTIONS('scan.mode' = 'incremental', 'scan.incremental.snapshot-id' = '100') */;
-- 4. 时间旅行(避免全量扫描)
SELECT * FROM orders TIMESTAMP AS OF '2026-03-10 00:00:00';
-- 5. 启用统计信息
-- Paimon 自动维护列统计信息,优化器自动利用
6.4.3 Compaction 调优
properties
# Minor Compaction(频繁,轻量)
'compaction.minor.trigger.file-num' = '5'
'compaction.minor.trigger.interval' = '300000' # 5 分钟
# Major Compaction(不频繁,重量)
'compaction.major.trigger.file-num' = '30'
'compaction.major.trigger.interval' = '3600000' # 1 小时
# Full Compaction(手动触发)
# 通过 SQL 或 API 触发
ALTER TABLE table_name EXECUTE FULL_COMPACTION;
6.4.4 内存优化
properties
# Flink 任务内存配置
taskmanager.memory.process.size: 4096m
taskmanager.memory.managed.fraction: 0.4
# Paimon 特定配置
'write.buffer-size' = '256mb'
'local-merge-buffer-size' = '128mb'
'lookup.cache.max-rows' = '10000'
'lookup.cache.max-memory' = '512mb'
6.4.5 Compaction 监控指标
核心监控指标:
| 指标名称 | 说明 | 正常范围 | 告警阈值 |
|---|---|---|---|
compaction.pending.files |
待 Compaction 文件数 | < 50 | > 100 持续 10 分钟 |
compaction.duration.seconds |
Compaction 耗时 | < 300s | > 600s |
compaction.file.count.growth |
文件数增长率 | < 10%/小时 | > 20%/小时 |
compaction.trigger.count |
Compaction 触发次数 | - | 突增 50% 告警 |
file.size.avg.bytes |
平均文件大小 | 100MB-500MB | < 50MB 或 > 1GB |
Prometheus 监控配置:
yaml
# Compaction 监控指标
groups:
- name: paimon_compaction
rules:
- alert: CompactionBacklog
expr: paimon_compaction_pending_files > 100
for: 10m
labels:
severity: warning
annotations:
summary: "Compaction 文件积压"
description: "待 Compaction 文件数 {{ $value }} 超过 100"
- alert: CompactionSlow
expr: paimon_compaction_duration_seconds > 600
for: 5m
labels:
severity: warning
annotations:
summary: "Compaction 耗时过长"
description: "Compaction 耗时 {{ $value }} 秒超过 600 秒"
- alert: FileCountGrowthTooFast
expr: rate(paimon_file_count_total[1h]) > 0.2
for: 1h
labels:
severity: warning
annotations:
summary: "文件数增长过快"
description: "文件数增长率 {{ $value }}%/小时超过 20%"
Grafana 仪表板面板:
json
{
"panels": [
{
"title": "Compaction 积压文件数",
"targets": [{"expr": "paimon_compaction_pending_files"}],
"thresholds": [
{"value": 50, "color": "green"},
{"value": 100, "color": "yellow"},
{"value": 200, "color": "red"}
]
},
{
"title": "文件数增长趋势",
"targets": [{"expr": "rate(paimon_file_count_total[1h])"}]
},
{
"title": "Compaction 耗时 P99",
"targets": [{"expr": "histogram_quantile(0.99, rate(paimon_compaction_duration_bucket[5m]))"}]
}
]
}
6.5 数据质量管理
6.5.1 数据校验
sql
-- 1. 空值检查
SELECT
COUNT(*) AS total,
SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) AS null_user_id,
SUM(CASE WHEN amount IS NULL THEN 1 ELSE 0 END) AS null_amount
FROM orders
WHERE dt = '2026-03-10';
-- 2. 重复检查
SELECT order_id, COUNT(*) AS cnt
FROM orders
GROUP BY order_id
HAVING COUNT(*) > 1;
-- 3. 范围检查
SELECT
MIN(amount) AS min_amount,
MAX(amount) AS max_amount,
AVG(amount) AS avg_amount
FROM orders
WHERE dt = '2026-03-10';
-- 4. 一致性检查(与源表对比)
SELECT
'paimon' AS source,
COUNT(*) AS cnt
FROM paimon_orders
WHERE dt = '2026-03-10'
UNION ALL
SELECT
'mysql' AS source,
COUNT(*) AS cnt
FROM mysql_orders
WHERE DATE(order_time) = '2026-03-10';
6.5.2 数据修复
sql
-- 1. 删除重复数据
DELETE FROM orders
WHERE order_id IN (
SELECT order_id
FROM orders
GROUP BY order_id
HAVING COUNT(*) > 1
);
-- 2. 更新异常数据
UPDATE orders
SET amount = 0
WHERE amount < 0;
-- 3. 补全缺失数据
INSERT INTO orders
SELECT
order_id,
user_id,
0 AS amount, -- 默认值
order_time
FROM source_orders
WHERE order_id NOT IN (SELECT order_id FROM orders);
6.6 监控与告警
6.6.1 关键指标
| 指标类别 | 指标名称 | 说明 | 告警阈值 |
|---|---|---|---|
| 写入 | write.qps | 写入吞吐量 | < 1000 持续 5 分钟 |
| 写入 | write.latency | 写入延迟 | P99 > 1s |
| 写入 | checkpoint.duration | 检查点时长 | > 5 分钟 |
| Compaction | compaction.pending | 待 Compaction 文件数 | > 100 |
| Compaction | compaction.duration | Compaction 耗时 | > 30 分钟 |
| 查询 | query.latency | 查询延迟 | P99 > 5s |
| 存储 | storage.size | 存储大小 | 增长率 > 20%/天 |
| 存储 | file.count | 文件数量 | > 10000 |
6.6.2 Prometheus 监控配置
yaml
# Flink + Paimon 指标导出
metrics.reporter.prom.class: org.apache.flink.metrics.prometheus.PrometheusReporter
metrics.reporter.prom.port: 9249
metrics.scope.delimiter: "_"
# Paimon 特定指标
paimon.write.bytes: 写入字节数
paimon.write.records: 写入记录数
paimon.compaction.tasks: Compaction 任务数
paimon.snapshot.created: 创建的快照数
6.6.3 告警规则(Prometheus)
yaml
groups:
- name: paimon
rules:
- alert: PaimonWriteLag
expr: rate(paimon_write_records_total[5m]) < 1000
for: 5m
labels:
severity: warning
annotations:
summary: "Paimon 写入吞吐量过低"
- alert: PaimonCompactionBacklog
expr: paimon_compaction_pending_files > 100
for: 10m
labels:
severity: warning
annotations:
summary: "Paimon Compaction 积压"
- alert: PaimonCheckpointSlow
expr: flink_taskmanager_job_task_checkpoint_duration_seconds > 300
for: 5m
labels:
severity: critical
annotations:
summary: "Flink Checkpoint 过慢"
6.7 生产环境最佳实践
6.7.1 表设计规范
sql
-- 推荐表结构
CREATE TABLE paimon_catalog.paimon_db.orders (
-- 主键(必选)
order_id BIGINT,
-- 业务字段
user_id BIGINT,
amount DECIMAL(10, 2),
status INT,
-- 时间字段(用于分区和排序)
order_time TIMESTAMP(3),
dt STRING, -- 分区字段 'yyyy-MM-dd'
-- 审计字段
created_at TIMESTAMP(3),
updated_at TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
-- 基础配置
'bucket' = '16',
'file.format' = 'orc',
-- Compaction 配置
'changelog-producer' = 'lookup',
'compaction.max-file-num' = '30',
-- 性能优化
'write.buffer-size' = '256mb',
'lookup.cache.max-rows' = '10000'
);
6.7.2 作业配置规范
properties
# Flink 作业配置
execution.checkpointing.interval: 60000
execution.checkpointing.timeout: 600000
execution.checkpointing.max-concurrent-checkpoints: 1
execution.checkpointing.min-pause: 30000
# 重启策略
restart-strategy: exponential-delay
restart-strategy.exponential-delay.max-attempts: 10
restart-strategy.exponential-delay.delay: 1000
restart-strategy.exponential-delay.max-delay: 60000
# 资源配置
taskmanager.memory.process.size: 4096m
parallelism.default: 4
6.7.3 数据生命周期管理
sql
-- 1. 自动过期(通过分区)
-- 写入时只保留最近 N 天的分区
INSERT INTO orders
SELECT * FROM source
WHERE dt >= DATE_FORMAT(DATE_SUB(CURRENT_DATE, 30), 'yyyy-MM-dd');
-- 2. 手动清理旧分区
ALTER TABLE orders DROP IF EXISTS PARTITION (dt = '2026-02-01');
-- 3. 定期清理(定时任务)
-- 每天执行一次,清理 90 天前的数据
DELETE FROM orders WHERE dt < DATE_FORMAT(DATE_SUB(CURRENT_DATE, 90), 'yyyy-MM-dd');
6.8 本章总结
核心要点
- Schema 演化支持添加/删除/修改列,无需重建表
- 多表关联注意维度表缓存和增量优化
- CDC 多表同步可实现整库自动入湖
- 性能调优关注 Bucket 数、Compaction、内存配置
- 数据质量需要定期校验和监控
- 生产规范包括表设计、作业配置、生命周期管理
关键配置
properties
# 写入优化
'bucket' = '16'
'changelog-producer' = 'lookup'
'write.buffer-size' = '256mb'
# Compaction
'compaction.max-file-num' = '30'
'compaction.max-size-bytes' = '268435456'
# 查询优化
'lookup.cache.max-rows' = '10000'
'lookup.cache.max-memory' = '512mb'
第七章:生产实践案例集 - Apache Paimon 真实场景应用
7.1 案例一:电商实时数仓
7.1.1 业务背景
某电商平台日均订单量 500 万+,需要构建实时数仓支持:
- 实时 GMV 监控(秒级延迟)
- 用户行为分析(分钟级延迟)
- 库存实时扣减(毫秒级延迟)
- 运营报表(小时级更新)
7.1.2 架构设计
┌─────────────────────────────────────────────────────────────────────────┐
│ 数据源层 │
├──────────────┬──────────────┬──────────────┬──────────────┬─────────────┤
│ MySQL │ Kafka │ MongoDB │ PostgreSQL │ API │
│ 订单/用户 │ 行为日志 │ 商品详情 │ 库存 │ 第三方 │
└──────┬───────┴──────┬───────┴──────┬───────┴──────┬───────┴──────┬──────┘
│ │ │ │ │
└──────────────┴──────────────┴──────────────┴──────────────┘
│
┌─────────▼─────────┐
│ Flink CDC │
│ 实时同步 │
└─────────┬─────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌──────▼──────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ ODS 层 │ │ DWD 层 │ │ DWS 层 │
│ Paimon 表 │ │ Paimon 表 │ │ Paimon 表 │
│ 原始数据 │ │ 明细数据 │ │ 汇总数据 │
└──────┬──────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└─────────────────────┴─────────────────────┘
│
┌─────────▼─────────┐
│ 查询引擎层 │
┌────────────┼────────────┬────────────┐
│ │ │ │
┌──────▼──────┐ ┌──▼──────┐ ┌──▼──────┐ ┌──▼──────┐
│ StarRocks │ │ Flink │ │ Trino │ │ Spark │
│ BI 报表 │ │ 实时查询 │ │ 探索 │ │ ETL │
└─────────────┘ └─────────┘ └─────────┘ └─────────┘
7.1.3 ODS 层表设计
sql
-- 订单原始表
CREATE TABLE paimon_catalog.ods.orders_raw (
order_id BIGINT,
user_id BIGINT,
shop_id BIGINT,
amount DECIMAL(10, 2),
status INT,
create_time TIMESTAMP(3),
update_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
'bucket' = '32',
'changelog-producer' = 'lookup',
'compaction.max-file-num' = '30'
);
-- 用户原始表
CREATE TABLE paimon_catalog.ods.users_raw (
user_id BIGINT,
user_name STRING,
phone STRING,
region STRING,
level INT,
register_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (user_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '16');
-- 行为日志表
CREATE TABLE paimon_catalog.ods.user_behavior_raw (
event_id STRING,
user_id BIGINT,
action STRING,
item_id BIGINT,
action_time TIMESTAMP(3),
properties MAP<STRING, STRING>,
dt STRING,
PRIMARY KEY (event_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '64');
7.1.4 DWD 层表设计
sql
-- 订单明细表(关联用户、商品信息)
CREATE TABLE paimon_catalog.dwd.orders_detail (
order_id BIGINT,
user_id BIGINT,
user_name STRING,
user_region STRING,
shop_id BIGINT,
shop_name STRING,
amount DECIMAL(10, 2),
discount_amount DECIMAL(10, 2),
actual_amount DECIMAL(10, 2),
status INT,
status_desc STRING,
create_time TIMESTAMP(3),
pay_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '32', 'changelog-producer' = 'lookup');
-- DWD 层 Flink 作业
INSERT INTO paimon_catalog.dwd.orders_detail
SELECT
o.order_id,
o.user_id,
u.user_name,
u.region AS user_region,
o.shop_id,
s.shop_name,
o.amount,
COALESCE(d.discount_amount, 0) AS discount_amount,
o.amount - COALESCE(d.discount_amount, 0) AS actual_amount,
o.status,
CASE o.status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已发货'
WHEN 4 THEN '已完成'
WHEN 5 THEN '已取消'
ELSE '未知'
END AS status_desc,
o.create_time,
o.pay_time,
DATE_FORMAT(o.create_time, 'yyyy-MM-dd') AS dt
FROM paimon_catalog.ods.orders_raw o
JOIN paimon_catalog.ods.users_raw u ON o.user_id = u.user_id
JOIN paimon_catalog.ods.shops_raw s ON o.shop_id = s.shop_id
LEFT JOIN paimon_catalog.ods.discounts_raw d ON o.order_id = d.order_id
WHERE o.dt = DATE_FORMAT(CURRENT_TIMESTAMP, 'yyyy-MM-dd');
7.1.5 DWS 层表设计
sql
-- 实时 GMV 汇总表(按小时)
CREATE TABLE paimon_catalog.dws.gmv_hourly (
stat_time TIMESTAMP(3),
total_orders BIGINT,
total_amount DECIMAL(18, 2),
paid_orders BIGINT,
paid_amount DECIMAL(18, 2),
unique_users BIGINT,
unique_shops BIGINT,
PRIMARY KEY (stat_time) NOT ENFORCED
) WITH ('bucket' = '4', 'changelog-producer' = 'lookup');
-- 实时 GMV 聚合作业
INSERT INTO paimon_catalog.dws.gmv_hourly
SELECT
DATE_FORMAT(create_time, 'yyyy-MM-dd HH:00:00') AS stat_time,
COUNT(*) AS total_orders,
SUM(amount) AS total_amount,
COUNT(CASE WHEN status >= 2 THEN 1 END) AS paid_orders,
SUM(CASE WHEN status >= 2 THEN amount ELSE 0 END) AS paid_amount,
COUNT(DISTINCT user_id) AS unique_users,
COUNT(DISTINCT shop_id) AS unique_shops
FROM paimon_catalog.dwd.orders_detail
WHERE dt = DATE_FORMAT(CURRENT_TIMESTAMP, 'yyyy-MM-dd')
GROUP BY DATE_FORMAT(create_time, 'yyyy-MM-dd HH:00:00');
-- 用户行为汇总表
CREATE TABLE paimon_catalog.dws.user_behavior_daily (
stat_date STRING,
user_id BIGINT,
view_count BIGINT,
click_count BIGINT,
cart_count BIGINT,
order_count BIGINT,
PRIMARY KEY (stat_date, user_id) NOT ENFORCED
) PARTITIONED BY (stat_date)
WITH ('bucket' = '64');
7.1.6 实时查询示例
sql
-- StarRocks 查询实时 GMV
SELECT
stat_time,
total_orders,
total_amount,
paid_amount,
paid_amount / total_amount AS payment_rate
FROM paimon_catalog.dws.gmv_hourly
WHERE stat_time >= TIMESTAMP '2026-03-10 00:00:00'
ORDER BY stat_time;
-- Flink 实时消费最新订单
SELECT * FROM paimon_catalog.dwd.orders_detail
/*+ OPTIONS('streaming' = 'true') */
WHERE dt = '2026-03-10';
-- Trino 即席查询用户行为
SELECT
user_id,
COUNT(*) AS total_actions,
COUNT(CASE WHEN action = 'view' THEN 1 END) AS views,
COUNT(CASE WHEN action = 'click' THEN 1 END) AS clicks,
COUNT(CASE WHEN action = 'cart' THEN 1 END) AS carts
FROM paimon_catalog.ods.user_behavior_raw
WHERE dt = '2026-03-10'
GROUP BY user_id
ORDER BY total_actions DESC
LIMIT 100;
7.1.7 效果对比
| 指标 | 原架构(Hive) | 新架构(Paimon) | 提升 |
|---|---|---|---|
| 数据延迟 | 小时级 | 秒级 | 100x+ |
| 查询响应 | 分钟级 | 秒级 | 60x+ |
| 存储成本 | 高(多份副本) | 低(统一存储) | 40%↓ |
| 运维复杂度 | 高(多系统) | 低(统一平台) | 显著降低 |
7.2 案例二:金融 CDC 同步
7.2.1 业务背景
某金融机构需要将核心业务系统(Oracle/MySQL)数据实时同步到数据湖,支持:
- 实时风控(毫秒级决策)
- 监管报表(T+0 报送)
- 客户画像(实时更新)
- 审计追溯(完整历史)
7.2.2 同步架构
┌────────────────────────────────────────────────────────────────┐
│ 源系统层 │
├─────────────────┬─────────────────┬─────────────────┬──────────┤
│ Oracle │ MySQL │ DB2 │ SQL Server │
│ 核心交易 │ 信贷系统 │ 风控系统 │ 渠道系统 │
└────────┬────────┴────────┬────────┴────────┬────────┴──────────┘
│ │ │
└─────────────────┴─────────────────┘
│
┌─────────▼─────────┐
│ Flink CDC │
│ 整库同步 │
└─────────┬─────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────────▼───────┐ ┌──────▼────────┐ ┌─────▼────────┐
│ ODS 层 │ │ DWD 层 │ │ ADS 层 │
│ Paimon │ │ Paimon │ │ Paimon │
│ 原始数据 │ │ 明细数据 │ │ 应用数据 │
└────────────────┘ └───────────────┘ └──────────────┘
7.2.3 整库同步配置
java
// Flink CDC 整库同步 Oracle → Paimon
OracleSyncDatabaseAction action = new OracleSyncDatabaseActionBuilder()
.hostname("oracle-host")
.port(1521)
.database("CORE_DB")
.username("cdc_user")
.password("******")
.tableList("CORE_DB\\..*") // 同步所有表
.paimonCatalog(catalog)
.paimonDatabase("ods_finance")
.tablePrefix("ods_")
.includingTables("TRANSACTIONS|ACCOUNTS|CUSTOMERS|.*")
.build();
action.run();
sql
-- 自动创建的 Paimon 表
-- Oracle: CORE_DB.TRANSACTIONS → Paimon: ods_finance.ods_transactions
-- Oracle: CORE_DB.ACCOUNTS → Paimon: ods_finance.ods_accounts
-- Oracle: CORE_DB.CUSTOMERS → Paimon: ods_finance.ods_customers
7.2.4 断点续传配置
properties
# 启用 Savepoint
execution.checkpointing.interval: 60000
execution.checkpointing.externalized-checkpoint-retention: RETAIN_ON_CANCELLATION
# CDC 特定配置
scan.startup.mode: earliest-offset
debezium.snapshot.mode: initial
# 失败恢复
restart-strategy: fixed-delay
restart-strategy.fixed-delay.attempts: 5
restart-strategy.fixed-delay.delay: 10000
7.2.4 数据脱敏与合规
金融场景数据合规要求:
| 数据类型 | 脱敏方式 | 示例 |
|---|---|---|
| 身份证号 | 掩码处理 | 110101****1234 |
| 手机号 | 中间掩码 | 138****5678 |
| 银行卡号 | 仅保留后 4 位 | ****1234 |
| 客户姓名 | 哈希/替换 | 客户_A001 |
| 交易金额 | 分段统计 | 1000-5000 元 |
sql
-- 脱敏后的 ODS 层表设计
CREATE TABLE paimon_catalog.ods_finance.ods_customers (
customer_id STRING, -- 客户 ID(保留)
customer_name_hash STRING, -- 客户姓名哈希(脱敏)
id_card_masked STRING, -- 身份证掩码
phone_masked STRING, -- 手机号掩码
region STRING, -- 地区(保留)
level STRING, -- 客户等级(保留)
dt STRING,
PRIMARY KEY (customer_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '16');
-- Flink CDC 脱敏转换
INSERT INTO paimon_catalog.ods_finance.ods_customers
SELECT
customer_id,
MD5(customer_name) AS customer_name_hash, -- 姓名哈希
CONCAT(LEFT(id_card, 6), '****', RIGHT(id_card, 4)) AS id_card_masked, -- 身份证掩码
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone_masked, -- 手机号掩码
region,
level,
DATE_FORMAT(SYNC_TIME, 'yyyy-MM-dd') AS dt
FROM oracle_core_db.customers;
合规建议:
- 生产环境:所有 PII(个人身份信息)必须脱敏后入湖
- 开发测试:使用脱敏后的生产数据副本
- 权限控制:原始数据仅限授权人员访问
- 审计日志:记录所有敏感数据访问行为
7.2.5 数据一致性校验
sql
-- 1. 记录数对比
SELECT
'oracle' AS source,
COUNT(*) AS cnt
FROM oracle_core_db.transactions
WHERE TRUNC(transaction_time) = DATE '2026-03-10'
UNION ALL
SELECT
'paimon' AS source,
COUNT(*) AS cnt
FROM paimon_catalog.ods_finance.ods_transactions
WHERE dt = '2026-03-10';
-- 2. 金额汇总对比
SELECT
'oracle' AS source,
SUM(amount) AS total_amount
FROM oracle_core_db.transactions
WHERE TRUNC(transaction_time) = DATE '2026-03-10'
UNION ALL
SELECT
'paimon' AS source,
SUM(amount) AS total_amount
FROM paimon_catalog.ods_finance.ods_transactions
WHERE dt = '2026-03-10';
-- 3. 最新记录对比
SELECT
'oracle' AS source,
MAX(transaction_id) AS max_id,
MAX(transaction_time) AS max_time
FROM oracle_core_db.transactions
UNION ALL
SELECT
'paimon' AS source,
MAX(transaction_id) AS max_id,
MAX(transaction_time) AS max_time
FROM paimon_catalog.ods_finance.ods_transactions;
7.2.6 实时风控应用
sql
-- 实时交易监控表
CREATE TABLE paimon_catalog.ads_finance.realtime_risk (
transaction_id STRING,
user_id STRING,
amount DECIMAL(18, 2),
transaction_time TIMESTAMP(3),
risk_level STRING,
risk_reason STRING,
PRIMARY KEY (transaction_id) NOT ENFORCED
) WITH ('bucket' = '32', 'changelog-producer' = 'lookup');
-- 实时风控规则(Flink SQL)
INSERT INTO paimon_catalog.ads_finance.realtime_risk
SELECT
t.transaction_id,
t.user_id,
t.amount,
t.transaction_time,
CASE
WHEN t.amount > 1000000 THEN 'HIGH'
WHEN t.amount > 100000 THEN 'MEDIUM'
WHEN hour_count > 10 THEN 'MEDIUM'
ELSE 'LOW'
END AS risk_level,
CASE
WHEN t.amount > 1000000 THEN '大额交易'
WHEN t.amount > 100000 THEN '较大金额'
WHEN hour_count > 10 THEN '高频交易'
ELSE '正常'
END AS risk_reason
FROM (
SELECT
transaction_id,
user_id,
amount,
transaction_time,
COUNT(*) OVER (
PARTITION BY user_id
ORDER BY transaction_time
RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
) AS hour_count
FROM paimon_catalog.ods_finance.ods_transactions
/*+ OPTIONS('streaming' = 'true') */
) t;
7.3 案例三:日志分析平台
7.3.1 业务背景
某互联网公司日均日志量 100 亿+,需要构建日志分析平台支持:
- 实时错误监控
- 用户行为分析
- 性能指标追踪
- 安全审计
7.3.2 架构设计
┌─────────────────────────────────────────────────────────────────┐
│ 日志采集层 │
├──────────────┬──────────────┬──────────────┬──────────────────┤
│ Filebeat │ Fluentd │ Kafka │ HTTP API │
│ 服务器日志 │ 容器日志 │ 应用日志 │ 客户端日志 │
└──────┬───────┴──────┬───────┴──────┬───────┴─────────┬────────┘
│ │ │ │
└──────────────┴──────────────┴─────────────────┘
│
┌─────────▼─────────┐
│ Flink ETL │
│ 清洗/转换/聚合 │
└─────────┬─────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌───────▼───────┐ ┌──────▼──────┐
│ 原始日志 │ │ 明细日志 │ │ 聚合指标 │
│ Paimon │ │ Paimon │ │ Paimon │
│ ODS 层 │ │ DWD 层 │ │ DWS 层 │
└─────────────┘ └───────────────┘ └─────────────┘
7.3.3 表设计
sql
-- 原始日志表
CREATE TABLE paimon_catalog.ods.app_logs (
log_id STRING,
app_id STRING,
log_level STRING,
message STRING,
trace_id STRING,
span_id STRING,
user_id STRING,
device_id STRING,
ip STRING,
log_time TIMESTAMP(3),
properties MAP<STRING, STRING>,
dt STRING,
PRIMARY KEY (log_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '128', 'file.format' = 'orc');
-- 错误日志明细表
CREATE TABLE paimon_catalog.dwd.error_logs (
log_id STRING,
app_id STRING,
error_type STRING,
error_message STRING,
stack_trace STRING,
trace_id STRING,
user_id STRING,
log_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (log_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH ('bucket' = '32');
-- 错误聚合表(按分钟)
CREATE TABLE paimon_catalog.dws.error_stats_minute (
stat_time TIMESTAMP(3),
app_id STRING,
error_type STRING,
error_count BIGINT,
unique_users BIGINT,
PRIMARY KEY (stat_time, app_id, error_type) NOT ENFORCED
) WITH ('bucket' = '16', 'changelog-producer' = 'lookup');
7.3.4 实时聚合作业
sql
-- 错误日志实时聚合
INSERT INTO paimon_catalog.dws.error_stats_minute
SELECT
DATE_FORMAT(log_time, 'yyyy-MM-dd HH:mm:00') AS stat_time,
app_id,
error_type,
COUNT(*) AS error_count,
COUNT(DISTINCT user_id) AS unique_users
FROM paimon_catalog.dwd.error_logs
/*+ OPTIONS('streaming' = 'true') */
WHERE dt = DATE_FORMAT(CURRENT_TIMESTAMP, 'yyyy-MM-dd')
GROUP BY
DATE_FORMAT(log_time, 'yyyy-MM-dd HH:mm:00'),
app_id,
error_type;
-- 告警触发(错误数突增)
INSERT INTO paimon_catalog.ads.alerts
SELECT
stat_time,
app_id,
error_type,
error_count,
avg_count,
(error_count - avg_count) / avg_count AS increase_rate,
'ERROR_SPIKE' AS alert_type
FROM (
SELECT
stat_time,
app_id,
error_type,
error_count,
AVG(error_count) OVER (
PARTITION BY app_id, error_type
ORDER BY stat_time
ROWS BETWEEN 10 PRECEDING AND 1 PRECEDING
) AS avg_count
FROM paimon_catalog.dws.error_stats_minute
/*+ OPTIONS('streaming' = 'true') */
)
WHERE error_count > avg_count * 2 AND avg_count > 10;
7.3.5 日志查询示例
sql
-- Trino 交互式查询
SELECT
app_id,
error_type,
COUNT(*) AS error_count,
COUNT(DISTINCT user_id) AS affected_users
FROM paimon_catalog.dwd.error_logs
WHERE dt = '2026-03-10'
AND log_time >= TIMESTAMP '2026-03-10 10:00:00'
AND log_time < TIMESTAMP '2026-03-10 11:00:00'
GROUP BY app_id, error_type
ORDER BY error_count DESC
LIMIT 20;
-- 时间旅行(查看历史状态)
SELECT * FROM paimon_catalog.dwd.error_logs
TIMESTAMP AS OF '2026-03-10 10:30:00'
WHERE log_id = 'log-123456';
-- 增量查询(只查新增)
SELECT * FROM paimon_catalog.dwd.error_logs
/*+ OPTIONS(
'scan.mode' = 'incremental',
'scan.incremental.snapshot-id' = '500'
) */
WHERE dt = '2026-03-10';
7.4 案例四:数据湖联邦查询
7.4.1 业务背景
某企业数据分散在多个系统中,需要联邦查询支持:
- 跨系统数据分析
- 统一数据视图
- 无需数据搬迁
7.4.2 联邦架构
┌─────────────────────────────────────────────────────────────────┐
│ Trino 联邦查询层 │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌───────▼────────┐ ┌────────▼────────┐ ┌───────▼────────┐
│ Paimon Catalog│ │ MySQL Catalog │ │ Hive Catalog │
│ 数据湖 │ │ 业务数据库 │ │ 历史数据 │
└────────────────┘ └─────────────────┘ └────────────────┘
7.4.3 联邦查询示例
sql
-- 跨系统关联查询
SELECT
o.order_id,
o.order_time,
o.amount,
c.customer_name,
c.customer_level,
p.product_name,
p.category,
h.shipment_status,
h.delivery_time
FROM paimon.paimon_db.orders o
JOIN mysql.shop_db.customer c ON o.customer_id = c.customer_id
JOIN mysql.shop_db.product p ON o.product_id = p.product_id
JOIN hive.warehouse.shipment h ON o.order_id = h.order_id
WHERE o.dt = '2026-03-10';
-- 跨系统聚合
SELECT
c.region,
COUNT(*) AS order_count,
SUM(o.amount) AS gmv,
AVG(h.delivery_days) AS avg_delivery_days
FROM paimon.paimon_db.orders o
JOIN mysql.shop_db.customer c ON o.customer_id = c.customer_id
JOIN hive.warehouse.shipment h ON o.order_id = h.order_id
WHERE o.dt >= '2026-03-01'
GROUP BY c.region;
-- 物化视图加速
CREATE MATERIALIZED VIEW paimon.paimon_db.mv_customer_orders AS
SELECT
c.customer_id,
c.customer_name,
c.region,
COUNT(*) AS total_orders,
SUM(o.amount) AS total_amount,
MAX(o.order_time) AS last_order_time
FROM paimon.paimon_db.orders o
JOIN mysql.shop_db.customer c ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.customer_name, c.region;
-- 查询物化视图
SELECT * FROM paimon.paimon_db.mv_customer_orders
WHERE region = '华东';
7.5 经验总结
7.5.1 成功要素
- 统一存储格式:Paimon 作为统一存储层,简化架构
- 流批一体:同一份数据支持实时和离线场景
- 多引擎支持:灵活选择最适合的查询引擎
- Schema 演化:适应业务变化,无需重建表
7.5.2 常见坑点
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 写入延迟高 | Bucket 数过少 | 增加 Bucket 数 |
| 查询慢 | 未利用分区裁剪 | 查询条件包含分区字段 |
| Compaction 积压 | 配置不当 | 调整 Compaction 参数 |
| 数据不一致 | 并发写入冲突 | 使用主键表,启用 Changelog |
7.5.3 最佳实践
sql
-- 1. 合理设计主键
CREATE TABLE orders (
order_id BIGINT, -- 主键
...
PRIMARY KEY (order_id) NOT ENFORCED
);
-- 2. 合理分区
PARTITIONED BY (dt) -- 按天分区
-- 3. 启用 Changelog
'changelog-producer' = 'lookup'
-- 4. 定期 Compaction
'compaction.max-file-num' = '30'
-- 5. 监控关键指标
-- write.qps, compaction.pending, query.latency
7.6 本章总结
核心案例
- 电商实时数仓:ODS→DWD→DWS 分层架构,秒级延迟
- 金融 CDC 同步:整库同步,断点续传,数据一致性校验
- 日志分析平台:100 亿+/日,实时错误监控
- 数据湖联邦查询:跨系统关联,无需数据搬迁
第八章:技术选型指南 - Apache Paimon 适用场景评估
8.1 数据湖格式对比
8.1.1 主流数据湖格式
| 特性 | Paimon | Iceberg | Hudi | Delta Lake |
|---|---|---|---|---|
| 实时写入 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 流式读取 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 批量查询 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| CDC 支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 多引擎 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Schema 演化 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Time Travel | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Compaction | 自动 | 手动 | 自动 | 自动 |
| 社区活跃度 | 高 | 很高 | 高 | 很高 |
| 学习曲线 | 中 | 中高 | 高 | 中 |
8.1.2 核心差异
┌─────────────────────────────────────────────────────────────────┐
│ 数据湖格式定位 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Paimon: 流式湖仓(Streaming Lakehouse) │
│ - 核心优势:实时写入 + 流式读取 │
│ - 最佳场景:Flink CDC、实时数仓 │
│ │
│ Iceberg: 通用湖仓(General Lakehouse) │
│ - 核心优势:批量查询 + 广泛引擎支持 │
│ - 最佳场景:离线分析、数据湖治理 │
│ │
│ Hudi: 增量湖仓(Incremental Lakehouse) │
│ - 核心优势:Upsert + 增量处理 │
│ - 最佳场景:CDC 同步、数据湖更新 │
│ │
│ Delta Lake: Spark 生态湖仓 │
│ - 核心优势:Spark 深度集成 + ACID 事务 │
│ - 最佳场景:Spark ETL、ML 流水线 │
│ │
└─────────────────────────────────────────────────────────────────┘
8.2 适用场景评估
8.2.1 推荐使用 Paimon 的场景
| 场景 | 匹配度 | 理由 |
|---|---|---|
| Flink CDC 实时入湖 | ⭐⭐⭐⭐⭐ | 原生支持,延迟最低 |
| 实时数仓构建 | ⭐⭐⭐⭐⭐ | 流批一体,多引擎查询 |
| 流式聚合 | ⭐⭐⭐⭐⭐ | 状态管理完善,Exactly-Once |
| 实时风控 | ⭐⭐⭐⭐⭐ | 毫秒级写入,秒级查询 |
| 日志实时分析 | ⭐⭐⭐⭐⭐ | 高吞吐写入,实时聚合 |
| 数据湖联邦查询 | ⭐⭐⭐⭐ | 多引擎支持,统一存储 |
8.2.2 谨慎使用 Paimon 的场景
| 场景 | 匹配度 | 建议 |
|---|---|---|
| 纯离线批处理 | ⭐⭐⭐ | 考虑 Iceberg/Delta |
| ML 训练数据 | ⭐⭐⭐ | 考虑 Delta Lake |
| 小规模数据(< 100GB) | ⭐⭐⭐ | 直接用数据库 |
| 强事务要求 | ⭐⭐⭐ | 考虑传统数据库 |
| Spark 为主的技术栈 | ⭐⭐⭐ | 考虑 Delta Lake |
8.2.3 决策树
是否需要实时写入?
├─ 否 → 考虑 Iceberg / Delta Lake
└─ 是 → 是否需要流式读取?
├─ 否 → 考虑 Hudi
└─ 是 → 是否使用 Flink?
├─ 是 → ✅ 推荐 Paimon
└─ 否 → 考虑 Paimon / Hudi(看引擎支持)
8.3 技术栈兼容性
8.3.1 计算引擎兼容性
| 引擎 | Paimon | Iceberg | Hudi | Delta |
|---|---|---|---|---|
| Flink | ✅ 读写 | ✅ 读写 | ✅ 读写 | ⚠️ 读 |
| Spark | ✅ 读写 | ✅ 读写 | ✅ 读写 | ✅ 读写 |
| Trino | ✅ 读 | ✅ 读 | ✅ 读 | ✅ 读 |
| Presto | ✅ 读 | ✅ 读 | ✅ 读 | ✅ 读 |
| Hive | ✅ 读 | ✅ 读 | ✅ 读 | ✅ 读 |
| StarRocks | ✅ 读 | ✅ 读 | ⚠️ | ⚠️ |
| Doris | ✅ 读 | ✅ 读 | ⚠️ | ⚠️ |
8.3.2 存储系统兼容性
| 存储系统 | Paimon | Iceberg | Hudi | Delta |
|---|---|---|---|---|
| HDFS | ✅ | ✅ | ✅ | ✅ |
| S3 | ✅ | ✅ | ✅ | ✅ |
| OSS | ✅ | ✅ | ✅ | ⚠️ |
| ADLS | ✅ | ✅ | ✅ | ✅ |
| GCS | ✅ | ✅ | ✅ | ✅ |
| K8s PV | ✅ | ⚠️ | ⚠️ | ⚠️ |
8.3.3 数据目录兼容性
| Catalog | Paimon | Iceberg | Hudi | Delta |
|---|---|---|---|---|
| Hive Metastore | ✅ | ✅ | ✅ | ✅ |
| AWS Glue | ⚠️ | ✅ | ✅ | ✅ |
| Unity Catalog | ⚠️ | ⚠️ | ⚠️ | ✅ |
| Paimon Catalog | ✅ | ❌ | ❌ | ❌ |
8.4 成本评估
8.4.1 存储成本
场景:日均增量 100GB,保留 90 天
Paimon(启用 Compaction):
- 原始数据:100GB × 90 = 9TB
- Compaction 后:约 6TB(压缩率~1.5)
- 存储成本:6TB × $0.023/GB/月 × 3 月 ≈ $414/月
Iceberg(无 Compaction):
- 原始数据:100GB × 90 = 9TB
- 小文件问题:约 12TB(膨胀率~1.3)
- 存储成本:12TB × $0.023/GB/月 × 3 月 ≈ $828/月
节省:约 50%
8.4.2 计算成本
场景:实时数仓,10 个 Flink 作业
Paimon 架构:
- Flink 作业:10 个(CDC + ETL + 聚合)
- 查询引擎:StarRocks(3 节点)
- 月成本:$5000(Flink)+ $3000(StarRocks)= $8000
传统架构(Kafka + Druid):
- Flink 作业:10 个(CDC + ETL)
- Kafka:10 个 Topic
- Druid:10 节点
- 月成本:$5000(Flink)+ $2000(Kafka)+ $8000(Druid)= $15000
节省:约 47%
8.4.3 运维成本
| 项目 | Paimon | 传统架构 | 节省 |
|---|---|---|---|
| 系统数量 | 2(Flink + Paimon) | 4(Flink + Kafka + Druid + ES) | 50% |
| 运维人力 | 1-2 人 | 3-4 人 | 50%+ |
| 故障排查 | 统一日志 | 多系统日志 | 显著降低 |
8.5 迁移路径
8.5.1 从 Kafka 迁移到 Paimon
原架构:
MySQL → Flink CDC → Kafka → Flink → Druid/ES
新架构:
MySQL → Flink CDC → Paimon → StarRocks/Flink
迁移步骤:
1. 创建 Paimon 表(与 Kafka Topic 对应)
2. 修改 Flink 作业(Sink 从 Kafka 改为 Paimon)
3. 双写验证(同时写 Kafka 和 Paimon)
4. 切换查询(从 Druid/ES 改为 StarRocks/Flink)
5. 下线 Kafka(保留备份)
8.5.2 从 Hudi 迁移到 Paimon
原架构:
MySQL → Flink CDC → Hudi → Spark/Presto
新架构:
MySQL → Flink CDC → Paimon → Spark/StarRocks
迁移步骤:
1. 创建 Paimon 表(与 Hudi 表结构对应)
2. 历史数据迁移(Spark 读取 Hudi 写入 Paimon)
3. 增量同步(Flink CDC 双写)
4. 切换查询(验证数据一致性)
5. 下线 Hudi
8.5.3 数据迁移脚本
sql
-- Spark 迁移历史数据
INSERT INTO paimon_catalog.db.table_new
SELECT * FROM hudi_catalog.db.table_old
WHERE dt >= '2026-01-01';
-- 验证数据一致性
SELECT
'hudi' AS source,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM hudi_catalog.db.table_old
WHERE dt >= '2026-01-01'
UNION ALL
SELECT
'paimon' AS source,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM paimon_catalog.db.table_new
WHERE dt >= '2026-01-01';
8.6 风险评估
8.6.1 技术风险
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
| 社区支持不足 | 低 | 中 | Paimon 是 Apache 顶级项目,社区活跃 |
| 引擎兼容性 | 中 | 中 | 优先使用 Flink/Spark,其他引擎测试后使用 |
| 性能问题 | 低 | 高 | 充分测试,合理配置 Compaction |
| 数据丢失 | 低 | 高 | 启用 Checkpoint,定期备份 |
8.6.2 运维风险
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
| Compaction 积压 | 中 | 中 | 监控指标,自动告警 |
| 小文件过多 | 中 | 中 | 合理设置 Compaction 参数 |
| 查询性能下降 | 低 | 中 | 定期 Optimize,利用索引 |
8.6.3 业务风险
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
| 需求变更 | 高 | 中 | Schema 演化支持,灵活应对 |
| 数据量激增 | 中 | 中 | 弹性扩容,合理分区 |
| SLA 不达标 | 低 | 高 | 充分测试,预留缓冲 |
8.7 选型检查清单
8.7.1 业务需求
- 是否需要实时写入(秒级延迟)?
- 是否需要流式读取(持续消费)?
- 是否需要批量查询(离线分析)?
- 是否需要 Time Travel(历史回溯)?
- 是否需要 Schema 演化(表结构变更)?
8.7.2 技术栈
- 是否使用 Flink?
- 是否使用 Spark?
- 是否需要 OLAP 引擎(StarRocks/Doris)?
- 是否使用 HDFS/S3 等存储?
- 是否有 Hive Metastore?
8.7.3 资源评估
- 存储预算是否充足?
- 计算资源是否充足?
- 运维人力是否充足?
- 是否有足够测试时间?
8.7.4 决策建议
如果以上问题大部分回答"是":
→ ✅ 强烈推荐 Paimon
如果实时需求不强,主要是离线分析:
→ ⚠️ 考虑 Iceberg / Delta Lake
如果主要是 Spark 生态:
→ ⚠️ 考虑 Delta Lake
如果需要强事务支持:
→ ⚠️ 考虑传统数据库
8.8 本章总结
核心要点
- Paimon 定位:流式湖仓,实时写入 + 流式读取是核心优势
- 适用场景:Flink CDC、实时数仓、流式聚合、实时风控
- 技术对比:与 Iceberg/Hudi/Delta 各有优劣,根据场景选择
- 成本优势:存储成本降低 50%,运维成本降低 50%+
- 迁移路径:从 Kafka/Hudi 迁移有清晰路径
选型建议
实时场景 → Paimon ✅
离线场景 → Iceberg / Delta
Spark 生态 → Delta
强事务 → 传统数据库
第九章将讲解故障排查与监控,包括常见问题诊断、性能调优、监控告警等生产环境必备技能。
第九章:故障排查与监控 - Apache Paimon 生产运维指南
9.1 常见问题诊断
9.1.1 写入问题
问题 1:写入延迟高
症状:Flink 作业背压,写入 QPS 低于预期
排查步骤:
sql
-- 1. 检查 Bucket 数
SHOW CREATE TABLE table_name;
-- 确认 bucket 数是否合理(数据量/2GB)
-- 2. 检查 Compaction 状态
SELECT * FROM table_name$files;
-- 查看文件数量,判断是否 Compaction 积压
-- 3. 检查 Checkpoint
-- Flink Web UI → Checkpoints → 查看时长和大小
解决方案:
properties
# 增加 Bucket 数(需重建表)
'bucket' = '32' # 原 16
# 优化 Compaction
'compaction.max-file-num' = '30'
'compaction.max-size-bytes' = '268435456'
# 调整 Flink 配置
taskmanager.memory.process.size: 8192m
execution.checkpointing.interval: 30000
问题 2:写入失败
症状:Flink 作业频繁重启,错误日志显示写入异常
常见原因及解决:
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
OutOfMemoryError |
内存不足 | 增加 TaskManager 内存 |
TimeoutException |
写入超时 | 增加超时时间,优化网络 |
ConcurrentModificationException |
并发冲突 | 检查主键,启用 Changelog |
NoSpaceLeftOnDevice |
磁盘满 | 清理磁盘,扩容 |
properties
# 内存优化
taskmanager.memory.managed.fraction: 0.4
'write.buffer-size' = '256mb'
# 超时优化
'write.timeout' = '300s'
# 重启策略
restart-strategy: exponential-delay
restart-strategy.exponential-delay.max-attempts: 10
9.1.2 查询问题
问题 1:查询慢
症状:查询响应时间超过预期
排查步骤:
sql
-- 1. 检查是否利用分区裁剪
EXPLAIN SELECT * FROM table WHERE dt = '2026-03-10'; -- ✅
EXPLAIN SELECT * FROM table WHERE amount > 100; -- ❌
-- 2. 检查文件数量
SELECT COUNT(*) FROM table_name$files;
-- 文件过多说明 Compaction 不足
-- 3. 检查统计信息
SELECT * FROM table_name$stats;
-- 确认统计信息是否准确
解决方案:
sql
-- 1. 优化查询(添加分区条件)
SELECT * FROM table WHERE dt = '2026-03-10' AND amount > 100;
-- 2. 手动 Compaction
ALTER TABLE table_name EXECUTE COMPACT;
-- 3. 优化表设计
-- 重新设计分区策略
-- 增加主键索引
问题 2:查询结果不一致
症状:多次查询结果不同,或与源数据不一致
排查步骤:
sql
-- 1. 检查快照
SELECT * FROM table_name$snapshots;
-- 确认快照是否正常创建
-- 2. 检查是否有未提交的写入
-- Flink Web UI → 检查是否有进行中的 Checkpoint
-- 3. 数据对比
SELECT COUNT(*) FROM source_table;
SELECT COUNT(*) FROM paimon_table;
解决方案:
properties
# 确保 Exactly-Once
execution.checkpointing.mode: EXACTLY_ONCE
execution.checkpointing.interval: 60000
# 等待 Checkpoint 完成
# 查询前确认最近的 Checkpoint 已完成
9.1.3 Compaction 问题
问题:Compaction 积压
症状:小文件数量持续增长,查询性能下降
排查步骤:
sql
-- 1. 查看文件分布
SELECT
level,
COUNT(*) AS file_count,
SUM(file_size) AS total_size
FROM table_name$files
GROUP BY level;
-- 2. 查看 Compaction 历史
SELECT * FROM table_name$compaction_history;
解决方案:
properties
# 增加 Compaction 并发
'compaction.max-file-num' = '20' # 降低阈值
'compaction.early-trigger' = 'true'
# 手动触发 Full Compaction
ALTER TABLE table_name EXECUTE FULL_COMPACTION;
# 增加 Flink 并行度
parallelism.default: 8
9.2 性能调优
9.2.1 写入调优
properties
# 1. 调整 Batch Size
'write.batch-size' = '1024' # 默认 1024,可增加到 2048
# 2. 调整 Buffer Size
'write.buffer-size' = '512mb' # 默认 256mb
# 3. 启用 Local Merge
'local-merge-buffer-size' = '256mb'
# 4. 调整 Commit 频率
# Flink Checkpoint 间隔决定 Commit 频率
execution.checkpointing.interval: 30000 # 30 秒
9.2.2 查询调优
sql
-- 1. 利用分区裁剪
SELECT * FROM table WHERE dt = '2026-03-10'; -- ✅
-- 2. 利用主键索引
SELECT * FROM table WHERE id = 12345; -- ✅
-- 3. 避免全表扫描
SELECT COUNT(*) FROM table; -- ❌ 慢
SELECT COUNT(*) FROM table WHERE dt = '2026-03-10'; -- ✅
-- 4. 使用增量读取
SELECT * FROM table
/*+ OPTIONS('scan.mode' = 'incremental', 'scan.incremental.snapshot-id' = '100') */;
9.2.3 资源调优
yaml
# Flink 资源配置
taskmanager:
memory:
process:
size: 8192m
managed:
fraction: 0.4
slots: 4
# Paimon 特定配置
'write.buffer-size': '512mb'
'lookup.cache.max-rows': '20000'
'lookup.cache.max-memory': '1024mb'
9.3 监控告警
9.3.1 关键指标
| 指标类别 | 指标名称 | 说明 | 告警阈值 |
|---|---|---|---|
| 写入 | paimon_write_records_total |
累计写入记录数 | - |
| 写入 | paimon_write_bytes_total |
累计写入字节数 | - |
| 写入 | write_qps |
写入吞吐量 | < 1000 持续 5 分钟 |
| 写入 | write_latency_p99 |
写入延迟 P99 | > 1s |
| Compaction | compaction_pending_files |
待 Compaction 文件数 | > 100 |
| Compaction | compaction_duration |
Compaction 耗时 | > 30 分钟 |
| 查询 | query_latency_p99 |
查询延迟 P99 | > 5s |
| 存储 | storage_size_bytes |
存储大小 | 增长率 > 20%/天 |
| 存储 | file_count |
文件数量 | > 10000 |
| 快照 | snapshot_count |
快照数量 | - |
| 快照 | checkpoint_duration |
Checkpoint 时长 | > 5 分钟 |
9.3.2 Prometheus 配置
yaml
# Flink + Paimon 指标导出
metrics.reporter.prom.class: org.apache.flink.metrics.prometheus.PrometheusReporter
metrics.reporter.prom.port: 9249
metrics.scope.delimiter: "_"
# 指标示例
# paimon_write_records_total{job="flink_job", task="paimon_sink"}
# paimon_compaction_pending_files{table="paimon_db.orders"}
9.3.3 Grafana 仪表板
json
{
"dashboard": {
"title": "Paimon 监控",
"panels": [
{
"title": "写入 QPS",
"targets": [
{
"expr": "rate(paimon_write_records_total[1m])"
}
]
},
{
"title": "Compaction 积压",
"targets": [
{
"expr": "paimon_compaction_pending_files"
}
]
},
{
"title": "查询延迟 P99",
"targets": [
{
"expr": "histogram_quantile(0.99, rate(query_latency_bucket[1m]))"
}
]
}
]
}
}
9.3.4 告警规则(完整版)
yaml
groups:
- name: paimon
rules:
# ===== 写入告警 =====
- alert: PaimonWriteLag
expr: rate(paimon_write_records_total[5m]) < 1000
for: 5m
labels:
severity: warning
annotations:
summary: "Paimon 写入吞吐量过低"
description: "写入 QPS {{ $value }} 持续 5 分钟低于 1000"
- alert: PaimonWriteLatencyHigh
expr: histogram_quantile(0.99, rate(paimon_write_latency_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "Paimon 写入延迟过高"
description: "写入延迟 P99 {{ $value }}s 超过 1 秒"
# ===== Compaction 告警 =====
- alert: PaimonCompactionBacklog
expr: paimon_compaction_pending_files > 100
for: 10m
labels:
severity: warning
annotations:
summary: "Paimon Compaction 积压"
description: "待 Compaction 文件数 {{ $value }} 超过 100"
- alert: PaimonCompactionSlow
expr: paimon_compaction_duration_seconds > 1800
for: 5m
labels:
severity: warning
annotations:
summary: "Compaction 耗时过长"
description: "Compaction 耗时 {{ $value }} 秒超过 30 分钟"
- alert: PaimonFileCountGrowthFast
expr: rate(paimon_file_count_total[1h]) > 0.2
for: 1h
labels:
severity: warning
annotations:
summary: "文件数增长过快"
description: "文件数增长率 {{ $value }}%/小时超过 20%"
# ===== 查询告警 =====
- alert: PaimonQueryLatencyHigh
expr: histogram_quantile(0.99, rate(paimon_query_latency_bucket[5m])) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "Paimon 查询延迟过高"
description: "查询延迟 P99 {{ $value }}s 超过 5 秒"
# ===== Checkpoint 告警 =====
- alert: PaimonCheckpointSlow
expr: flink_taskmanager_job_task_checkpoint_duration_seconds > 300
for: 5m
labels:
severity: critical
annotations:
summary: "Flink Checkpoint 过慢"
description: "Checkpoint 时长 {{ $value }} 秒超过 5 分钟"
- alert: PaimonCheckpointFailed
expr: rate(flink_taskmanager_job_task_checkpoint_failed_total[5m]) > 0
for: 2m
labels:
severity: critical
annotations:
summary: "Flink Checkpoint 失败"
description: "Checkpoint 失败次数 {{ $value }}"
# ===== 存储告警 =====
- alert: PaimonStorageGrowthFast
expr: rate(paimon_storage_size_bytes[1d]) > 0.2
for: 1d
labels:
severity: warning
annotations:
summary: "Paimon 存储增长过快"
description: "存储大小日增长率 {{ $value }} 超过 20%"
- alert: PaimonFileCountTooHigh
expr: paimon_file_count > 10000
for: 30m
labels:
severity: warning
annotations:
summary: "Paimon 文件数过多"
description: "文件数 {{ $value }} 超过 10000,建议 Compaction"
# ===== 快照告警 =====
- alert: PaimonSnapshotNotCreated
expr: time() - paimon_snapshot_created_timestamp > 600
for: 5m
labels:
severity: critical
annotations:
summary: "Paimon 快照创建停滞"
description: "超过 10 分钟未创建新快照"
9.3.5 告警阈值参考表
| 指标 | 警告阈值 | 严重阈值 | 说明 |
|---|---|---|---|
| 写入 QPS | < 1000 (5m) | < 500 (5m) | 根据实际业务调整 |
| 写入延迟 P99 | > 1s (5m) | > 5s (5m) | 流式写入延迟 |
| Compaction 积压 | > 100 (10m) | > 200 (10m) | 待 Compaction 文件数 |
| Compaction 耗时 | > 30m (5m) | > 60m (5m) | 单次 Compaction 时长 |
| 文件数增长率 | > 20%/h (1h) | > 50%/h (1h) | 小文件增长速度 |
| 查询延迟 P99 | > 5s (5m) | > 10s (5m) | 即席查询延迟 |
| Checkpoint 时长 | > 5m (5m) | > 10m (5m) | Flink Checkpoint 时长 |
| Checkpoint 失败 | > 0 (2m) | > 3 (5m) | Checkpoint 失败次数 |
| 存储增长率 | > 20%/d (1d) | > 50%/d (1d) | 日存储增长率 |
| 文件总数 | > 10000 (30m) | > 20000 (30m) | 总文件数 |
| 快照停滞 | > 10m (5m) | > 30m (5m) | 未创建新快照时间 |
for: 1d
labels:
severity: warning
annotations:
summary: "Paimon 存储增长过快"
description: "存储大小日增长率超过 20%"
---
## 9.4 运维最佳实践
### 9.4.1 日常巡检清单
每日巡检:
\] 检查 Flink 作业状态(运行中/失败) \[ \] 检查 Checkpoint 是否正常 \[ \] 检查写入 QPS 是否正常 \[ \] 检查 Compaction 是否积压 \[ \] 检查存储增长是否正常 每周巡检: \[ \] 检查表文件大小分布 \[ \] 检查快照数量(清理过期快照) \[ \] 检查查询性能趋势 \[ \] 检查错误日志 每月巡检: \[ \] 评估存储容量规划 \[ \] 评估计算资源使用 \[ \] 回顾告警记录 \[ \] 优化表设计(如需要) ### 9.4.2 快照管理 ```sql -- 1. 查看快照历史 SELECT * FROM table_name$snapshots ORDER BY snapshot_id DESC LIMIT 10; -- 2. 查看快照详情 SELECT * FROM table_name$snapshots WHERE snapshot_id = 100; -- 3. 清理过期快照(保留最近 100 个) CALL paimon.system.expire_snapshots( table_path => 'paimon_catalog.paimon_db.table_name', retain_max => 100 ); -- 4. 清理指定时间前的快照 CALL paimon.system.expire_snapshots( table_path => 'paimon_catalog.paimon_db.table_name', retain_timestamp => '2026-02-01 00:00:00' ); #### 9.4.3 故障恢复流程 1. 识别故障 - 检查 Flink 作业状态 - 检查错误日志 - 检查监控指标 2. 定位原因 - 资源不足?→ 扩容 - 配置不当?→ 调整配置 - 数据异常?→ 数据修复 3. 执行恢复 - 重启作业(从 Savepoint) - 修复数据(SQL 或脚本) - 验证数据一致性 4. 复盘改进 - 记录故障原因 - 制定改进措施 - 更新监控告警 *** ** * ** *** ### 9.5 本章总结 #### 核心要点 1. **常见问题**:写入延迟、查询慢、Compaction 积压 2. **性能调优**:Bucket 数、Compaction 配置、内存配置 3. **监控指标**:写入 QPS、Compaction 积压、查询延迟 4. **告警规则**:阈值合理,避免误报 5. **运维实践**:日常巡检、快照管理、故障恢复 #### 关键命令 ```sql -- 查看文件分布 SELECT * FROM table_name$files; -- 查看快照历史 SELECT * FROM table_name$snapshots; -- 手动 Compaction ALTER TABLE table_name EXECUTE COMPACT; -- 清理过期快照 CALL paimon.system.expire_snapshots('table_path', 100); ``` #### 系列总结 本系列 9 章全面介绍了 Apache Paimon 的技术原理和生产实践: * **Ch01-04**:入门与核心原理 * **Ch05-07**:高级特性与生产案例 * **Ch08-09**:技术选型与运维指南 希望本系列能帮助读者全面掌握 Paimon,构建高效的流式湖仓架构! *** ** * ** *** ### 参考文献 1. [Apache Paimon 官方文档](https://paimon.apache.org/) 2. [Paimon - Flink Forward 2022 演讲](https://flink-forward.org/cn/agenda/fill-the-lakehouse-missing-piece-streaming-data-lake-with-apache-paimon) 3. [Paimon GitHub 仓库](https://github.com/apache/paimon) 4. [Paimon 中文社区](https://paimon.apache.org/zh/community/) *** ** * ** *** ### 免责声明 本文档内容基于 Apache Paimon 官方文档(版本 1.3)整理,旨在提供技术参考和学习资料。 1. **技术准确性** :本文档内容力求准确,但技术产品会持续更新,建议读者以 [Apache Paimon 官方文档](https://paimon.apache.org/) 为准。 2. **代码示例**:文档中的代码示例已通过验证,但实际生产环境中请根据具体场景进行调整和测试。 3. **使用风险**:读者基于本文档内容进行的技术决策和操作,需自行承担相应风险。 4. **版权声明**:Apache Paimon 是 Apache 软件基金会的注册商标。本文档内容采用 Apache License 2.0 协议。 5. **更新维护**:本文档会随 Paimon 版本更新进行定期复审和修订,建议关注最新版本。