Skip to content

Commit

Permalink
ticdc: add output-raw-change-event parameter and update ticdc-behavio…
Browse files Browse the repository at this point in the history
…r-change (#17699) (#18032)
  • Loading branch information
ti-chi-bot authored Jul 22, 2024
1 parent a1ba651 commit e1a971e
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 59 deletions.
2 changes: 1 addition & 1 deletion TOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@
- [DDL 同步](/ticdc/ticdc-ddl.md)
- [双向复制](/ticdc/ticdc-bidirectional-replication.md)
- [主从集群一致性读和数据校验](/ticdc/ticdc-upstream-downstream-check.md)
- [TiCDC 行为变更说明](/ticdc/ticdc-behavior-change.md)
- [拆分 UPDATE 事件行为说明](/ticdc/ticdc-split-update-behavior.md)
- 监控告警
- [监控指标](/ticdc/monitor-ticdc.md)
- [报警规则](/ticdc/ticdc-alert-rules.md)
Expand Down
2 changes: 1 addition & 1 deletion releases/release-6.5.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ TiDB 版本:6.5.10

## 兼容性变更

- 在之前的版本中,TiCDC 在处理包含 `UPDATE` 变更的事务时,如果事件的主键或者非空唯一索引的列值发生改变,则会将该条事件拆分为 `DELETE``INSERT` 两条事件。从 v6.5.10 开始,当使用 MySQL Sink 时,如果 `UPDATE` 变更所在事务的 `commitTS` 小于对应表开始向下游同步数据时从 PD 获取的当前时间戳 `thresholdTS`,TiCDC 就会将该 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件,然后写入 Sorter 模块。该行为变更解决了由于 TiCDC 接收到的 `UPDATE` 事件顺序可能不正确,导致拆分后的 `DELETE``INSERT` 事件顺序也可能不正确,从而引发下游数据不一致的问题。更多信息,请参考[用户文档](https://docs.pingcap.com/zh/tidb/v6.5/ticdc-behavior-change#mysql-sink)[#10918](https://github.com/pingcap/tiflow/issues/10918) @[lidezhu](https://github.com/lidezhu)
- 在之前的版本中,TiCDC 在处理包含 `UPDATE` 变更的事务时,如果事件的主键或者非空唯一索引的列值发生改变,则会将该条事件拆分为 `DELETE``INSERT` 两条事件。从 v6.5.10 开始,当使用 MySQL Sink 时,如果 `UPDATE` 变更所在事务的 `commitTS` 小于对应表开始向下游同步数据时从 PD 获取的当前时间戳 `thresholdTS`,TiCDC 就会将该 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件,然后写入 Sorter 模块。该行为变更解决了由于 TiCDC 接收到的 `UPDATE` 事件顺序可能不正确,导致拆分后的 `DELETE``INSERT` 事件顺序也可能不正确,从而引发下游数据不一致的问题。更多信息,请参考[用户文档](https://docs.pingcap.com/zh/tidb/v6.5/ticdc-split-update-behavior#mysql-sink-拆分-update-事件行为说明)[#10918](https://github.com/pingcap/tiflow/issues/10918) @[lidezhu](https://github.com/lidezhu)
- 使用 TiDB Lightning 的严格格式 `strict-format` 导入 CSV 文件时,必须设置行分隔符 [#37338](https://github.com/pingcap/tidb/issues/37338) @[lance6716](https://github.com/lance6716)

## 改进提升
Expand Down
2 changes: 1 addition & 1 deletion releases/release-6.5.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ TiDB 版本:6.5.3

### 行为变更

- TiCDC 在处理 Update 事件时,如果事件的主键或者非空唯一索引的列值发生改变,则会将该条事件拆分为 Delete 和 Insert 两条事件。更多信息,请参考[用户文档](/ticdc/ticdc-behavior-change.md#含有单条-update-变更的事务拆分)
- TiCDC 在处理 Update 事件时,如果事件的主键或者非空唯一索引的列值发生改变,则会将该条事件拆分为 Delete 和 Insert 两条事件。更多信息,请参考[用户文档](/ticdc/ticdc-split-update-behavior.md#含有单条-update-变更的事务拆分)

## 改进提升

Expand Down
2 changes: 1 addition & 1 deletion releases/release-6.5.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ TiDB 版本:6.5.4

### 行为变更

- 对于包含多条变更的事务,如果 Update 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 Delete 和 Insert 两条事件,并确保将所有事件有序,以保证 Delete 事件在 Insert 事件之前。更多信息,请参考[用户文档](/ticdc/ticdc-behavior-change.md#含有多条-update-变更的事务拆分)
- 对于包含多条变更的事务,如果 Update 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 Delete 和 Insert 两条事件,并确保将所有事件有序,以保证 Delete 事件在 Insert 事件之前。更多信息,请参考[用户文档](/ticdc/ticdc-split-update-behavior.md#含有多条-update-变更的事务拆分)

## 改进提升

Expand Down
5 changes: 5 additions & 0 deletions ticdc/ticdc-changefeed-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ sasl-oauth-grant-type = "client_credentials"
# Kafka SASL OAUTHBEARER 认证机制中的 audience。默认值为空。在使用该认证机制时,该参数可选填。
sasl-oauth-audience = "kafka"

# 控制是否输出原始的数据变更事件,默认值为 false,表示当使用非 MySQL Sink 且 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变时,TiCDC 会将该其拆分为 `DELETE` 和 `INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。设置为 true 时,表示不拆分事件,直接输出原始事件。
# output-raw-change-event = false

[sink.cloud-storage-config]
# 向下游存储服务保存数据变更记录的并发度,默认值为 16。
worker-count = 16
Expand All @@ -211,4 +214,6 @@ file-expiration-days = 0
file-cleanup-cron-spec = "0 0 2 * * *"
# 上传单个文件的并发数,默认值为 1,表示禁用并发。
flush-concurrency = 1
# 控制是否输出原始的数据变更事件,默认值为 false,表示当使用非 MySQL Sink 且 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变时,TiCDC 会将该其拆分为 `DELETE` 和 `INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。设置为 true 时,表示不拆分事件,直接输出原始事件。
output-raw-change-event = false
```
1 change: 1 addition & 0 deletions ticdc/ticdc-open-api-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ curl -X GET http://127.0.0.1:8300/api/v2/health
| `file_expiration_days` | `INT` 类型,文件保留的时长。|
| `file_cleanup_cron_spec` | `STRING` 类型,定时清理任务的运行周期,与 crontab 配置兼容,格式为 `<Second> <Minute> <Hour> <Day of the month> <Month> <Day of the week (Optional)>`|
| `flush_concurrency` | `INT` 类型,上传单个文件的并发数。|
| `output_raw_change_event` | `BOOLEAN` 类型,控制使用非 MySQL Sink 时是否输出原始的数据变更事件。|

### 使用样例

Expand Down
2 changes: 1 addition & 1 deletion ticdc/ticdc-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ MySQL binlog 直接记录了上游执行的所有 DML 操作的 SQL 语句。与

TiCDC 会根据收到的这些数据变更信息,适配各个类型的下游来生成合适格式的数据传输给下游。例如,生成 Canal-JSON、Avro 等格式的数据写入 Kafka 中,或者重新转换成 SQL 语句发送给下游的 MySQL 或者 TiDB。

目前 TiCDC 将数据变更信息适配对应的协议时,对于特定的 `UPDATE` 事件,可能会将其拆成一条 `DELETE` 事件和一条 `INSERT` 事件。详见[将 Update 事件拆分为 Delete 和 Insert 事件](/ticdc/ticdc-behavior-change.md#将-update-事件拆分为-delete-和-insert-事件)
目前 TiCDC 将数据变更信息适配对应的协议时,对于特定的 `UPDATE` 事件,可能会将其拆成一条 `DELETE` 事件和一条 `INSERT` 事件。详见 [MySQL Sink 拆分 `UPDATE` 事件行为说明](/ticdc/ticdc-split-update-behavior.md#mysql-sink-拆分-update-事件行为说明)[非 MySQL Sink 拆分主键或唯一键 `UPDATE` 事件](/ticdc/ticdc-split-update-behavior.md#非-mysql-sink-拆分主键或唯一键-update-事件)

当下游是 MySQL 或者 TiDB 时,因为 TiCDC 并非直接获取原生上游执行的 DML 语句,而是重新根据数据变更信息来生成 SQL 语句,因此不能保证写入下游的 SQL 语句和上游执行的 SQL 语句完全相同,但会保证最终结果的一致性。

Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,12 @@
---
title: TiCDC 行为变更说明
summary: 介绍 TiCDC changefeed 的行为变更,说明变更原因以及影响范围。
title: TiCDC 拆分 UPDATE 事件行为说明
summary: 介绍 TiCDC changefeed 拆分 UPDATE 事件的行为变更,说明变更原因以及影响范围。
aliases: ['/zh/tidb/dev/ticdc-behavior-change']
---

# TiCDC 行为变更说明
# TiCDC 拆分 UPDATE 事件行为说明

## `UPDATE` 事件拆分为 `DELETE``INSERT` 事件

### 含有单条 `UPDATE` 变更的事务拆分

从 v6.5.3 开始,使用非 MySQL Sink 时,对于仅包含一条 `UPDATE` 变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该条事件拆分为 `DELETE``INSERT` 两条事件。详情见 GitHub issue [#9086](https://github.com/pingcap/tiflow/issues/9086)

该变更主要为了解决如下问题:

* 在使用 CSV 和 AVRO 协议时,仅输出新值而不输出旧值。因此,当主键或者非空唯一索引的列值发生改变时,消费者只能接收到变化后的新值,无法得到旧值,导致无法处理变更前的值(例如删除旧值)。
* 在使用 Index value dispatcher 将数据按照 Key 分发到不同的 Kafka partition 时,下游的消费者组内多个消费者进程独立消费 Kafka Topic partition,由于消费进度不同,可能导致数据不一致的问题。

以如下 SQL 为例:

```sql
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
UPDATE t SET a = 2 WHERE a = 1;
```

在上述示例中,主键 `a` 的值从 `1` 修改为 `2`。如果不将该 `UPDATE` 事件进行拆分:

* 在使用 CSV 和 AVRO 协议时,消费者仅能看到新值 `a = 2`,而无法得到旧值 `a = 1`。这可能导致下游消费者只插入了新值 `2`,而没有删除旧值 `1`
* 在使用 Index value dispatcher 时,插入记录 `(1, 1)` 的事件可能被发送到 Partition 0,而变更事件 `(2, 1)` 可能被发送到 Partition 1。如果 Partition 1 的消费进度快于 Partition 0,则可能由于下游数据系统中找不到相应数据而导致出错。因此,TiCDC 会将该条 `UPDATE` 事件拆分为 `DELETE``INSERT` 两条事件,其中,删除记录 `(1, 1)` 被发送到 Partition 0,写入记录 `(2, 1)` 被发送到 Partition 1,以确保无论消费者的进度如何,事件都能被消费成功。

### 含有多条 `UPDATE` 变更的事务拆分

从 v6.5.4 开始,对于一个含有多条变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 `DELETE``INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。详情见 GitHub issue [#9430](https://github.com/pingcap/tiflow/issues/9430)

该变更主要为了解决当使用 MySQL Sink 直接将这两条事件写入下游时,可能会出现主键或唯一键冲突的问题,从而导致 changefeed 报错。当使用 Kafka Sink 或其他 Sink 时,如果消费者需要将数据变更写入关系型数据库或进行类似操作,也可能遇到相同问题。

以如下 SQL 为例:

```sql
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
INSERT INTO t VALUES (2, 2);

BEGIN;
UPDATE t SET a = 3 WHERE a = 1;
UPDATE t SET a = 1 WHERE a = 2;
UPDATE t SET a = 2 WHERE a = 3;
COMMIT;
```

在上述示例中,通过执行三条 SQL 语句对两行数据的主键进行交换,但 TiCDC 只会接收到两条 `UPDATE` 变更事件,即将主键 `a``1` 变更为 `2`,将主键 `a``2` 变更为 `1`,如果 MYSQL Sink 直接将这两条 `UPDATE` 事件写入下游,会出现主键冲突的问题,导致 changefeed 报错。

因此,TiCDC 会将这两条事件拆分为四条事件,即删除记录 `(1, 1)``(2, 2)` 以及写入记录 `(2, 1)``(1, 2)`

### MySQL Sink
## MySQL Sink 拆分 `UPDATE` 事件行为说明

从 v6.5.10 开始,当使用 MySQL Sink 时,TiCDC 的任意节点在收到某张表的同步任务请求并开始向下游同步数据之前,会从 PD 获取当前的时间戳 `thresholdTS`,并根据时间戳的值决定是否拆分 `UPDATE` 事件:

Expand Down Expand Up @@ -115,3 +68,61 @@ UPDATE t SET a = 3 WHERE a = 2;
> **注意:**
>
> 该行为变更后,在使用 MySQL Sink 时,TiCDC 在大部分情况下都不会拆分 `UPDATE` 事件,因此 changefeed 在运行时可能会出现主键或唯一键冲突的问题。该问题会导致 changefeed 自动重启,重启后发生冲突的 `UPDATE` 事件会被拆分为 `DELETE``INSERT` 事件并写入 Sorter 模块中,此时可以确保同一事务内所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序,从而正确完成数据同步。

## 非 MySQL Sink 拆分主键或唯一键 `UPDATE` 事件

### 含有单条 `UPDATE` 变更的事务拆分

v6.5.3 开始,使用非 MySQL Sink 时,对于仅包含一条 `UPDATE` 变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该条事件拆分为 `DELETE``INSERT` 两条事件。详情见 GitHub issue [#9086](https://github.com/pingcap/tiflow/issues/9086)。

该变更主要为了解决在使用 CSV 和 AVRO 协议时,TiCDC 在默认配置下仅输出新值而不输出旧值的问题。因此,当主键或者非空唯一索引的列值发生改变时,消费者只能接收到变化后的新值,无法得到旧值,导致无法处理变更前的值(例如删除旧值)。以如下 SQL 为例:

```sql
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
UPDATE t SET a = 2 WHERE a = 1;
```

在上述示例中,主键 `a` 的值从 `1` 修改为 `2`。如果不将该 `UPDATE` 事件进行拆分,在使用 CSV 和 AVRO 协议时,消费者仅能看到新值 `a = 2`,而无法得到旧值 `a = 1`。这可能导致下游消费者只插入了新值 `2`,而没有删除旧值 `1`

### 含有多条 `UPDATE` 变更的事务拆分

v6.5.4 开始,对于一个含有多条变更的事务,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 `DELETE``INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。详情见 GitHub issue [#9430](https://github.com/pingcap/tiflow/issues/9430)。

该变更主要为了解决当使用 Kafka Sink 或其他 Sink 时,由于 TiCDC 接收到的 `UPDATE` 事件顺序可能不正确,消费者将数据变更写入关系型数据库或进行类似操作,可能遇到主键或唯一键冲突的问题。

以如下 SQL 为例:

```sql
CREATE TABLE t (a INT PRIMARY KEY, b INT);
INSERT INTO t VALUES (1, 1);
INSERT INTO t VALUES (2, 2);
BEGIN;
UPDATE t SET a = 3 WHERE a = 1;
UPDATE t SET a = 1 WHERE a = 2;
UPDATE t SET a = 2 WHERE a = 3;
COMMIT;
```

在上述示例中,通过执行三条 SQL 语句对两行数据的主键进行交换,但 TiCDC 只会接收到两条 `UPDATE` 变更事件,即将主键 `a``1` 变更为 `2`,将主键 `a``2` 变更为 `1`,如果消费者直接将这两条 `UPDATE` 事件写入下游,会出现主键冲突的问题,导致 changefeed 报错。

因此,TiCDC 会将这两条事件拆分为四条事件,即删除记录 `(1, 1)``(2, 2)` 以及写入记录 `(2, 1)``(1, 2)`

### 控制是否拆分主键或唯一键 `UPDATE` 事件

v6.5.10 开始,使用非 MySQL Sink 时,TiCDC 支持通过 `output-raw-change-event` 参数控制是否拆分主键或唯一键 `UPDATE` 事件,详情见 GitHub issue [#11211](https://github.com/pingcap/tiflow/issues/11211)。这个参数的具体行为是:

-`output-raw-change-event = false` 时,如果 `UPDATE` 事件的主键或者非空唯一索引的列值发生改变,TiCDC 会将该其拆分为 `DELETE``INSERT` 两条事件,并确保所有事件按照 `DELETE` 事件在 `INSERT` 事件之前的顺序进行排序。
-`output-raw-change-event = true` 时,TiCDC 不拆分 `UPDATE` 事件。注意,当表的主键为聚簇索引时,对主键的更新会在 TiDB 中拆分为 `DELETE``INSERT` 两个事件,该行为不受 `output-raw-change-event` 参数的影响。

#### Release 6.5 的兼容性

| 版本 | 协议 | 拆分主键或唯一键 `UPDATE` 事件 | 不拆分主键或唯一键 `UPDATE` 事件 | 备注 |
| -- | -- | -- | -- | -- |
| <= v6.5.2 | 所有协议 | ✗ | ✓ |  |
| v6.5.3v6.5.4 | Canal/Open | ✗ | ✓ |  |
| v6.5.3 | CSV/Avro | ✗ | ✗ | 拆分但是不排序, 详见 [#9086](https://github.com/pingcap/tiflow/issues/9658) |
| v6.5.4 | Canal/Open | ✗ | ✗ | 只拆分并排序包含多条变更的事务 |
| v6.5.5v6.5.9 | 所有协议 | ✓ | ✗ | |
| \>= v6.5.10 | 所有协议 | ✓ (默认值:`output-raw-change-event = false`) | ✓ (可选配置项:`output-raw-change-event = true`) | |
12 changes: 10 additions & 2 deletions ticdc/troubleshoot-ticdc.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,21 @@ fetch.message.max.bytes=2147483648

## TiCDC 同步时,在下游执行 DDL 语句失败会有什么表现,如何恢复?

如果某条 DDL 语句执行失败,同步任务 (changefeed) 会自动停止,checkpoint-ts 断点时间戳为该条出错 DDL 语句的结束时间戳 (finish-ts) 减去一。如果希望让 TiCDC 在下游重试执行这条 DDL 语句,可以使用 `cdc cli changefeed resume` 恢复同步任务。例如:
如果某条 DDL 语句执行失败,同步任务 (changefeed) 会自动停止,checkpoint-ts 断点时间戳为该条出错 DDL 语句的结束时间戳 (finish-ts)。如果希望让 TiCDC 在下游重试执行这条 DDL 语句,可以使用 `cdc cli changefeed resume` 恢复同步任务。例如:

```shell
cdc cli changefeed resume -c test-cf --server=http://127.0.0.1:8300
```

如果希望跳过这条出错的 DDL 语句,可以将 changefeed 的 start-ts 设为报错时的 checkpoint-ts 加上一,然后通过 `cdc cli changefeed create` 新建同步任务。假设报错时的 checkpoint-ts 为 `415241823337054209`,可以进行如下操作来跳过该 DDL 语句:
如果希望跳过这条出错的 DDL 语句,可以通过配置 `ignore-txn-start-ts` 参数跳过指定的 `start-ts` 对应的事务。例如:

1. 首先在 TiCDC 日志中搜寻 `apply job` 字段,确认耗时较长的 DDL 操作的 `start-ts`
2. 修改 changefeed 配置,将上述 `start-ts` 添加到 `ignore-txn-start-ts` 配置项中。
3. 恢复被暂停的 changefeed。

> **注意:**
>
> 虽然将 changefeed 的 `start-ts` 设为报错时的 `checkpoint-ts` 值加上 1,然后重建任务也可以跳过该 DDL 语句,但同时会导致 TiCDC 丢失 `checkpointTs+1` 时刻对应的 DML 数据变更。严禁在生产环境执行这样的操作。

```shell
cdc cli changefeed remove --server=http://127.0.0.1:8300 --changefeed-id simple-replication-task
Expand Down

0 comments on commit e1a971e

Please sign in to comment.