Dumpling导出表内并发优化丨TiD

Dumpling是由Go语言编写的用于对数据库进行数据导出的工具。目前支持MySQL协议的数据库,并且针对TiDB的特性进行了优化。GoDumpling!让导出数据更稳定文章对Dumpling进阶使用进行了介绍。本文接下来将会介绍Dumpling内部表内并发的优化逻辑,从而帮助大家更深刻地理解Dumpling工作原理。

为什么需要表内并发

Dumpling内部的导出逻辑可以用生产消费者模型进行诠释。生产者线程会遍历待导出数据库表集合,再会将生成好的导出SQL发送给消费者线程,由消费者线程将SQL执行结果格式化后写入文件。不难看出,不同消费者间可以互不干扰地进行并发导出。由上文较容易推导的是,待导出的数据表彼此并无联系,可以由不同消费者并发导出。但大部分业务场景中,表和表之间的数据量差异巨大,很容易会出现线程空转在一张大表的情况。因此需要将大表划分为更小的“导出单元”(后文将简称为chunk)以便于消费者线程并行导出,从而提升导出速度。chunk划分也应该保证尽可能均匀,不均匀的chunk划分与大表小表并发导出的问题类似,会使得导出时间加倍,并极大提升数据库服务器内存使用。

导出MySQL时的表内并发

那么如何将大表划分为更小且较为均匀的chunk呢?可以想到,相比于其他类型,整型数字可以较为均匀地划分为多个limit范围,是个最为理想的划分方式。同时,为了保证划分的整数范围能够命中索引,避免重复扫全表从而浪费计算资源,使用的划分范围应该为索引的第一列。由此可以得到针对MySQL的表内并发划分方式:首先选取第一列为整数的索引列记为field,按照主键、唯一索引、具有最大Cardinality的索引的顺序进行选取,从而保证该列整型数据尽量不同。选择好整数列后,Dumpling通过explain语句粗略估算该表在限定条件下会导出的数据行数并记为count。根据开头指定了划分行数大小的参数rows,可以得到Dumpling需要将数据划分为count/rows个chunk。随后通过selectmin(field),max(field)的方式得出在限定条件下的数据中的最大最小field记为max_field与min_field。假设在这个范围内数据是呈现大体均匀分布的,则可以求出划分步长为d=(max_field-min_field)*rows/count。各个表内并发chunk通过where条件约束,范围分别为[min_field,min_field+d),[min_field+d,min_field+2d)…从上述实现可以看出指定rows后划分chunk并不一定为rows行。同时,调大rows将直接增大各个chunk的步长范围即增大各个chunk的数据量。因此,如果发现Dumpling导出时对数据库内存消耗过大时,可以适当调小rows从而减小各个chunk的数据量。在实际导出场景中,rows设置应较为适中:过大会消耗过多内存,且容易使并发效果不好;过小则容易导致Dumpling频繁向数据库请求少量数据,使导出速度下降。在目前的实践场景中,配置--rows=一般能够兼顾并发效果与导出速度。

导出TiDBv3.0/v4.0时的表内并发

从上文可以看出,当用户表不存在分布均匀的整数索引,或者explain语句获取数据行数的结果不准确时,表内并发效果将大打折扣。那么,TiDB和Dumpling会怎么处理这一问题呢?在TiDB数据库如何计算一文中,提到了TiDB会为表中每行数据分配一个行ID,用RowID表示。该RowID表内唯一且可以通过select_tidb_rowid的方式直接从数据库中获取。因此,简单的思路是直接将_tidb_rowid当作上文中的整型主键,采用相同的方式进行chunk划分即可。然而,在TiDB高并发写入场景最佳实践中提到,为了避免TiDB写入热点,TiDB表时常会使用AUTO_RANDOM列或在建表时加入SHARD_ROW_ID_BITS参数。这些参数会使得_tidb_rowid列分布极其不均匀,从而导致Dumpling导出表内并发划分chunk时划分不准确形成大chunk,影响导出速度甚至引发OOM。在TiDB数据库的存储中,可以得到TiDB的数据映射为KV键值对后,以rangeregion的形式存储在TiKV上,每个region保存了[StartKey,EndKey)范围的数据且TiKV会尽量保持每个Region中保存的数据不超过一定的大小。这些特性非常有利于Dumpling划分均匀的chunk数据。因此,Dumpling通过TiDB的INFORMATION_SCHEMA库下的TIKV_REGION_STATUS表获取导出目标表所有Region的StartKey,解码出所需要的row_id,再使用得到rowid作为WHERE条件划分出chunk。从上述实现中可以看出Dumpling的表内并发的划分尺度为region大小,rows的具体值已经不对划分结果产生影响。但是rows值设置与否仍将决定Dumpling是否采取表内并发的方式导出TiDB数据库。

导出TiDBv5.0时的表内并发

TiDBv5.0.0开始支持了聚簇索引来避免TiDB此前使用rowid时的回表操作,提升写入查询速度。开启聚簇索引的表将不再有_tidb_rowid列。同时,在splitregion等特定场景下,region的StartKey也不一定为合法值。但上文按region划分的思路仍然是行之有效的方法,然而需要更好的获取region边界划分数据的方法。为了解决这一问题,TiDB在v5.0.0及以上版本支持了SELECTfieldsFROMtableTABLESAMPLEREGIONS()语法。执行该SQL后,TiKV会扫描出表涉及到的每个region并获取第一个合法kv对,再将得到的数据返回给Dumpling。例如使用该SQLSELECT聚簇索引的各个列时,该SQL会返回该表每个REGION中第一行聚簇索引的各列值用于均匀划分chunk。

Dumpling后续开发计划

以下为Dumpling后续开发的一些计划与设想。目前Dumpling已经迁移到tidbrepo,欢迎大家在DumplingRepo一起交流讨论,参与开发。

支持导出更多种类的源数据库(issue#11)

一般来说,只要需要支持的数据库有对应的databasedriver或client,比如Oracle数据库的golangdrivergodror,都可以轻微改造导出语句和调用的Go代码库后就实现该数据库的导出支持。这里也欢迎社区的小伙伴们参与,帮助Dumpling支持导出更多类型的数据库。

支持导出Sequence(issue#61)

Dumpling目前不支持导出TiDBSequence,支持该功能将使导出功能更完整。

支持checksum校验

Dumpling需要支持checksum校验来保证导出数据的正确性。

支持checkpoint(issue#10)

支持Dumpling使用snapshot模式导出TiDB时部分导出后从断点继续导出。




转载请注明:http://www.aierlanlan.com/cyrz/2655.html