更新操作在cdc中被解析为删除+新增(升级v7.1.3)

Bug 反馈
清晰准确地描述您发现的问题,提供任何可能复现问题的步骤有助于研发同学及时处理问题

【 TiDB 版本】v7.1.3
【 Bug 的影响】
下游根据cdc增量日志做衍生计算的数据出现大量先删除,再插入的情况,导致下游业务更新量迅速放大。
现在也不确定是cdc的增量日志捕捉问题,还是数据库中确实做了删除再新增, 至少从cdc到kafka和cdc到存储两个方面确认,确实部分更新操作被拆解成了删除+更新。

此处纠正一下:手动更新也能重现,更新某些字段会重现,有些字段不会
最终的测试结果为:更新唯一性字段,会有问题

【可能的问题复现步骤】
版本由v7.1.1 升级至v7.1.3

【看到的非预期行为】
升级之后立即出现好多表的更新操作,在升级之后通过cdc推送到kafka的增量日志变成了delete+insert, 不是所有记录都有这个现象,部分出现此现象的记录,如果用sql语句再去直接update,也不复现。

【期望看到的行为】
正常的更新操作,在cdc的增量日志中,应该就是一次update

【相关组件及具体版本】
刚刚升级到v7.1.3

【其他背景信息或者截图】

首先回答,默认情况是有的,这个不是缺陷,是使用方式的问题。
为了保证数据下去下游是可重入的,所以会对写入sql 有改写。

可以修改关闭safe mode参数的方法来达到你的目的。

1 个赞

之前做测试时发现v5.3.0 的 TiCDC 工具会把上游的 Replace 转换为下游的 Delete + Replace 操作,会有写放大的现象。当前版本下,经过观察上游的 DML 经过 TiCDC 工具后会有下面的转换:

  • 1 delete → 1 delete
  • 1 insert → 1 delete + 1 replace
  • 1 update → 1 delete + 1 replace
  • 1 replace → 1 delete + 1 replace

默认情况下,ticdc 的 mysql sink 使用 safe_mode ,即上面的方式,会影响性能,但是可以保证同步可重入。 mysql sink 配置可关闭 safe_mode(ticdc v6.1.3 默认情况下关闭 safeMode)。

为了保证业务数据同步的幂等性,还是保留默认的 safe_mode 配置。楼主根据需要调整一下就好。

1 个赞

目前的现象是如果更新了主键或者唯一性字段,更新操作会被拆分为delete+update, 但是我看文档里说safe mode 只有下游是mysql或者tidb时才有效,但我现在下游是kafka

:joy:

你这个表述有点绕。
我可以理解为,有的表cdc到下游是update,有的表cdc到下游是delete+update。
出现delete+update的表,直接update也是可以的,你是这个意思吗?

https://github.com/pingcap/tiflow/issues/9086

我觉得你可以看看这个。

Avro and CSV Protocol, does not emit the old value for the update event to downstream, so if the primary key is updated, the old data cannot be deleted from the downstream data system.

By split the update event into delete event and insert event, the old data can be delete first, and then insert the new data.

看上去像是为了兼容某些协议而作出的增强。
我得承认我对ticdc并不熟悉。这算是一种可能性。

看起来有点接近,但我用的open-protocal协议

上游更新主键时,确实应该在下游变为delete+update,我觉得还比较合理吧 :flushed:

1 个赞

某种程度来说是合理的,但是不能在升级的时候,默认就调整了,也没有在升级文档中说明,或者有个开关能关闭也行,不然会对现有系统造成冲击的。

1 个赞

我觉得还是要确认下是否和主键更新有关。

如果确实是主键更新-》delete+update,非主键更新-》update。这就比较正常。
怕的是非主键更新也是 delete+update。

只要不是bug,其他的年后再说吧。
都这个时候了,能不卷,就不卷了。 :joy:

反复测试过了,确实是和更新唯一性字段有关,更新其他字段正常

1 个赞

先删 再replace。写放大, 我想知道为啥这么搞的原因

删除再insert是转成replace语句了吧

我这边是kafka消息,变成两条消息了,先删除,后新增

cdc 默认开启safe-mod, 你关掉就好了

请问cdc to kafka 的safe-mode在哪里配置的,我在文档里翻了半天没看到

各位好,我是该功能的开发者,很高心看到大家对该问题的探讨。

  • cdc 到 kafka 链路,没有 safe-mode 参数。
  • 在使用 kafka sink 的情况下,update 事件的拆分逻辑如下。对于单条 update 事件,如果发生了 primary key 或者 unique key 的修改,那么拆分为 delete + insert 语句。

关于 update 事件拆分,用户观察到的是输出内容发生了改变。该行为变更,表面上是对某些协议,如 avro,csv 的兼容,实际上它还牵扯了其他模块的功能正确性,如 index-value dispatcher。

考虑如下 SQL 语句

create table t (a int primary key, b int);
insert into t values (1, 2);
update t set a = 2 where a = 1;

使用 index-value dispatcher,将上述两条 sql 语句的变更,分发到有多个 partitions 的 topic。
第一条 insert message,假设发送到了 partition-1,该值是基于 primary key = 1 计算得到。
第二条 update 语句,如果不做拆分,那么分发到 partition-2.

可能出现的一个情况是,consumer 先消费了 partition-2 的数据,此时下游系统中可能并不存在 primary key = 1 的行,那么就可能出错。

拆分之后。update 语句变成了 delete 1, insert 2.
partition-1 的数据流,先后有 insert-1,delete-1。
partition-2 的数据有,有 insert-2.

这个时候,无论 consumer group 中的 consumer 消费进度如何,最终每个 key 对应的事件,都能被按照正确顺序消费。这即是做出上述改动的最核心动机。

关于 key updated 事件的拆分,参考了 Debezium connector for MySQL :: Debezium Documentation

可以看到,它们对 update 事件,是明确做出了区分的。

3 个赞

您好!
对于这个功能我们没有任何异议,也能够理解这么做的原因,但是,你们在原有的版本上不加任何参数,在release note里不加任何相关说明,这个做法就比较不好。
任何功能都是有使用场景的,对于cdc to kafka,我们的使用场景都是单表保证在单partition,来严格保证单表的数据顺序的,对于我们这种应用场景,根本不需要将在特殊场景下的update拆分成delete+insert。
你如果一开始就是这样设计的,我们也能够兼容,但是对一个现有的系统在没有任何说明的情况突然改变,我们下游的应用将变得非常被动。
现在我们全公司的技术leader都被召集在一起来评估此事件的影响,再确定是否要对tidb进行版本回滚。当然这个事情也有我们自己的问题,就是在开发站和测试站的测试未能提前发现此更改。

感谢研发大佬出来答疑解惑。

config(ticdc): enable-old-value always false if using avro or csv as the encoding protocol (#9079) by ti-chi-bot · Pull Request #9318 · pingcap/tiflow (github.com)
这个 PR 一次性改动的内容比较多,release-note 只覆盖了部分内容。
后续我们会对文档加以更新,说明情况。之后也会注意这种前后行为变化的修改。

单表单 partition,存在的一个问题是,cdc 吞吐量有限,kafka consumer 消费速率也不能横向扩展。如果单表流量非常大,使用 index-value dispatcher 是一个更好的选择,能够显著提升吞吐量。

针对你们的场景,不做拆分是不会引起问题的。针对更加普适的场景就有必要了。

很抱歉给你们造成了这么大的困扰。

cli changefeed create --pd=http://%s:%d --sink-uri=“mysql://%s:%s@%s:%d/?safe-mode=%s”