万字长文解析Apache Paimon

第一章:Apache Paimon 入门与架构概览


1.1 什么是 Apache Paimon

1.1.1 Paimon 的定位

Apache Paimon 是一个流式湖仓存储格式(Streaming Lakehouse Storage Format),专为实时数据湖设计。它提供了流批统一的存储抽象,能够同时支持高吞吐的实时写入和高效的批量查询。

Paimon 的核心定位可以概括为三点:

  1. 实时湖仓存储:填补了传统数据湖(如 Iceberg、Hudi)在实时写入场景下的能力空白
  2. 流批统一:同一份数据既支持流式读取(实时消费),也支持批量读取(历史分析)
  3. 多引擎支持:与 Flink 深度集成,同时支持 Spark、StarRocks、Doris、Trino 等多种计算引擎

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 本章小结

核心要点回顾

  1. Paimon 的定位:流式湖仓存储格式,填补了传统数据湖在实时场景下的能力空白
  2. 核心特性:流批统一、高吞吐更新、多引擎支持
  3. 架构优势:基于快照和清单机制,支持批式、流式、增量多种读取模式
  4. 写入方式:支持 CDC 流式同步和批量插入/覆盖
  5. 生态集成:与 Flink 深度集成,同时支持 Spark、StarRocks、Doris、Trino 等引擎

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 读取任务单元,包含分区、桶、快照信息

读取流程

  1. SplitEnumerator 扫描 Paimon 表的分区和桶
  2. 根据查询条件生成 PaimonSplit 列表
  3. 将 Split 分配给下游的 SourceReader
  4. SourceReader 解析 Manifest 文件,定位 Data 文件
  5. 并行读取 Data 文件(Parquet/ORC/Avro),返回数据记录

批式 vs 流式读取

模式 读取内容 适用场景
批式 指定快照的全部数据 离线分析、历史数据查询
流式 从最新快照持续消费增量 实时 ETL、实时数仓

2.1.3 Sink 实现原理

Paimon Sink 负责将 Flink 处理后的数据写入 Paimon 表,支持UpsertAppend两种写入模式。

核心组件

组件 作用
PaimonSink Sink 入口,配置写入参数
PaimonCommitter 提交器,负责快照的原子提交
PaimonWriter 实际执行写入的写入器
PaimonMultiTableSink 多表写入支持(CDC 场景)

写入流程

  1. PaimonWriter 接收 Flink 数据流,缓存到 MemTable
  2. MemTable 达到阈值后,flush 到磁盘生成 SSTable 文件
  3. 后台 Compaction 线程合并小文件,优化查询性能
  4. PaimonCommitter 在 Checkpoint 完成时提交快照
  5. 更新 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)

  1. Pre-commit:Checkpoint 触发时,Sink 预提交数据(写入文件但未更新快照)
  2. Commit:Checkpoint 成功后,Committer 原子性地更新快照
  3. 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 集成

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 行/秒
  • 查询延迟:秒级(亿级数据)

本章小结

核心要点回顾

  1. Flink Connector 架构:Source 负责读取,Sink 负责写入,利用 Checkpoint 实现 Exactly-Once
  2. 流式读写:支持 latest/earliest/specific 三种消费模式,Upsert/Append 两种写入模式
  3. 批式读写:支持 INSERT OVERWRITE 分区覆盖,适合离线分析场景
  4. CDC 集成:支持 MySQL/PostgreSQL 等数据库的实时同步,自动处理 Schema Evolution
  5. 实战案例:实时数仓、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/)       │
└─────────────────┘

这种分层设计的优势:

  1. 高效快照访问:通过 Snapshot → Manifest → Data 的层级,可以快速定位任意快照的全部数据
  2. 增量追踪:Manifest 记录文件级别的变更,支持高效的增量读取
  3. 并发控制:快照不可变,支持多读者并发访问

3.1.3 从 Snapshot 递归访问所有记录

