TiCDC 常见问题解答
本文档总结了使用 TiCDC 时经常遇到的问题。
TiCDC 创建任务时如何选择 start-ts?
首先需要理解同步任务的 start-ts
对应于上游 TiDB 集群的一个 TSO,同步任务会从这个 TSO 开始请求数据。所以同步任务的 start-ts
需要满足以下两个条件:
start-ts
的值需要大于 TiDB 集群当前的tikv_gc_safe_point
,否则创建任务时会报错。- 启动任务时,需要保证下游已经具有
start-ts
之前的所有数据。对于同步到消息队列等场景,如果不需要保证上下游数据的一致,可根据业务场景放宽此要求。
如果不指定 start-ts
或者指定 start-ts=0
,在启动任务的时候会去 PD 获取一个当前 TSO,并从该 TSO 开始同步。
为什么 TiCDC 创建任务时提示部分表不能同步?
在使用 cdc cli changefeed create
创建同步任务时会检查上游表是否符合同步要求。如果存在表不满足同步限制,会提示 some tables are not eligible to replicate
并列出这些不满足的表。如果选择 Y
或 y
则会继续创建同步任务,并且同步过程中自动忽略这些表的所有更新。如果选择其他输入,则不会创建同步任务。
如何查看 TiCDC 同步任务的状态?
可以使用 cdc cli
查询同步任务的状态。例如:
cdc cli changefeed list --server=http://127.0.0.1:8300
上述命令输出如下:
[{
"id": "4e24dde6-53c1-40b6-badf-63620e4940dc",
"summary": {
"state": "normal",
"tso": 417886179132964865,
"checkpoint": "2020-07-07 16:07:44.881",
"error": null
}
}]
checkpoint
:即为 TiCDC 已经将该时间点前的数据同步到了下游。state
为该同步任务的状态,状态的值和含义参考 TiCDC 同步任务状态。
上游停止更新后,如何判断 TiCDC 是否已将所有更新同步到下游?
在上游 TiDB 集群停止更新后,可以通过比较上游 TiDB 集群的最新 TSO 时间戳与 TiCDC 当前的同步进度判断同步是否完成。如果 TiCDC 的同步进度时间大于或等于上游 TiDB 集群的 TSO,则说明 TiCDC 已同步所有更新。具体操作步骤如下:
获取上游 TiDB 集群的最新 TSO 时间戳。
以下示例使用
TIDB_PARSE_TSO()
将 TSO 转换为可读的时间格式,便于后续比较:BEGIN; SELECT TIDB_PARSE_TSO(TIDB_CURRENT_TSO()); ROLLBACK;输出结果示例如下:
+------------------------------------+ | TIDB_PARSE_TSO(TIDB_CURRENT_TSO()) | +------------------------------------+ | 2024-11-12 20:35:34.848000 | +------------------------------------+获取 TiCDC 当前的同步进度。
你可以通过以下两种方法之一检查当前的同步进度:
方法一:查询同步任务的 Checkpoint(推荐)
使用 TiCDC 命令行工具
cdc cli
查看所有同步任务的 Checkpoint:cdc cli changefeed list --server=http://127.0.0.1:8300输出结果示例如下:
[ { "id": "syncpoint", "namespace": "default", "summary": { "state": "normal", "tso": 453880043653562372, "checkpoint": "2024-11-12 20:36:01.447", "error": null } } ]在输出结果中,
"checkpoint": "2024-11-12 20:36:01.447"
表示 TiCDC 当前的同步进度为2024-11-12 20:36:01.447
,即所有在该时间之前的上游 TiDB 变更已同步完成。如果该时间戳大于或等于步骤 1 中获取的上游 TiDB TSO 时间,则表示所有更新已经同步到下游。方法二:查询下游 TiDB 的 Syncpoint 信息
如果下游是 TiDB,并且已启用 TiCDC Syncpoint 功能,可以通过查询下游 TiDB 的 Syncpoint 信息获取同步进度。
在下游 TiDB 中执行以下 SQL 语句,获取上游 TSO (
primary_ts
) 和下游 TSO (secondary_ts
) 信息。SELECT * FROM tidb_cdc.syncpoint_v1;输出结果示例如下:
+------------------+------------+--------------------+--------------------+---------------------+ | ticdc_cluster_id | changefeed | primary_ts | secondary_ts | created_at | +------------------+------------+--------------------+--------------------+---------------------+ | default | syncpoint | 453879870259200000 | 453879870545461257 | 2024-11-12 20:25:01 | | default | syncpoint | 453879948902400000 | 453879949214351361 | 2024-11-12 20:30:01 | | default | syncpoint | 453880027545600000 | 453880027751907329 | 2024-11-12 20:35:00 | +------------------+------------+--------------------+--------------------+---------------------+在输出结果中,每一行表示上游 TiDB 在
primary_ts
时刻的 snapshot 与下游 TiDB 在secondary_ts
时刻的 snapshot 一致。要查看同步进度,可以将最新的
primary_ts
转换为可读的时间格式:SELECT TIDB_PARSE_TSO(453880027545600000);转换结果示例如下:
+------------------------------------+ | TIDB_PARSE_TSO(453880027545600000) | +------------------------------------+ | 2024-11-12 20:35:00 | +------------------------------------+如果
primary_ts
对应的时间大于或等于步骤 1 中获取的上游 TiDB 集群的 TSO 时间戳,说明 TiCDC 已经将所有更新同步到下游。
TiCDC 的 gc-ttl
是什么?
从 TiDB v4.0.0-rc.1 版本起,PD 支持外部服务设置服务级别 GC safepoint。任何一个服务可以注册更新自己服务的 GC safepoint。PD 会保证任何晚于该 GC safepoint 的 KV 数据不会在 TiKV 中被 GC 清理掉。
在 TiCDC 中启用了这一功能,用来保证 TiCDC 在不可用、或同步任务中断情况下,可以在 TiKV 内保留 TiCDC 需要消费的数据不被 GC 清理掉。
启动 TiCDC server 时可以通过 gc-ttl
指定 GC safepoint 的 TTL,也可以通过 TiUP 修改 TiCDC 的 gc-ttl
,默认值为 24 小时。在 TiCDC 中这个值有如下两重含义:
- 当 TiCDC 服务全部停止后,由 TiCDC 在 PD 所设置的 GC safepoint 保存的最长时间。
- 当 TiCDC 的 GC safepoint 阻塞 TiKV GC 数据时,
gc-ttl
表示 TiCDC 同步任务的最大同步延迟,若同步任务延迟超过gc-ttl
所设置的值,那么该同步任务就会进入failed
状态,并报ErrGCTTLExceeded
错误,无法被恢复,不再阻塞 GC safepoint 推进。
以上第二种行为是在 TiCDC v4.0.13 版本及之后版本中新增的。目的是为了防止 TiCDC 中某个同步任务停滞时间过长,导致上游 TiKV 集群的 GC safepoint 长时间不推进,保留的旧数据版本过多,进而影响上游集群性能。
TiCDC GC safepoint 的完整行为是什么?
TiCDC 服务启动后,如果有任务开始同步,TiCDC owner 会根据所有同步任务最小的 checkpoint-ts 更新到 PD service GC safepoint,service GC safepoint 可以保证该时间点及之后的数据不被 GC 清理掉。如果 TiCDC 中某个同步任务中断、或者被用户主动停止,则该任务的 checkpoint-ts 不会再改变,PD 对应的 service GC safepoint 最终会停滞在该任务的 checkpoint-ts 处不再更新。
如果该同步任务停滞的时间超过了 gc-ttl
指定的时长,那么该同步任务就会进入 failed
状态,并且无法被恢复,PD 对应的 service GC safepoint 就会继续推进。
TiCDC 为 service GC safepoint 设置的默认存活有效期为 24 小时,即 TiCDC 服务中断 24 小时内恢复能保证 TiCDC 继续同步所需的数据不因 GC 而丢失。
Failed 同步任务失败后如何恢复?
- 通过
cdc cli changefeed query
查询同步任务的错误信息,尽快修复错误。 - 调大
gc-ttl
的值,给修复错误留出时间,确保错误修复后不会因为同步延迟超过gc-ttl
而导致同步任务进入failed
状态。 - 在评估系统影响后,调大 TiDB 的 tidb_gc_life_time 的值以阻止 GC、保留数据,确保错误修复后不会因为 GC 清理数据而导致同步任务进入
failed
状态。
如何理解 TiCDC 时区和上下游数据库系统时区之间的关系?
上游时区 | TiCDC 时区 | 下游时区 | |
---|---|---|---|
配置方式 | 见时区支持 | 启动 ticdc server 时的 --tz 参数 | sink-uri 中的 time-zone 参数 |
说明 | 上游 TiDB 的时区,影响 timestamp 类型的 DML 操作和与 timestamp 类型列相关的 DDL 操作。 | TiCDC 会将假设上游 TiDB 的时区和 TiCDC 时区配置相同,对 timestamp 类型的列进行相关处理。 | 下游 MySQL 将按照下游的时区设置对 DML 和 DDL 操作中包含的 timestamp 进行处理。 |
创建同步任务时,如果不指定 --config
配置文件,TiCDC 的默认的行为是什么?
在使用 cdc cli changefeed create
命令时如果不指定 --config
参数,TiCDC 会按照以下默认行为创建同步任务:
- 同步所有的非系统表
- 只同步包含有效索引的表
TiCDC 是否支持输出 Canal 协议的变更数据?
支持。注意:对于 Canal 协议,TiCDC 只支持 JSON 输出格式,对 protobuf 格式尚未提供官方支持。要开启 Canal 协议的输出,只需在 --sink-uri
配置中指定 protocol
为 canal-json
即可。例如:
cdc cli changefeed create --server=http://127.0.0.1:8300 --sink-uri="kafka://127.0.0.1:9092/cdc-test?kafka-version=2.4.0&protocol=canal-json" --config changefeed.toml
更多信息请参考 TiCDC Changefeed 配置参数。
为什么 TiCDC 到 Kafka 的同步任务延时越来越大?
- 请参考如何查看 TiCDC 同步任务的状态?检查下同步任务的状态是否正常。
- 请适当调整 Kafka 的以下参数:
message.max.bytes
,将 Kafka 的server.properties
中该参数调大到1073741824
(1 GB)。replica.fetch.max.bytes
,将 Kafka 的server.properties
中该参数调大到1073741824
(1 GB)。fetch.message.max.bytes
,适当调大consumer.properties
中该参数,确保大于message.max.bytes
。
TiCDC 把数据同步到 Kafka 时,能在 TiDB 中控制单条消息大小的上限吗?
对于 Avro 和 Canal-JSON 格式,消息是以行变更为单位发送的,一条 Kafka Message 仅包含一条行变更。一般情况下,消息的大小不会超过 Kafka 单条消息上限,因此,一般不需要限制单条消息大小。如果单条 Kafka 消息大小确实超过 Kafka 上限,请参考为什么 TiCDC 到 Kafka 的同步任务延时越来越大。
对于 Open Protocol 格式,一条 Kafka Message 可能包含多条行变更。因此,有可能存在某条 Kafka Message 消息过大。可以通过 max-message-bytes
控制每次向 Kafka broker 发送消息的最大数据量(可选,默认值 10 MB),通过 max-batch-size
参数指定每条 kafka 消息中变更记录的最大数量(可选,默认值 16
)。
在一个事务中对一行进行多次修改,TiCDC 会输出多条行变更事件吗?
不会,在进行事务操作时,对于在一个事务内多次修改同一行的情况,TiDB 仅会将最新一次的修改结果发送给 TiKV。因此 TiCDC 仅能获取到最新一次修改的结果。
TiCDC 把数据同步到 Kafka 时,一条消息中会不会包含多种数据变更?
会,一条消息中可能出现多个 update
或 delete
,update
和 delete
也有可能同时存在。
TiCDC 把数据同步到 Kafka 时,如何查看 TiCDC Open protocol 输出变更数据中的时间戳、表名和库名?
这些信息包含在 Kafka 消息的 Key 中,比如:
{
"ts":<TS>,
"scm":<Schema Name>,
"tbl":<Table Name>,
"t":1
}
更多信息请参考 Open protocol Event 格式定义
TiCDC 把数据同步到 Kafka 时,如何确定一条消息中包含的数据变更发生在哪个时间点?
把 Kafka 消息的 Key 中的 ts
右移 18 位即得 unix timestamp。
TiCDC Open protocol 如何标示 null 值?
Open protocol 的输出中 type = 6 即为 null,比如:
类型 | Code | 输出示例 | 说明 |
---|---|---|---|
Null | 6 | {"t":6,"v":null} |
更多信息请参考 Open protocol Event 格式定义。
如何区分 TiCDC Open Protocol 中的 Row Changed Event 是 INSERT
事件还是 UPDATE
事件?
- 如果同时存在
"p"
和"u"
字段为UPDATE
事件 - 如果只存在
"u"
字段则为INSERT
事件 - 如果只存在
"d"
字段则为DELETE
事件
更多信息请参考 Open protocol Row Changed Event 格式定义。
TiCDC 占用多少 PD 的存储空间
TiCDC 使用 PD 内部的 etcd 来存储元数据并定期更新。因为 etcd 的多版本并发控制 (MVCC) 以及 PD 默认的 compaction 间隔是 1 小时,TiCDC 占用的 PD 存储空间与 1 小时内元数据的版本数量成正比。在 v4.0.5、v4.0.6、v4.0.7 三个版本中 TiCDC 存在元数据写入频繁的问题,如果 1 小时内有 1000 张表创建或调度,就会用尽 etcd 的存储空间,出现 etcdserver: mvcc: database space exceeded
错误。出现这种错误后需要清理 etcd 存储空间,参考 etcd maintenance space-quota。如果你的 TiCDC 版本为 v4.0.5、v4.0.6 或 v4.0.7,建议升级到 v4.0.9 及以后版本。
TiCDC 支持同步大事务吗?有什么风险吗?
TiCDC 对大事务(大小超过 5 GB)提供部分支持,根据场景不同可能存在以下风险:
- 可能导致主从同步延迟大幅增高。
- 当 TiCDC 内部处理能力不足时,可能出现同步任务报错
ErrBufferReachLimit
。 - 当 TiCDC 内部处理能力不足或 TiCDC 下游吞吐能力不足时,可能出现内存溢出 (OOM)。
从 v6.2 版本开始,TiCDC 支持拆分单表事务功能,可大幅降低同步大事务的延时和内存消耗。因此,在业务对事务原子性要求不高的场景下,建议通过设置 sink uri 参数 transaction-atomicity
打开拆分事务功能以解决可能出现的同步延迟和 OOM 问题。
如果实际同步过程中仍然遇到了上述错误,建议将包含大事务部分的增量数据通过 BR 进行增量恢复,具体操作如下:
- 记录因为大事务而终止的 changefeed 的
checkpoint-ts
,将这个 TSO 作为 BR 增量备份的--lastbackupts
,并执行增量备份。 - 增量备份结束后,可以在 BR 日志输出中找到类似
["Full backup Failed summary : total backup ranges: 0, total success: 0, total failed: 0"] [BackupTS=421758868510212097]
的日志,记录其中的BackupTS
。 - 执行增量恢复。
- 建立一个新的 changefeed,从
BackupTS
开始同步任务。 - 删除旧的 changefeed。
TiCDC 是否会将有损 DDL 产生的数据变更同步到下游?
有损 DDL 是指在 TiDB 中执行可能会导致数据改变的 DDL。一些常见的有损 DDL 操作包括:
- 修改列的类型,例如:INT -> VARCHAR
- 修改列的长度,例如:VARCHAR(20) -> VARCHAR(10)
- 修改列的精度,例如:DECIMAL(10, 3) -> DECIMAL(10, 2)
- 修改列的符号(有符号数/无符号数),例如:INT UNSIGNED -> INT SIGNED
在 TiDB v7.1.0 之前,TiCDC 会将一条新旧数据相同的 DML 事件同步到下游。当下游是 MySQL 时,这些 DML 事件不会产生任何数据变更,只有下游接收并执行该 DDL 语句后,数据才会发生变更。但是当下游是 Kafka 或者云存储时,TiCDC 会写入一条无用的数据到下游。
从 TiDB v7.1.0 开始,TiCDC 会过滤掉这些无用的 DML 事件,不再将它们同步到下游。
同步 DDL 到下游 MySQL 5.7 时为什么时间类型字段默认值不一致?
比如上游 TiDB 的建表语句为 create table test (id int primary key, ts timestamp)
,TiCDC 同步该语句到下游 MySQL 5.7,MySQL 使用默认配置,同步得到的表结构如下所示,timestamp 字段默认值会变成 CURRENT_TIMESTAMP
:
mysql root@127.0.0.1:test> show create table test;
+-------+----------------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------------+
| test | CREATE TABLE `test` ( |
| | `id` int(11) NOT NULL, |
| | `ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | PRIMARY KEY (`id`) |
| | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+-------+----------------------------------------------------------------------------------+
1 row in set
产生表结构不一致的原因是 explicit_defaults_for_timestamp
的默认值在 TiDB 和 MySQL 5.7 不同。从 TiCDC v5.0.1/v4.0.13 版本开始,同步到 MySQL 会自动设置 session 变量 explicit_defaults_for_timestamp = ON
,保证同步时间类型时上下游行为一致。对于 v5.0.1/v4.0.13 以前的版本,同步时间类型时需要注意 explicit_defaults_for_timestamp
默认值不同带来的兼容性问题。
使用 TiCDC 创建同步任务时将 safe-mode
设置为 true
后,为什么上游的 INSERT
/UPDATE
语句经 TiCDC 同步到下游后变为了 REPLACE INTO
?
TiCDC 提供至少一次的数据同步保证,当下游有重复数据时,会引起写冲突。为了避免该问题,TiCDC 会将 INSERT
和 UPDATE
语句转成 REPLACE INTO
语句。该行为由 safe-mode
参数来控制。
在 v6.1.3 版本之前,safe-mode
默认为 true
,即所有的 INSERT
和 UPDATE
语句都转成 REPLACE INTO
语句。在 v6.1.3 及之后版本,系统能自动判断下游是否存在重复数据,safe-mode
默认更改为 false
,当系统判断下游无重复数据时,会直接同步 INSERT
和 UPDATE
语句。
数据同步下游的 Sink 为 TiDB 或 MySQL 时,下游数据库的用户需要哪些权限?
Sink 为 TiDB 或 MySQL 时,下游数据库的用户需要以下权限:
Select
Index
Insert
Update
Delete
Create
Drop
Alter
Create View
如果要同步 recover table
到下游 TiDB,需要有 Super
权限。
为什么 TiCDC 需要使用磁盘,什么时候会写磁盘,TiCDC 能否利用内存缓存提升同步性能?
TiCDC 需要磁盘是为了缓冲上游写入高峰时下游消费不及时堆积的数据。TiCDC 正常运行期间都需要写入磁盘,但这通常不是同步吞吐和同步延时的瓶颈,写磁盘对延时影响在百毫秒内。TiCDC 也利用了内存来提升加速读取磁盘中的数据,以提升同步性能。
为什么在上游使用了 TiDB Lightning 物理导入模式和 BR 恢复了数据之后,TiCDC 同步会出现卡顿甚至卡住?
目前 TiCDC 尚未完全适配 TiDB Lightning 物理导入模式 (Physical Import Mode) 和 BR,请避免在使用 TiCDC 同步的表上使用 TiDB Lightning 物理导入模式和 BR。否则,可能会出现未知的错误,例如 TiCDC 同步卡住、同步延迟大幅增加、或者同步数据丢失。
如果有某些使用 TiCDC 同步的表需要使用 TiDB Lightning 物理导入模式或者 BR 恢复数据,可以这么做:
删除涉及这些表的 TiCDC 同步任务。
使用 TiDB Lightning 物理导入模式或 BR 在 TiCDC 的上游集群和下游集群分别恢复数据。
恢复完成并检查了上下游集群对应表的数据一致性之后,使用上游备份的时间点 (TSO) 作为 TiCDC 同步任务的
start-ts
,创建新的 TiCDC 同步任务,进行增量同步。例如,假设上游集群的 BR 备份的 snapshot 时间点为431434047157698561
,那么可以使用以下命令创建新的 TiCDC 同步任务:cdc cli changefeed create -c "upstream-to-downstream-some-tables" --start-ts=431434047157698561 --sink-uri="mysql://root@127.0.0.1:4000? time-zone="
为什么恢复暂停的 changefeed 后,changefeed 同步延迟越来越高,数分钟后才恢复正常?
当 changefeed 启动时,为了补齐 changefeed 暂停期间产生的增量数据日志,TiCDC 需要扫描 TiKV 中数据的历史版本,待扫描完毕后,才能够继续推进复制过程,扫描过程可能长达数分钟到数十分钟。
在两个异地 TiDB 集群之间同步数据,如何部署 TiCDC?
对于 v6.5.2 之前的版本,建议将 TiCDC 部署在下游 TiDB 集群。这是因为,如果上下游网络延迟较大,例如超过 100 ms 时,由于 MySQL 传输协议的原因,TiCDC 向下游执行 SQL 的延迟会急剧增加,导致系统的吞吐下降。部署在下游能够极大缓解该问题。经过优化后,v6.5.2 及之后的版本建议将 TiCDC 部署在上游集群。
如何理解 DML 和 DDL 语句之间的执行顺序?
目前,TiCDC 采用了以下执行顺序:
- TiCDC 阻塞受 DDL 影响的表的同步进度,直到 DDL
commitTS
的时间点,以确保在 DDLcommitTS
之前执行的 DML 先成功同步到下游。 - TiCDC 继续同步 DDL。当存在多个 DDL 时,TiCDC 是以串行的方式进行同步的。
- 当 DDL 在下游执行完成之后,TiCDC 继续同步 DDL
commitTS
之后执行的 DML。
如何对比上下游数据的一致性?
如果下游是 TiDB 集群或者 MySQL,我们推荐使用 sync diff inspector 工具进行数据对比。
单表数据同步只能在一个 TiCDC 节点上运行,TiCDC 是否考虑使用多个节点同步多表数据?
从 v7.1.0 起,TiCDC 支持 MQ sink 按照 TiKV Region 粒度来同步数据变更日志,实现处理能力上的可扩展性,使得 TiCDC 能够同步 Region 数量庞大的单表。如需开启,请在 TiCDC 配置文件中配置以下参数:
[scheduler]
enable-table-across-nodes = true
上游有运行时间比较长的未提交事务,TiCDC 同步是否会被卡住?
TiDB 有事务超时的机制,当事务运行超过 max-txn-ttl
后,会被 TiDB 强制回滚。TiCDC 遇到未提交的事务,会等待其提交后再继续同步其数据,因此会出现同步延迟。
为什么通过 TiDB Operator 部署的 TiCDC 集群无法使用 cdc cli 命令进行操作?
因为通过 TiDB Operator 部署的 TiCDC 集群的默认端口号为 8301, 而 cdc cli 命令默认连接的 cdc 服务器的端口号是 8300。在使用 cdc cli 操作 TiCDC 集群时,你需要显式地指定 --server
参数,如下:
./cdc cli changefeed list --server "127.0.0.1:8301"
[
{
"id": "4k-table",
"namespace": "default",
"summary": {
"state": "stopped",
"tso": 441832628003799353,
"checkpoint": "2023-05-30 22:41:57.910",
"error": null
}
},
{
"id": "big-table",
"namespace": "default",
"summary": {
"state": "normal",
"tso": 441872834546892882,
"checkpoint": "2023-06-01 17:18:13.700",
"error": null
}
}
]