Apache Doris 4.0.8 CDC 正确性(第 9 篇):Flink Checkpoint 一直成功,表里为什么仍是旧数据

Flink Checkpoint 和 Doris 2PC 都成功,订单 9001 却从已支付退回待支付。没有重复事务,也没有丢失事件;一条更旧的 CDC 记录最后到达,并被精确提交了一次。

2PC 保证批次提交边界,Unique Key 保证一主键一行,只有可靠 Sequence 才能决定乱序事件谁获胜。

四层机制各自只管一件事

层次 负责 不负责
Flink Checkpoint 算子状态与 Source 位点一致恢复 业务事件新旧
Doris Stream Load 2PC Checkpoint 对应事务只提交一次 主键设计和列值正确
Unique Key MoW 相同 Key 只暴露一个逻辑版本 没有 Sequence 时的业务顺序
Sequence 同 Key 按业务版本选胜者 跨 Key 事务约束和漏事件

把这四层都叫 Exactly-Once,事故发生后就只能互相甩锅。

复现一次旧状态回写

错误表只有 Unique Key,没有 Sequence:

sql 复制代码
CREATE TABLE order_state_bad (
    order_id BIGINT,
    op_version BIGINT,
    status VARCHAR(16),
    amount DECIMAL(12,2)
)
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 4
PROPERTIES ("enable_unique_key_merge_on_write" = "true");

先写版本 105 的已支付,再写迟到的版本 103:

sql 复制代码
INSERT INTO order_state_bad VALUES (9001, 105, 'PAID', 100.00);
INSERT INTO order_state_bad VALUES (9001, 103, 'PENDING', 100.00);

后到行成为可见版本。正确表必须把业务版本映射为 Sequence:

sql 复制代码
CREATE TABLE order_state_good (
    order_id BIGINT,
    op_version BIGINT,
    status VARCHAR(16),
    amount DECIMAL(12,2)
)
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 4
PROPERTIES (
    "enable_unique_key_merge_on_write" = "true",
    "function_column.sequence_col" = "op_version"
);

同样顺序写入后,版本 105 仍然获胜。

Sequence 选择比字段本身更重要

候选 Sequence 优点 经典失败
应用更新时间 直观 时钟回拨、同秒冲突、不同服务不可比
数据库自增版本 单表稳定 分库后不一定全序
Binlog 文件与 Position 源库提交顺序可靠 需要编码成可比较值,跨源无天然顺序
Kafka Offset 单 Partition 单调 跨 Partition 不可直接比较
业务状态序号 符合领域规则 需要业务明确状态机和补偿语义

不存在脱离 Source 拓扑的万能 Sequence。一个订单的所有事件如果可能分散到多个 Kafka Partition,Offset 就不能直接做全局版本。

DDL 演进会制造另一种旧数据

上游增加字段后,Flink 状态、Connector 映射和 Doris Schema 的生效顺序不一致,可能产生:新字段丢失、默认值覆盖、部分更新失败或整批过滤。

安全顺序应是:

text 复制代码
目标表先兼容新字段
→ Connector 能识别旧与新两种事件
→ 上游开始发送新 Schema
→ 对账新字段非空率与默认值
→ 最后清理旧兼容逻辑

不要用 Checkpoint 成功证明 Schema Evolution 成功。Checkpoint 只保存作业当时接受到的状态。

排障需要两张表

Unique Key 服务表只能看胜者,失败版本可能已被 Delete Bitmap 隐藏。保留一张 Duplicate Key 审计表,记录 Source 位点、事件类型、业务版本和原始载荷:

sql 复制代码
SELECT order_id, op_version, op_type, source_offset, ingest_time
FROM order_event_audit
WHERE order_id = 9001
ORDER BY op_version, ingest_time;

再与服务表比较:

sql 复制代码
SELECT order_id, op_version, status
FROM order_state_good
WHERE order_id = 9001;

审计表回答发生过什么,服务表回答现在是什么。没有审计表时,状态被覆盖后很难证明错误来自源端乱序还是 Sink 合并。

源码证据落在 Delete Bitmap

Doris 4.0.8BaseTablet::calc_segment_delete_bitmap 会查找历史 Rowset 的同 Key 行。历史行 Sequence 更大时,新到行自身进入 Delete Bitmap;新行 Sequence 更大时,历史位置进入 Delete Bitmap。

text 复制代码
Checkpoint 决定这批是否提交
Sequence 决定同 Key 哪行获胜
Delete Bitmap 决定查询跳过哪行
Compaction 最终回收失败版本

这条链解释了为什么不用原地更新也能立即查询唯一状态,也解释了写入侧要承担主键查找和位图维护成本。

修复不能只补一个属性

现有错误表不能靠修改属性安全变成有 Sequence 的正确表。生产修复通常需要新建目标表、从审计事件按最大业务版本重建、双写对账、切换读流量,再停止旧表。

验收至少覆盖 Insert、Update、Delete、同版本冲突、跨 Partition 乱序、Checkpoint 恢复和 DDL 变化。

Flink 可以把每条事件可靠地送到终点,但只有业务版本才能告诉 Doris 哪条事件代表现在。

官方资料与源码

相关推荐
Thomas214314 分钟前
flink处理迟到数据
大数据·flink
中防喷墨32 分钟前
在线式UV喷印机参数与性能综合总结评测
经验分享·生活
CIO401 小时前
AI未来--IT人面试36计
人工智能·面试·职场和发展
拾光Ծ2 小时前
【MySQL】对表数据的操作:增删查改(CRUD)
android·数据库·sql·mysql
IT研究室3 小时前
最新大数据毕业设计选题推荐-基于大数据的电商与本地服务消费评论数据可视化分析-大数据-Spark-Hadoop-Bigdata
大数据·信息可视化·课程设计
大大大大晴天️4 小时前
从 HDFS 到对象存储:计算存储分离如何重塑云原生大数据底座
大数据·云原生
kyriewen13 小时前
我把今年流传的前端 AI 面试题整理了一遍——4 类场景题+回答框架(附速查表)
前端·面试·程序员
大大大大晴天14 小时前
从 HDFS 到对象存储:计算存储分离如何重塑云原生大数据底座
大数据
这个DBA有点耶15 小时前
2026年分布式数据库有哪些主流选择?先评估这5个维度再决定
数据库·分布式·dba