Paimon 的查询引擎通过以下步骤从 Snapshot 递归访问所有数据记录:

  1. 读取 Snapshot 文件:解析 JSON 格式的快照,获取 Manifest List
  2. 解析 Manifest List:获取本次快照涉及的所有 Manifest 文件
  3. 解析 Manifest 文件:读取每个 Manifest,获取数据文件列表及其状态(新增/删除/修改)
  4. 读取 Data 文件:根据 Manifest 中的文件路径,并行读取数据文件
  5. 合并结果:根据 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 的作用:

  1. 快速定位:查询时无需遍历所有 Manifest,直接通过 Manifest List 定位
  2. 分区裁剪:根据分区统计信息,跳过不相关的 Manifest
  3. 增量识别:通过对比不同快照的 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    │ (删除标记)
                              └─────────────┘

变更追踪流程

  1. 写入新数据时,生成新的数据文件(file-C)
  2. 删除旧数据时,在 Manifest 中标记删除(file-A)
  3. 提交快照时,将新增的 Manifest 加入 Manifest List
  4. 查询时,根据 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 一致:

  • 分区是将表数据按某个字段的值进行物理隔离
  • 每个分区对应文件系统中的一个子目录
  • 查询时可以分区裁剪,只扫描相关分区

分区字段的选择

  • 通常是时间字段(如 dtevent_date
  • 也可以是业务字段(如 regioncategory
  • 分区字段不能是主键的一部分

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. 快照可见

两阶段提交流程

  1. Stage 1(准备):写入数据文件和 Manifest 文件
  2. 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 会检测到快照已更新
  • 提交失败,触发重试或回滚

本章小结

核心要点回顾

  1. 文件布局:Paimon 采用分层组织(Snapshot → Manifest → Data),支持高效访问
  2. 快照机制:每个快照代表一致性视图,支持时间旅行和增量消费
  3. Manifest 文件:记录数据文件变更,支持增量追踪和查询优化
  4. 数据文件:按分区和桶组织,支持 Parquet/ORC/Avro 多种格式
  5. 分区设计:类似 Hive,支持分区裁剪和分区级操作
  6. 一致性保证:通过 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 预写日志,防止数据丢失 磁盘

写入流程

  1. 新数据写入 WAL(Write-Ahead Log),保证持久性
  2. 数据插入 MemTable(内存有序结构)
  3. MemTable 达到阈值后,转为 Immutable MemTable
  4. 后台线程将 Immutable MemTable flush 到磁盘,生成 L0 SSTable
  5. 当某层 SSTable 数量超过阈值,触发 Compaction,合并到下一层

读取流程

  1. 先在 MemTable 中查找
  2. 未命中则从 L0 → L1 → L2 ... 逐层查找
  3. 每层使用 Bloom Filter 加速判断数据是否存在
  4. 合并所有层找到的数据,返回最新版本

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 特点

  1. 按分区和桶组织:每个分区 + 桶组合有独立的 SSTable 文件
  2. 支持多种格式:Parquet(默认)、ORC、Avro
  3. 包含统计信息:最小/最大 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%

最佳实践

  1. 监控 Compaction 频率:过于频繁说明写入压力大,需要调整参数
  2. 避免 Full Compaction 高峰:在业务低峰期执行
  3. 合理设置目标文件大小:过大影响查询,过小增加文件数

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 以节省空间。


本章小结

核心要点回顾

  1. LSM Tree 基础:将随机写转换为顺序写,大幅提升写入吞吐量
  2. Paimon 的 LSM 实现:MemTable + SSTable + 分层结构,支持分区和桶
  3. Compaction 策略:Minor/Major/Full 三种合并策略,平衡写入和查询性能
  4. 主键表:支持 Upsert 和删除,适用于 CDC 同步和实时数仓
  5. 追加表:仅追加,性能更高,适用于日志收集和事件流
  6. 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  │
│  分布式  │            │   对象存储   │          │  本地存储  │
└─────────┘            └─────────────┘          └───────────┘

Flink 是 Paimon 的首选计算引擎,提供最全的功能支持和最优的性能表现。

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 本章总结

核心要点

  1. 多引擎支持是 Paimon 的核心优势,实现"一次写入,多处查询"
  2. Flink是首选引擎,提供最完整的功能支持
  3. Spark 适合批量 ETL,StarRocks/Doris适合 OLAP 查询
  4. 快照机制保证多引擎读取的数据一致性
  5. 根据场景选择合适的引擎,发挥各自优势

关键代码

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
添加主键 ⚠️ 需确保数据唯一性 谨慎操作
修改分区 ⚠️ 影响文件组织 需重新组织数据
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 本章总结

核心要点

  1. Schema 演化支持添加/删除/修改列,无需重建表
  2. 多表关联注意维度表缓存和增量优化
  3. CDC 多表同步可实现整库自动入湖
  4. 性能调优关注 Bucket 数、Compaction、内存配置
  5. 数据质量需要定期校验和监控
  6. 生产规范包括表设计、作业配置、生命周期管理

关键配置

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;

合规建议:

  1. 生产环境:所有 PII(个人身份信息)必须脱敏后入湖
  2. 开发测试:使用脱敏后的生产数据副本
  3. 权限控制:原始数据仅限授权人员访问
  4. 审计日志:记录所有敏感数据访问行为

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 成功要素

  1. 统一存储格式:Paimon 作为统一存储层,简化架构
  2. 流批一体:同一份数据支持实时和离线场景
  3. 多引擎支持:灵活选择最适合的查询引擎
  4. 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 本章总结

核心案例

  1. 电商实时数仓:ODS→DWD→DWS 分层架构,秒级延迟
  2. 金融 CDC 同步:整库同步,断点续传,数据一致性校验
  3. 日志分析平台:100 亿+/日,实时错误监控
  4. 数据湖联邦查询:跨系统关联,无需数据搬迁

第八章:技术选型指南 - 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 本章总结

核心要点

  1. Paimon 定位:流式湖仓,实时写入 + 流式读取是核心优势
  2. 适用场景:Flink CDC、实时数仓、流式聚合、实时风控
  3. 技术对比:与 Iceberg/Hudi/Delta 各有优劣,根据场景选择
  4. 成本优势:存储成本降低 50%,运维成本降低 50%+
  5. 迁移路径:从 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 版本更新进行定期复审和修订,建议关注最新版本。

相关推荐
网络工程小王2 小时前
【大数据技术详解】——HIVE技术(学习笔记)
大数据·hive·hadoop
刘一说2 小时前
Git 工具知识全景图:从核心概念到高效协作实践
大数据·git·elasticsearch
MarsLord2 小时前
ElasticSearch快速入门实战(1)-索引、别名、建模最佳实践
大数据·elasticsearch·搜索引擎
徐礼昭|商派软件市场负责人2 小时前
“80%应用将消亡”?后App时代:AI智能体重构人机交互与数字商业新秩
大数据·人工智能·人机交互·零售·智能搜索·ai推荐
九河云2 小时前
容器化与微服务:企业上云过程中的技术债务治理
大数据·微服务·云原生·重构·架构·数字化转型
专注VB编程开发20年2 小时前
深思数盾国产.NET 加密工具与 VMProtect、.NET Reactor、Zprotect、ILProtector 的优缺点
大数据·网络·.net·加密·加壳
郭龙_Jack2 小时前
数据中台-大数据维度工程实施应用示例
大数据
远方16092 小时前
116-Oracle 26ai 断言(assertion)新特性
大数据·数据库·sql·oracle·database·ai编程
八月瓜科技2 小时前
擎策·知海全球专利数据库 技术赋能检索 让科技创新少走弯路
大数据·数据库·人工智能·科技·深度学习·娱乐