sync_diff_inspector做上下游数据对比时可能会导致集群性能抖动

【需求涉及的问题场景】
通过cdc做集群之间高可用,在使用sync_diff_inspector做上下游集群数据全量对比时,如果大表不是聚簇索引表,那么可能会导致集群性能抖动,影响其它业务。
【期望的需求行为】
希望针对非聚簇索引表进行优化,例如使用_tidb_rowid伪列进行拆分(但chunk划分的依据较难判断),或者退而求其次,强制走索引且利用hint将聚合进行下推( USE INDEX(xxx),AGG_TO_COP() ),尽量将运算发生在tikv上。

【背景信息】
sync_diff_inspector 做表数据对比的原理是将所有数据切割成多个chunk,每次计算一个chunk的所有数据行的crc32校验值(即checksum),如果这个chunk的checksum一致,则这批记录一致,直到对比完整个表。这里有两个点:1、切割的依据是按照show stats_buckets的Bound来判断切割范围的。2、切割字段的选择是优先选择主键,然后唯一索引,然后按照值大小:COUNT(DISTINCT a)/COUNT(*)来判定用哪个索引来拆分表为多个chunk。
获取chunk的checksum值的语句为:
select count(*),bit_xor(crc32(...)) as checksum from t where col1 > 'a' and col1 <='b'
对如下几种情况进行分析:

  1. 当表没有主键时,优化器根据执行成本可能会选择:
    1)全表扫描,可能会导致tikv大量扫描,占用大量CPU;
    2)索引回表,原本bit_xor,crc32等函数都可以下推到tikv执行,因为索引回表且优化器评估聚合不下推可能会导致该chunk的记录都返回给tidb-server来计算bit_xor和crc32,主要是大量的数据搬运可能会导致tikv-tidb之间grpc拥堵,导致集群TP业务抖动。
    3)索引回表且在tikv层发生聚合,此种情况是相对最优情况,但是索引根据_tidb_rowid回表查询回发生大量的seek操作,如果索引顺序和_tidb_rowid顺序不一致,那么无法优化成next操作,导致效率较低。

  2. 当表有主键,但表是非聚簇索引表,那么该主键还是二级索引,还是会发生类似于第1中情况。

  3. 当表有主键,且表是聚簇索引表,那么sync_diff_inspector总是会优先选择主键做chunk的范围划分,因为主键是一级索引,因此对于范围扫描都会走table range scan,因为没有索引回表的问题且不会发生全表扫描,因此不但可以让bit_xor,crc32函数下推到tikv运算而且还不会发生全表扫描,因此效率最优。

综上,聚簇索引表在做全表数据对比时总是高效且对系统影响不大(平稳)。非聚簇索引表可能会导致集群性能抖动,因此对于非聚簇索引表建议采用_tidb_rowid伪列来进行切割,但是Bound的选择是另外一个挑战(1、对于自增或者默认情况根据最大最小值均匀划分;2、对于做了打散的能否根据表的region的start_key,stop_key转换成对应的_tidb_rowid来框定范围呢?)

下面分别对聚簇索引表和非聚簇索引表做相关测试:

