第11章:Changelog变更日志
导言:实时数据流的核心
在第10章,我们讲了同一主键的多个版本如何合并 。现在需要讨论如何让下游系统感知到这些变化 。答案就是Changelog(变更日志)。
Changelog的作用:
ini
原始表数据:
order_id=1: status="未支付" → "已支付" → "已发货" → "已签收"
Changelog记录:
{order_id=1, status="未支付", _change_flag=INSERT}
{order_id=1, status="已支付", _change_flag=UPDATE_AFTER}
{order_id=1, status="已发货", _change_flag=UPDATE_AFTER}
{order_id=1, status="已签收", _change_flag=UPDATE_AFTER}
下游系统(如Kafka、实时处理)可以实时感知这些变化
第一部分:Changelog的基本概念
1.1 Change Flag(变化标记)
每条Changelog记录都有一个Change Flag,表示数据的变化类型:
sql
+I (INSERT):新增数据
-D (DELETE):删除数据
-U (UPDATE_BEFORE):更新前的镜像(用于旧值)
+U (UPDATE_AFTER):更新后的值(用于新值)
例子:
订单状态从"未支付"更新为"已支付"
Changelog:
-U: {order_id=1, status="未支付"} ← 旧值
+U: {order_id=1, status="已支付"} ← 新值
下游系统可以:
├─ 看到完整的前后对比
├─ 识别出是UPDATE操作
└─ 可以决定如何处理(仅保留新值、两者都保留等)
1.2 ChangelogProducer(变更日志生成器)
ChangelogProducer负责生成和维护Changelog:
sql
配置:
changelog-producer: NONE | INPUT | FULL_COMPACTION | LOOKUP
NONE:不生成Changelog
INPUT:仅从输入生成(只有INSERT/DELETE)
FULL_COMPACTION:全量压缩时生成(完整的前后镜像)
LOOKUP:使用Lookup表加速(实时生成)
第二部分:Changelog的生成模式
2.1 NONE模式
yaml
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
status STRING,
...
) WITH (
'changelog-producer' = 'NONE'
);
特点:
├─ 不生成Changelog
├─ 性能最优(减少开销)
└─ 下游看不到数据变化
使用场景:
└─ 不需要实时感知变化的场景
如纯分析表、非实时处理
2.2 INPUT模式
yaml
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
status STRING,
...
) WITH (
'changelog-producer' = 'INPUT'
);
生成的Changelog:
只包含来自应用的INSERT和DELETE
不包含更新操作的前值
例子:
应用插入:INSERT order_id=1, status="未支付"
→ Changelog: +I {order_id=1, status="未支付"}
应用删除:DELETE order_id=1
→ Changelog: -D {order_id=1, status="未支付"}
应用更新:UPDATE status="已支付" WHERE order_id=1
→ Changelog: +I {order_id=1, status="已支付"} (新值)
-D {order_id=1, status="未支付"} (旧值)
特点:
├─ 生成完整的前后镜像
├─ 开销中等
└─ 适合需要UPDATE前值的场景
2.3 FULL_COMPACTION模式
yaml
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
status STRING,
...
) WITH (
'changelog-producer' = 'FULL_COMPACTION'
);
生成的Changelog:
仅在Compaction完成时生成
包含完整的前后镜像
流程:
Time 1: 用户插入order_id=1, status="未支付"
→ 内部写入Level 0,暂不生成Changelog
Time 2: 用户更新status="已支付"
→ 内部写入Level 0,暂不生成Changelog
Time 3: Compaction触发(Level 0→Level 1)
→ Changelog生成!
-D {order_id=1, status="未支付"}
+U {order_id=1, status="已支付"}
特点:
├─ Changelog生成延迟(等待Compaction)
├─ 性能最优(批量生成)
└─ 适合可接受短延迟的场景
2.4 LOOKUP模式
yaml
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
status STRING,
...
) WITH (
'changelog-producer' = 'LOOKUP'
);
生成的Changelog:
实时生成,每次写入时立即生成
流程:
Time 1: 用户插入order_id=1, status="未支付"
→ 立即生成Changelog: +I {order_id=1, status="未支付"}
Time 2: 用户更新status="已支付"
→ Lookup旧值(从现有数据读取)
→ 立即生成Changelog:
-U {order_id=1, status="未支付"} (旧值来自Lookup)
+U {order_id=1, status="已支付"} (新值来自输入)
特点:
├─ 实时生成,无延迟
├─ 开销较大(每次Lookup)
└─ 适合实时流处理
第三部分:Changelog的消费
3.1 从Paimon读取Changelog
java
// 创建支持Changelog的表扫描
StreamDataTableScan scan = table
.newStreamScan()
.withChangelogMode(ChangelogMode.ALL); // 读取所有变化
// 扫描Changelog
DataIterator iterator = scan.plan().execute();
while (iterator.hasNext()) {
Row row = iterator.next();
// 获取变化标记
RowKind kind = row.getKind();
switch (kind) {
case INSERT:
System.out.println("新增:" + row);
break;
case UPDATE_BEFORE:
System.out.println("更新前:" + row);
break;
case UPDATE_AFTER:
System.out.println("更新后:" + row);
break;
case DELETE:
System.out.println("删除:" + row);
break;
}
}
3.2 Flink消费Changelog
java
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
TableEnvironment tableEnv = StreamTableEnvironment.create(env);
// 注册Paimon表
tableEnv.executeSql(
"CREATE TABLE orders ("
+ " order_id BIGINT,"
+ " status STRING,"
+ " PRIMARY KEY (order_id) NOT ENFORCED"
+ ") WITH ("
+ " 'connector' = 'paimon',"
+ " 'path' = '/path/to/paimon/orders'"
+ ")");
// 实时读取Changelog
DataStream<Row> stream = tableEnv.toChangelogStream(
tableEnv.sqlQuery("SELECT * FROM orders"));
stream.print();
env.execute();
3.3 Kafka同步
Paimon可以将Changelog实时同步到Kafka:
yaml
CREATE TABLE orders_kafka (
order_id BIGINT,
status STRING,
...
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'connector' = 'kafka',
'topic' = 'orders-changelog',
'properties.bootstrap.servers' = 'localhost:9092',
'format' = 'avro'
);
-- 同步Changelog到Kafka
INSERT INTO orders_kafka
SELECT * FROM orders WHERE __op <> 'before';
第四部分:生产级配置
4.1 实时数据同步场景
yaml
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
user_id BIGINT,
amount DECIMAL,
status STRING,
created_at BIGINT,
updated_at BIGINT,
...
) WITH (
'changelog-producer' = 'LOOKUP',
'changelog.num.retained.min' = '5',
'changelog.num.retained.max' = '10'
);
-- 配置说明:
-- changelog-producer:使用LOOKUP实时生成
-- changelog.num.retained.min/max:最少保留5个,最多10个Snapshot的Changelog
下游消费:
Flink CDC Source → Paimon Table
↓
Changelog生成
↓
Kafka Sink(同步给消费方)
4.2 低延迟分析场景
yaml
CREATE TABLE metrics (
hour STRING PRIMARY KEY,
page_id STRING PRIMARY KEY,
pv BIGINT,
uv BIGINT,
...
) WITH (
'changelog-producer' = 'FULL_COMPACTION',
'compaction.min.file-num' = '3',
'num-sorted-run-compaction-trigger' = '3'
);
-- 配置说明:
-- 激进的Compaction触发,使得Changelog定期生成
-- 下游可以定时拉取Changelog进行分析
第五部分:Changelog的局限与最佳实践
5.1 选择合适的模式
css
场景1:实时数据同步(CDC)
→ 使用LOOKUP
└─ 需要实时前后镜像
场景2:准实时分析(小时级)
→ 使用FULL_COMPACTION
└─ 可接受Compaction延迟
场景3:纯仓库(无下游实时处理)
→ 使用NONE
└─ 最优性能
5.2 开销管理
arduino
LOOKUP模式开销:
├─ 每条记录都要Lookup旧值
├─ 额外的读取IO:~2倍
└─ 若表频繁更新,开销严重
FULL_COMPACTION模式开销:
├─ Changelog生成不涉及额外IO
├─ 仅在Compaction时生成
└─ 总体开销低,但有延迟
建议:
├─ 小表(<1GB)→ LOOKUP(可接受额外开销)
├─ 中表(1-100GB)→ FULL_COMPACTION(平衡方案)
└─ 大表(>100GB)→ 考虑不启用Changelog或使用INPUT
总结
Changelog的核心价值
markdown
Paimon表
↓ (启用Changelog)
原始数据 + Changelog数据流
↓
下游系统可以:
├─ 实时感知数据变化
├─ 获取完整的前后镜像
├─ 同步到其他系统(Kafka、数据库等)
└─ 支持CDC(Change Data Capture)
配置checklist
- 根据业务选择changelog-producer模式
- 设置合理的changelog retention(保留时间)
- 如果性能敏感,评估开销
- 配置下游消费者的处理逻辑
下一章预告:第12章讲解索引与加速,包括Deletion Vector、文件索引、统计信息