作者介绍
廖坚钧,PingCAP Data Platform 研发工程师,专注于数据库架构和生态工具方向。张文珺,华中科技大学研究生,database enthusiast,rust learner。简介
在数据同步的场景下,上下游数据的一致性校验是非常重要的一个环节,缺少数据校验,可能会对商业决策产生非常负面的影响。Sync-diff-inspector 是 PingCAP Data Platform 团队开发的一款一致性校验工具,它能对多种数据同步场景的上下游数据进行一致性校验,如多数据源到单一目的(MySQL 中分库分表到 TiDB 中)、单一源到单一目的( TiDB 表到 TiDB 表)等,在数据校验过程中,其效率和正确性是至关重要的。首先我们看下 Sync-diff-inspector 的架构图,对 Sync-diff-inspector 的作用和实现原理有一个大致的认知。
Sync-diff-inspector 2.0 架构图
Why Sync-diff-inspector 2.0 ?
在 1.0 版本中,我们遇到客户反馈的一些问题,包括:
针对大表进行一致性校验时出现 TiDB 端发生内存溢出。
不支持 Float 类型数据校验的问题。
结果输出对用户不友好,需要对校验结果进行精简。
检验过程中发生 GC,导致校验失败。
采用单线程划分 Chunk,该表中所有已被划分的 Chunk 需要等待该表中所有 Chunk 全部被划分才会开始进行比对,这会导致这段时间内,TiKV 的使用率降低
Checkpoint 功能将校验过的每个 Chunk 的状态写入数据库,所以写入数据库的 IO 成为校验过程的瓶颈。
当 chunk 范围内的 checksum 不同时,直接进行按行比对,消耗大量 IO 资源。
缺少自适应 GC 的功能,导致正在校验的 Snapshot 被 GC,使得校验失败
Sync-diff-inspector 2.0 新特性
Chunk 划分
对于比较两个表数据是否相同,可以通过分别计算两个表的 checksum 来判断,但是确定哪一行出现了不同则需要逐行比对。为了缩小 checksum 不一致时需要进行逐行比对的行数, Sync-diff-inspector 采用了折衷的方案:将表按照索引的顺序划分成若干块(chunk),再对每个 chunk 进行上下游数据比对。chunk 的划分也会占用一定的资源,chunk 划分过快会一定程度减慢 chunk 比对的速度,因此这里在消费端通过 channel 来限制多表划分chunk的速度。
Checkpoint 和修复 SQL
Sync-diff-inspector 支持在断点处继续进行校验的功能。Diff 进程每十秒钟会记录一次断点信息,当校验程序在某个时刻发生异常退出的时候,再次运行 Sync-diff-inspector 会从最近保存的断点处继续进行校验。如果在下一次运行时,Sync-diff-inspector 的配置文件发生改变,那么 Sync-diff-inspector 会抛弃断点信息,重新进行校验。如果先写入 checkpoint,那么此时程序异常退出,下一次执行会从该 checkpoint 记录的 chunk 的后面范围开始检验,如果该 chunk 存在修复 SQL 但还没有被记录,那么这个修复 SQL 信息就丢失了。
二分校验和自适应 chunkSize
大表做 checksum 和切分成 chunks 做 checksum 的性能损耗在于每次做 checksum 都会有一些额外消耗(包括一次会话建立传输的时间),如果把 chunk 划分的很小,那么这些额外消耗在一次 checksum 花费的时间占比会变大。通常需要把 chunk 的预定大小 chunkSize 设置大一些,但是 chunkSize 设置的过大,当上下游数据库对 chunk 做 checksum 的结果不同时,如果对这个大 chunk 直接进行按行对比,那么开销也会变得很大。索引处理
上下游数据库的表可能会出现 schema 不同,例如下游表只拥有一部分上游的索引。不恰当的索引的选择会造成一方数据库耗时加大。在做表结构校验时,只保留上下游都有的索引(若不存在这种索引,则保留所有索引)。另一方面,某些索引包含的列并不是 unique 属性的,可能会有大量的行拥有相同的索引值,这样 chunk 会划分的不均匀。Sync-diff-inspector 在选择索引时,会优先选择 primary key 或者 unique 的索引,其次是选择重复率最低的索引。where 处理
假设存在一张表 create table t (a int, b int, c int, primary key (a, b, c));((a > 1) OR (a = 1 AND b > 2) OR (a = 1 AND b = 2 AND c > 3))
((a < 1) OR (a = 1 AND b < 2) OR (a = 1 AND b = 2 AND c <= 4))
自适应 GC
在原版 Sync-diff-inspector 中,校验过程中可能会出现大量表被 GC 导致校验失败。Sync-diff-inspector 工具支持自适应 GC 的功能,在 Diff 进程初始化阶段启动一个后台 goroutine,在检验过程中不断的更新 GC safepoint TTL 参数,使得对应的 snapshot 不会被 GC,保证校验过程的顺利进行。处理 Float 列
根据 float 类型的特性,有效精度只有 6 位,因此在 checksum SQL 中对 float 类型的列使用 round(%s, 5-floor(log10(abs(`column`)))) 取 6 位有效数字作为 checksum string 的一部分,当 column 取特殊值为 0 时,该结果为 NULL,但是 ISNULL(NULL) 也作为 checksum string 的一部分,此时不为 true,这样可以把 0 和 NULL 区分开来。用户交互优化
Sync-diff-inspector 显示如下信息:将日志写入到日志文件中。
在前台显示进度条,并提示正在比较的表。
记录每个表校验相关结果,包括整体对比时间、对比数据量、平均速度、每张表对比结果和每张表的配置信息。
生成的修复 SQL 信息。
一定时间间隔记录的 checkpoint 信息。
性能提升
基于以上的优化手段,我们进行了性能测试,在 Sysbench中, 构造 668.4GB 数据,共 190 张表,每张表一千万行数据,测试结果如下:从测试结果可以看出,Sync-diff-inspector 2.0 相比于原版, 校验速度有明显提升,同时在TiDB 端内存占用显著减少。
未来展望
开放性的架构
在 Sync-diff-inspector 中我们定义了 Source 抽象,目前只支持 TiDB 端到 TiDB 端,MySQL 端到 MySQL 端以及 MySQL 端到 TiDB 端的数据一致性校验,但是在未来,通过实现 Source 对应的方法,可以适配多种其他数据库进行数据一致性校验,例如 Oracle, Aurora 等。支持更多类型
由于部分列类型特殊,目前 sync-diff-inspector 暂不支持(例如 json,bit,binary,blob )。需要在 checksum SQL 语句中对它们特殊处理,例如对于 json 类型的列,需要通过 json_extract 提取出现在 json 中的每一个 key 的值。更激进的二分 checksum
新版 Sync-diff-inspector 采用二分 checksum 方法来减小逐行比对的数据量,但是在发现二分后的两个 chunk 都存在不一致数据时就停止继续二分,进行逐行比对。这种方法比较悲观,认为此刻 chunk 可能存在多个不一致的地方。但是根据实际情况,sync-diff-inspector 的应用场景一般是只存在少量不一致的情况,更加激进的做法是,继续二分,最后得到的是一组拥有最小行数(默认 3000 行)的且存在不一致数据的 chunk 数组,再对这些数组分别进行逐行比对。