--聚簇索引表
Create Table: CREATE TABLE `orders` (
  `O_ORDERKEY` bigint(20) NOT NULL,
  `O_CUSTKEY` bigint(20) NOT NULL,
  `O_ORDERSTATUS` char(1) NOT NULL,
  `O_TOTALPRICE` decimal(15,2) NOT NULL,
  `O_ORDERDATE` date NOT NULL,
  `O_ORDERPRIORITY` char(15) NOT NULL,
  `O_CLERK` char(15) NOT NULL,
  `O_SHIPPRIORITY` bigint(20) NOT NULL,
  `O_COMMENT` varchar(79) NOT NULL,
  PRIMARY KEY (`O_ORDERKEY`) /*T![clustered_index] CLUSTERED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

--非聚簇索引表
CREATE TABLE `orders_bak` (
  `O_ORDERKEY` bigint(20) NOT NULL,
  `O_CUSTKEY` bigint(20) NOT NULL,
  `O_ORDERSTATUS` char(1) NOT NULL,
  `O_TOTALPRICE` decimal(15,2) NOT NULL,
  `O_ORDERDATE` date NOT NULL,
  `O_ORDERPRIORITY` char(15) NOT NULL,
  `O_CLERK` char(15) NOT NULL,
  `O_SHIPPRIORITY` bigint(20) NOT NULL,
  `O_COMMENT` varchar(79) NOT NULL,
  PRIMARY KEY (`O_ORDERKEY`) /*T![clustered_index] NONCLUSTERED */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

针对聚簇索引表获取chunk的checksum校验值的语句和执行计划:

SELECT 
  COUNT(*) AS CNT,
  BIT_XOR(
    CAST(
      CRC32(
        CONCAT_WS(
          ',',
          `O_ORDERKEY`,
          `O_CUSTKEY`,
          `O_ORDERSTATUS`,
          `O_TOTALPRICE`,
          `O_ORDERDATE`,
          `O_ORDERPRIORITY`,
          `O_CLERK`,
          `O_SHIPPRIORITY`,
          `O_COMMENT`,
          CONCAT(
            ISNULL(`O_ORDERKEY`),
            ISNULL(`O_CUSTKEY`),
            ISNULL(`O_ORDERSTATUS`),
            ISNULL(`O_TOTALPRICE`),
            ISNULL(`O_ORDERDATE`),
            ISNULL(`O_ORDERPRIORITY`),
            ISNULL(`O_CLERK`),
            ISNULL(`O_SHIPPRIORITY`),
            ISNULL(`O_COMMENT`)
          )
        )
      ) AS UNSIGNED
    )
  ) AS CHECKSUM
FROM
  `tpch10`.`orders` 
WHERE
  (
    (
      ((`O_ORDERKEY` > '8833953'))
      AND ((`O_ORDERKEY` <= '9668836'))
    )
    AND (TRUE)
  );

--执行计划:
+-----------------------------+-----------+---------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
| id                          | estRows   | actRows | task      | access object | execution info                                                                                                                                                                                                                                                                                                                                          | operator info                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | memory    | disk |
+-----------------------------+-----------+---------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
| HashAgg_12                  | 1.00      | 1       | root      |               | time:516.3µs, loops:2, RU:0.475597, partial_worker:{wall_time:496.366µs, concurrency:5, task_num:1, tot_wait:1.966037ms, tot_exec:4.041µs, tot_time:1.973846ms, max:397.408µs, p95:397.408µs}, final_worker:{wall_time:0s, concurrency:5, task_num:1, tot_wait:2.083908ms, tot_exec:5.721µs, tot_time:2.091214ms, max:424.129µs, p95:424.129µs}         | funcs:count(Column#12)->Column#10, funcs:bit_xor(Column#13)->Column#11                                                                                                                                                                                                                                                                                                                                                                                                                      | 13.6 KB   | N/A  |
| └─TableReader_13            | 1.00      | 1       | root      |               | time:399.1µs, loops:2, cop_task: {num: 1, max: 398.2µs, proc_keys: 0, tot_proc: 1.79µs, tot_wait: 59.6µs, rpc_num: 1, rpc_time: 382.4µs, copr_cache_hit_ratio: 1.00, build_task_duration: 9.15µs, max_distsql_concurrency: 1}                                                                                                                           | data:HashAgg_6                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | 275 Bytes | N/A  |
|   └─HashAgg_6               | 1.00      | 1       | cop[tikv] |               | tikv_task:{time:209ms, loops:204}, scan_detail: {get_snapshot_time: 16.7µs, rocksdb: {block: {}}}                                                                                                                                                                                                                                                       | funcs:count(1)->Column#12, funcs:bit_xor(cast(crc32(concat_ws(",", cast(tpch10.orders.o_orderkey, var_string(20)), cast(tpch10.orders.o_custkey, var_string(20)), tpch10.orders.o_orderstatus, cast(tpch10.orders.o_totalprice, var_string(18)), cast(tpch10.orders.o_orderdate, var_string(10)), tpch10.orders.o_orderpriority, tpch10.orders.o_clerk, cast(tpch10.orders.o_shippriority, var_string(20)), tpch10.orders.o_comment, "000000000")), bigint(22) UNSIGNED BINARY))->Column#13 | N/A       | N/A  |
|     └─TableRangeScan_11     | 203152.59 | 208723  | cop[tikv] | table:orders  | tikv_task:{time:73ms, loops:204}                                                                                                                                                                                                                                                                                                                        | range:(8833953,9668836], keep order:false                                                                                                                                                                                                                                                                                                                                                                                                                                                   | N/A       | N/A  |
+-----------------------------+-----------+---------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
4 rows in set (0.00 sec)

可以看到其走的table range scan且函数均下推到tikv中执行,返回给tidb-server的只有聚合后的几行记录(这里只有一个region是一行记录)。

针对非聚簇索引表获取chunk的checksum校验值语句和执行计划:

SELECT 
  COUNT(*) AS CNT,
  BIT_XOR(
    CAST(
      CRC32(
        CONCAT_WS(
          ',',
          `O_ORDERKEY`,
          `O_CUSTKEY`,
          `O_ORDERSTATUS`,
          `O_TOTALPRICE`,
          `O_ORDERDATE`,
          `O_ORDERPRIORITY`,
          `O_CLERK`,
          `O_SHIPPRIORITY`,
          `O_COMMENT`,
          CONCAT(
            ISNULL(`O_ORDERKEY`),
            ISNULL(`O_CUSTKEY`),
            ISNULL(`O_ORDERSTATUS`),
            ISNULL(`O_TOTALPRICE`),
            ISNULL(`O_ORDERDATE`),
            ISNULL(`O_ORDERPRIORITY`),
            ISNULL(`O_CLERK`),
            ISNULL(`O_SHIPPRIORITY`),
            ISNULL(`O_COMMENT`)
          )
        )
      ) AS UNSIGNED
    )
  ) AS CHECKSUM
FROM
  `tpch10`.`orders_bak` 
WHERE
  (
    (
      ((`O_ORDERKEY` > '8833953'))
      AND ((`O_ORDERKEY` <= '9668836'))
    )
    AND (TRUE)
  );

--其执行计划:
+------------------------------+-------------+----------+-----------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
| id                           | estRows     | actRows  | task      | access object    | execution info                                                                                                                                                                                                                                                                                                                                                              | operator info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | memory    | disk |
+------------------------------+-------------+----------+-----------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
| HashAgg_13                   | 1.00        | 1        | root      |                  | time:3.54s, loops:2, RU:35679.746372, partial_worker:{wall_time:3.537738662s, concurrency:5, task_num:1, tot_wait:17.68843555s, tot_exec:3.767µs, tot_time:17.688443423s, max:3.537694388s, p95:3.537694388s}, final_worker:{wall_time:0s, concurrency:5, task_num:1, tot_wait:17.688526743s, tot_exec:8.012µs, tot_time:17.688535975s, max:3.53771454s, p95:3.53771454s}   | funcs:count(Column#13)->Column#11, funcs:bit_xor(Column#14)->Column#12                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 13.6 KB   | N/A  |
| └─TableReader_14             | 1.00        | 1        | root      |                  | time:3.54s, loops:2, cop_task: {num: 2, max: 3.54s, min: 2.55s, avg: 3.04s, p95: 3.54s, max_proc_keys: 7939404, p95_proc_keys: 7939404, tot_proc: 6.07s, tot_wait: 129.2µs, rpc_num: 2, rpc_time: 6.09s, copr_cache_hit_ratio: 0.00, build_task_duration: 12.3µs, max_distsql_concurrency: 2}                                                                               | data:HashAgg_6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | 436 Bytes | N/A  |
|   └─HashAgg_6                | 1.00        | 1        | cop[tikv] |                  | tikv_task:{proc max:3.53s, min:2.55s, avg: 3.04s, p80:3.53s, p95:3.53s, iters:13561, tasks:2}, scan_detail: {total_process_keys: 13884864, total_process_keys_size: 2205608093, total_keys: 13884866, get_snapshot_time: 48.3µs, rocksdb: {key_skipped_count: 13884864, block: {cache_hit_count: 46859, read_count: 26392, read_byte: 507.9 MB, read_time: 183.2ms}}}       | funcs:count(1)->Column#13, funcs:bit_xor(cast(crc32(concat_ws(",", cast(tpch10.orders_bak.o_orderkey, var_string(20)), cast(tpch10.orders_bak.o_custkey, var_string(20)), tpch10.orders_bak.o_orderstatus, cast(tpch10.orders_bak.o_totalprice, var_string(18)), cast(tpch10.orders_bak.o_orderdate, var_string(10)), tpch10.orders_bak.o_orderpriority, tpch10.orders_bak.o_clerk, cast(tpch10.orders_bak.o_shippriority, var_string(20)), tpch10.orders_bak.o_comment, "000000000")), bigint(22) UNSIGNED BINARY))->Column#14 | N/A       | N/A  |
|     └─Selection_12           | 258213.41   | 208723   | cop[tikv] |                  | tikv_task:{proc max:3.53s, min:2.41s, avg: 2.97s, p80:3.53s, p95:3.53s, iters:13561, tasks:2}                                                                                                                                                                                                                                                                               | gt(tpch10.orders_bak.o_orderkey, 8833953), le(tpch10.orders_bak.o_orderkey, 9668836)                                                                                                                                                                                                                                                                                                                                                                                                                                            | N/A       | N/A  |
|       └─TableFullScan_11     | 17139728.00 | 13884864 | cop[tikv] | table:orders_bak | tikv_task:{proc max:3.42s, min:2.33s, avg: 2.87s, p80:3.42s, p95:3.42s, iters:13561, tasks:2}                                                                                                                                                                                                                                                                               | keep order:false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | N/A       | N/A  |
+------------------------------+-------------+----------+-----------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+------+
5 rows in set (3.54 sec)
--让其强制走主键索引

+----------------------------------+-----------+---------+-----------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+------+
| id                               | estRows   | actRows | task      | access object                               | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | operator info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | memory  | disk |
+----------------------------------+-----------+---------+-----------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+------+
| HashAgg_13                       | 1.00      | 1       | root      |                                             | time:83.9ms, loops:2, RU:753.270397, partial_worker:{wall_time:83.911166ms, concurrency:5, task_num:1, tot_wait:419.244414ms, tot_exec:14.561µs, tot_time:419.272395ms, max:83.867643ms, p95:83.867643ms}, final_worker:{wall_time:83.924262ms, concurrency:5, task_num:1, tot_wait:419.433702ms, tot_exec:26.633µs, tot_time:419.463128ms, max:83.90034ms, p95:83.90034ms}                                                                                                                                                                                                                                                                                   | funcs:count(Column#14)->Column#11, funcs:bit_xor(Column#15)->Column#12                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 13.6 KB | N/A  |
| └─IndexLookUp_14                 | 1.00      | 14      | root      |                                             | time:83.8ms, loops:2, index_task: {total_time: 61.1ms, fetch_handle: 59.6ms, build: 17.2µs, wait: 1.41ms}, table_task: {total_time: 245.9ms, num: 14, concurrency: 5}, next: {wait_index: 1.99ms, wait_table_lookup_build: 88.2µs, wait_table_lookup_resp: 81.7ms}                                                                                                                                                                                                                                                                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 2.46 MB | N/A  |
|   ├─IndexRangeScan_11(Build)     | 258213.41 | 208723  | cop[tikv] | table:orders_bak, index:PRIMARY(O_ORDERKEY) | time:54.5ms, loops:207, cop_task: {num: 12, max: 13.9ms, min: 491.5µs, avg: 4.86ms, p95: 13.9ms, max_proc_keys: 50144, p95_proc_keys: 50144, tot_proc: 51.6ms, tot_wait: 707.1µs, rpc_num: 12, rpc_time: 58.2ms, copr_cache_hit_ratio: 0.00, build_task_duration: 18.3µs, max_distsql_concurrency: 1}, tikv_task:{proc max:13ms, min:0s, avg: 4.08ms, p80:9ms, p95:13ms, iters:251, tasks:12}, scan_detail: {total_process_keys: 208723, total_process_keys_size: 9183812, total_keys: 208735, get_snapshot_time: 166.3µs, rocksdb: {key_skipped_count: 208723, block: {cache_hit_count: 23, read_count: 303, read_byte: 526.5 KB, read_time: 487.9µs}}}      | range:(8833953,9668836], keep order:false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | N/A     | N/A  |
|   └─HashAgg_7(Probe)             | 1.00      | 14      | cop[tikv] |                                             | time:240.9ms, loops:28, cop_task: {num: 14, max: 25.2ms, min: 2.24ms, avg: 17.1ms, p95: 25.2ms, max_proc_keys: 20480, p95_proc_keys: 20480, tot_proc: 233.3ms, tot_wait: 761.5µs, rpc_num: 14, rpc_time: 239.8ms, copr_cache_hit_ratio: 0.00, build_task_duration: 234µs, max_distsql_concurrency: 1}, tikv_task:{proc max:25ms, min:1ms, avg: 16.6ms, p80:23ms, p95:25ms, iters:210, tasks:14}, scan_detail: {total_process_keys: 208723, total_process_keys_size: 33148172, total_keys: 208737, get_snapshot_time: 204µs, rocksdb: {key_skipped_count: 208723, block: {cache_hit_count: 1128}}}                                                             | funcs:count(1)->Column#14, funcs:bit_xor(cast(crc32(concat_ws(",", cast(tpch10.orders_bak.o_orderkey, var_string(20)), cast(tpch10.orders_bak.o_custkey, var_string(20)), tpch10.orders_bak.o_orderstatus, cast(tpch10.orders_bak.o_totalprice, var_string(18)), cast(tpch10.orders_bak.o_orderdate, var_string(10)), tpch10.orders_bak.o_orderpriority, tpch10.orders_bak.o_clerk, cast(tpch10.orders_bak.o_shippriority, var_string(20)), tpch10.orders_bak.o_comment, "000000000")), bigint(22) UNSIGNED BINARY))->Column#15 | N/A     | N/A  |
|     └─TableRowIDScan_12          | 258213.41 | 208723  | cop[tikv] | table:orders_bak                            | tikv_task:{proc max:17ms, min:0s, avg: 6.29ms, p80:9ms, p95:17ms, iters:210, tasks:14}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | keep order:false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | N/A     | N/A  |
+----------------------------------+-----------+---------+-----------+---------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------+------+
5 rows in set (0.09 sec)

可以看到默认情况下因为选择范围较大导致发生了全表扫描(我已经对orders和orders_bak表做了统计信息搜集),在强制指定让其走主键索引后通过主键回表查找对应的记录,但需要把符合条件记录都搬到tidb-server上进行计算,在并发较多的情况下会影响集群的稳定性。

2 个赞

感谢大佬分享,现在对这个工具的特性和使用有了更深入全面的了解了 :+1: :+1: :+1:

1 个赞

学废了 :smiley:

:+1: :+1: :+1:

1 个赞

已反馈

这个可能需要限制在特定的下游恢复场景下(例如 br 恢复 sst 文件,key 的 _tidb_rowid 保持不变),因为 tidb 会为每个 session 分配 alloc id cache,所以通过 SQL 方式并发写回数据可能会导致上下游原本相同的行,_tidb_rowid 不一致。
例如恢复程序创建两个 session,给一个 session 分配了 1 ~ 30000 的 cache,后面由这个 session 写入的行的 _tidb_rowid 会依次从 1 分配到 30000,再继续申请下一个 alloc id cache。
另一个 session 分配了 30001 ~ 60000 的 cache,如果两个 session 并发写入数据,这样会导致 _tidb_rowid 上下游就不匹配了。