简介
在数据同步的场景下,上下游数据的一致性校验 是十分重要的一个环节,短少数据校验,可能会对商业决策产生十分负面的影响。。Sync-diff-inspector 是 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 的划分沿用了之前的办法。TiDB 统计信息会以索引作为范畴将表划分为若干个桶,再对这些桶依据 chunk 的大小进行合并或切分。切分过程则抉择随机行作为范畴。
原版 Sync-diff-inspector 采纳单线程划分 chunk,已被划分的 chunk 须要期待该表划分完所有 chunk 才会开始比对,这里采纳异步划分 chunk 的办法来进步这段时间的资源利用率。这里有 两种升高资源利用率 的状况:
- chunk 划分过程中可能因为 chunk 的预约大小小于一个桶的大小,须要切分这个桶为若干个 chunk,这是个绝对比较慢的过程,因而生产端也就是 chunk 的比对线程会呈现期待的状况,资源利用率会升高。这里采纳两种解决办法:采纳多个桶异步划分来进步资源利用率;有些表没有桶的信息,因而只能把整个表当作一个桶来切分,采纳多表划分来进步总体的异步划分桶数。
- chunk 的划分也会占用肯定的资源,chunk 划分过快会肯定水平减慢 chunk 比对的速度,因而这里在生产端通过 channel 来限度多表划分 chunk 的速度。
总结来说,优化后的 Sync-diff-inspector 对 chunk 的划分由 三局部 组成。如下图所示,这里指定存在 3 个 chunk_iter,每个 chunk_iter 划分一个表,这里通过全局的 channel 调整 chunks_iter 划分的进度。留神这里只按表限流,每个 chunk_iter 开始划分时,会异步划分所有 chunk,当全局的 channel 的 buffer 满了,chunk_iter 会阻塞。当 chunk_iter 的所有 chunk 都进入全局 channel 的 buffer 后,该 chunk_iter 会开始划分下一个表。
Checkpoint 和修复 SQL
Sync-diff-inspector 反对在断点处持续进行校验的性能。Diff 过程每十秒钟会记录一次断点信息,当校验程序在某个时刻产生异样退出的时候,再次运行 Sync-diff-inspector 会从最近保留的断点处持续进行校验。如果在下一次运行时,Sync-diff-inspector 的配置文件产生扭转,那么 Sync-diff-inspector 会摈弃断点信息,从新进行校验。
该性能的完整性和正确性依赖于在 Chunk 划分过程中定义的全局有序性和连续性。相比于原版,Sync-diff-inspector 2.0 实现的 checkpoint 不须要记录每个 chunk 的状态,只须要记录间断的、最近校验实现的 chunk 的状态,大大减少了须要记录的数据量。chunk 的全局有序个性由一个构造体组成,构造体蕴含了该 chunk 属于第几个表,属于该表的第几个桶到第几个桶(如果该 chunk 由两个或多个桶合并而成,则记录桶的首末),这个桶被切分成多少个 chunk,这个 chunk 是切分后的 chunk 的第几个。同时这种个性也能够判断两个 chunk 是不是间断的。每次断点时钟触发时,会抉择已实现比对的间断的 chunk 的最初一个 chunk 作为检查点,写入该 chunk 的信息到本地文件。
当校验出不同行时,Sync-diff-inspector 会生成修复 SQL 并保留在本地文件中。因为测验的 chunk 是乱序且并行的,所以这里为每个 chunk 创立(若该 chunk 存在不同行)一个文件来保留修复 SQL,文件名是该 chunk 的全局有序的构造体。修复 SQL 和 checkpoint 的记录必定存在先后顺序:
- 如果先写入修复 SQL 的记录,那么此时程序异样退出,这个被写入修复 SQL 但没被 checkpoint 记录的 chunk 会在下一次生成,个别状况下,这个修复 SQL 文件会被从新笼罩。然而因为桶的切分是随机分的,因而只管切分后的 chunk 个数固定,上一次查看出的不同行在切分后 chunks 的第三个,这次可能跑到了第四个 chunk 的范畴内。这样就会存在反复的修复 SQL。
- 如果先写入 checkpoint,那么此时程序异样退出,下一次执行会从该 checkpoint 记录的 chunk 的前面范畴开始测验,如果该 chunk 存在修复 SQL 但还没有被记录,那么这个修复 SQL 信息就失落了。
这里采纳了先写入修复 SQL 记录,下一次执行时会将排在 checkpoint 记录的 chunk 后的所有修复 SQL 文件(文件是以该 chunk 的全局有序构造体命名,因而能够很容易判断两个 chunk 的先后顺序)都移到 trash 文件夹中,以此 避免出现反复的修复 SQL。
二分校验和自适应 chunkSize
大表做 checksum 和切分成 chunks 做 checksum 的性能损耗在于每次做 checksum 都会有一些额定耗费(包含一次会话建设传输的工夫),如果把 chunk 划分的很小,那么这些额定耗费在一次 checksum 破费的工夫占比会变大。通常须要把 chunk 的预约大小 chunkSize 设置大一些,然而 chunkSize 设置的过大,当上下游数据库对 chunk 做 checksum 的后果不同时,如果对这个大 chunk 间接进行按行比照,那么开销也会变得很大。
在数据同步过程中,个别只会呈现 大量的数据不统一 ,基于这个假设,当校验过程中,发现某个 chunk 的上下游的 checksum 不统一,能够通过二分法将原来的 chunk 划分成大小靠近的两个子 chunk,对子 chunk 进行 checksum 比照,进一步放大不统一行的可能范畴。这个优化的益处在于,checksum 比照所耗费的工夫和内存资源远小于逐行进行数据比对的耗费,通过 checksum 比照一直的放大不统一行的可能范畴,能够缩小须要进行逐行比照的数据行, 放慢比照速度,缩小内存损耗。并且因为每次计算 checksum 都相当于遍历一次二分后的子 chunk,实践上不思考屡次额定耗费,二分测验的开销相当于只对原 chunk 多做两次 checksum。
因为做一次 checksum 相当于遍历范畴内的所有行,能够在这个过程中顺便计算这段范畴的行数。这样做是因为 checksum 的原理是对一行的数据进行 crc32 运算,再对每一行的后果计算异或和,这种 checksum 的无奈校验出三行反复的谬误,在索引列不是 unique 属性的状况下是存在这种谬误的。同时计算出每个 chunk 的行数,能够应用 limit 语法定位到该 chunk 的两头一行数据的索引,这是二分办法应用的前提。
然而 chunkSize 也不能设定的过大,当一次二分后两边的子 chunk 都存在不同行,那么会进行二分,进行行比对。过大的 chunk 就更有可能同时蕴含多个不同行,二分校验的作用也会减小。这里设置每张表默认的 chunkSize 为 50000 行,每张表最多划分出 10000 个 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));
并且一个划分后的 chunk 范畴是 ((1, 2, 3), (1, 2, 4)]
原版 Sync-diff-inspector 会生成 where 语句:
- ((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))
能够优化为 (a = 1) AND (b = 2) AND ((c > 3) 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 信息。
其成果如下图:
具体细节可参考 overview
性能晋升
基于以上的优化伎俩,咱们进行了性能测试,在 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 数组,再对这些数组别离进行逐行比对。