简介

在数据同步的场景下,上下流数据的一致性校验是非常重要的一个环节,短少数据校验,或许会对商业决策产生非常负面的影响。。Sync-diff-inspector 是 Data Platform 团队开发的一款一致性校验东西,它能对多种数据同步场景的上下流数据进行一致性校验,如多数据源到单一意图(mysql 中分库分表到 TiDB 中)、单一源到单一意图( TiDB 表 到 TiDB 表)等,在数据校验进程中,其功率和正确性是至关重要的。首先咱们看下 Sync-diff-inspector 的架构图,对 Sync-diff-inspector 的作用和完结原理有一个大致的认知。

TiDB 数据一致性校验实现: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 的办法来进步这段时刻的资源利用率。这儿有两种下降资源利用率的状况:

  1. chunk 区分进程中或许因为 chunk 的预定巨细小于一个桶的巨细,需求切分这个桶为若干个 chunk,这是个相比照较慢的进程,因而消费端也就是 chunk 的比对线程会呈现等候的状况,资源利用率会下降。这儿选用两种处理办法:选用多个桶异步区分来进步资源利用率;有些表没有桶的信息,因而只能把整个表当作一个桶来切分,选用多表区分来进步总体的异步区分桶数。
  2. 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 会开端区分下一个表。

TiDB 数据一致性校验实现:Sync-diff-inspector 优化方案

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 的记载必定存在先后顺序:

  1. 假如先写入修正 SQL 的记载,那么此刻程序异常退出,这个被写入修正 SQL 但没被 checkpoint 记载的 chunk 会在下一次生成,一般状况下,这个修正 SQL 文件会被从头覆盖。可是因为桶的切分是随机分的,因而虽然切分后的 chunk 个数固定,上一次检查出的不同行在切分后 chunks 的第三个,这次或许跑到了第四个chunk 的规模内。这样就会存在重复的修正 SQL。
  2. 假如先写入 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 信息。

其作用如下图:

TiDB 数据一致性校验实现:Sync-diff-inspector 优化方案

详细细节可参阅 overview

功用提高

依据以上的优化手法,咱们进行了功用测验,在 Sysbench中, 结构 668.4GB 数据,共 190 张表,每张表一千万行数据,测验成果如下:

TiDB 数据一致性校验实现:Sync-diff-inspector 优化方案

从测验成果能够看出,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 数组,再对这些数组别离进行逐行比对。