{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/dumpling-export-in-table-concurrency-optimisation",
    "result": {"pageContext":{"blog":{"id":"Blogs_333","title":"Dumpling 导出表内并发优化丨 TiDB 工具","tags":["Dumpling"],"category":{"name":"产品技术解读"},"summary":"之前的文章对 Dumpling 进阶使用进行了介绍，本文接下来将会介绍 Dumpling 内部表内并发的优化逻辑，从而帮助大家更深刻地理解 Dumpling 工作原理。","body":"> 李淳竹（lichunzhu），TiDB 研发工程师  \n> SIG  组：Migrate SIG Community，主要涵盖 TiDB 数据处理工具，包含 TiDB 数据备份/导入导出，TiDB 数据变更捕获，其他数据库数据迁移至 TiDB 等\n\n## 前言\n\n[Dumpling](https://github.com/pingcap/dumpling) 是由 Go 语言编写的用于对数据库进行数据导出的工具。目前支持 MySQL 协议的数据库，并且针对 TiDB 的特性进行了优化。[Go Dumpling! 让导出数据更稳定](https://pingcap.com/zh/blog/go-dumpling-makes-exporteddata-stable)文章对 Dumpling 进阶使用进行了介绍。**本文接下来将会介绍 Dumpling 内部表内并发的优化逻辑，从而帮助大家更深刻地理解 Dumpling 工作原理。**\n\n## 为什么需要表内并发\n\nDumpling 内部的导出逻辑可以用生产消费者模型进行诠释。生产者线程会遍历待导出数据库表集合，再会将生成好的导出 SQL 发送给消费者线程，由消费者线程将 SQL 执行结果格式化后写入文件。不难看出，不同消费者间可以互不干扰地进行并发导出。\n\n由上文较容易推导的是，待导出的数据表彼此并无联系，可以由不同消费者并发导出。但大部分业务场景中，表和表之间的数据量差异巨大，很容易会出现线程空转在一张大表的情况。因此需要将大表划分为更小的“导出单元”(后文将简称为 chunk )以便于消费者线程并行导出，从而提升导出速度。chunk 划分也应该保证尽可能均匀，不均匀的 chunk 划分与大表小表并发导出的问题类似，**会使得导出时间加倍，并极大提升数据库服务器内存使用**。\n\n\n## 导出 MySQL 时的表内并发\n\n那么如何将大表划分为更小且较为均匀的 chunk 呢？可以想到，相比于其他类型，整型数字可以较为均匀地划分为多个 limit 范围，是个最为理想的划分方式。同时，为了保证划分的整数范围能够命中索引，避免重复扫全表从而浪费计算资源，使用的划分范围应该为索引的第一列。由此可以得到针对 MySQL 的表内并发划分方式：\n\n首先选取第一列为整数的索引列记为 field，按照主键、唯一索引、具有最大 [Cardinality](https://dev.mysql.com/doc/refman/8.0/en/show-index.html)  的索引的顺序进行选取，从而保证该列整型数据尽量不同。选择好整数列后，Dumpling 通过 explain 语句粗略估算该表在限定条件下会导出的数据行数并记为 count。根据开头指定了划分行数大小的参数 rows，可以得到 Dumpling 需要将数据划分为 count/rows 个 chunk。随后通过 select min(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) …\n\n从上述实现可以看出指定 rows 后划分 chunk 并不一定为 rows 行。同时，调大 rows 将直接增大各个 chunk 的步长范围即增大各个 chunk 的数据量。因此，如果发现 Dumpling 导出时对数据库内存消耗过大时，可以适当调小 rows 从而减小各个 chunk 的数据量。在实际导出场景中，rows 设置应较为适中：过大会消耗过多内存，且容易使并发效果不好；过小则容易导致 Dumpling 频繁向数据库请求少量数据，使导出速度下降。在目前的实践场景中，配置 --rows=200000 一般能够兼顾并发效果与导出速度。\n\n\n## 导出 TiDB v3.0/v4.0 时的表内并发\n\n从上文可以看出，当用户表不存在分布均匀的整数索引，或者 explain 语句获取数据行数的结果不准确时，表内并发效果将大打折扣。那么，TiDB 和 Dumpling 会怎么处理这一问题呢？在  [TiDB 数据库如何计算](https://docs.pingcap.com/zh/tidb/stable/tidb-computing/#%E8%A1%A8%E6%95%B0%E6%8D%AE%E4%B8%8E-key-value-%E7%9A%84%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB-1)一文中，提到了 TiDB 会为表中每行数据分配一个行 ID，用 RowID 表示。该 RowID 表内唯一且可以通过 select _tidb_rowid 的方式直接从数据库中获取。因此，简单的思路是直接将 _tidb_rowid 当作上文中的整型主键，采用相同的方式进行 chunk 划分即可。\n\n然而，在 [TiDB 高并发写入场景最佳实践](https://docs.pingcap.com/zh/tidb/stable/high-concurrency-best-practices/)中提到，为了避免 TiDB 写入热点，TiDB 表时常会使用 AUTO_RANDOM 列或在建表时加入 SHARD_ROW_ID_BITS 参数。这些参数会使得 _tidb_rowid 列分布极其不均匀，从而导致 Dumpling 导出表内并发划分 chunk 时划分不准确形成大 chunk，影响导出速度甚至引发 OOM。\n\n在 [TiDB 数据库的存储](https://docs.pingcap.com/zh/tidb/stable/tidb-storage/#region)中，可以得到 TiDB 的数据映射为 KV 键值对后，以 range region 的形式存储在 TiKV 上，每个 region 保存了 [StartKey，EndKey) 范围的数据且 TiKV 会尽量保持每个 Region 中保存的数据不超过一定的大小。这些特性非常有利于 Dumpling 划分均匀的 chunk 数据。因此，Dumpling 通过 TiDB 的 INFORMATION_SCHEMA 库下的 TIKV_REGION_STATUS 表获取导出目标表所有 Region 的 StartKey，解码出所需要的 row_id，再使用得到 rowid 作为 WHERE 条件划分出 chunk。\n\n从上述实现中可以看出 Dumpling 的表内并发的划分尺度为 region 大小，rows 的具体值已经不对划分结果产生影响。但是 rows 值设置与否仍将决定 Dumpling 是否采取表内并发的方式导出 TiDB 数据库。\n\n\n## 导出 TiDB v5.0 时的表内并发\n\nTiDB v5.0.0 开始支持了[聚簇索引](https://docs.pingcap.com/zh/tidb/stable/clustered-indexes/)来避免 TiDB 此前使用 rowid 时的回表操作，提升写入查询速度。开启聚簇索引的表将不再有 _tidb_rowid 列。同时，在 split region 等特定场景下，region 的 StartKey 也不一定为合法值。但上文按 region 划分的思路仍然是行之有效的方法，然而需要更好的获取 region 边界划分数据的方法。\n\n为了解决这一问题，TiDB 在 v5.0.0 及以上版本支持了 SELECT fields FROM table TABLESAMPLE REGIONS() 语法。执行该 SQL 后，TiKV 会扫描出表涉及到的每个 region 并获取第一个合法 kv 对，再将得到的数据返回给 Dumpling。例如使用该 SQL SELECT 聚簇索引的各个列时，该 SQL 会返回该表每个 REGION 中第一行聚簇索引的各列值用于均匀划分 chunk。\n\n## Dumpling 后续开发计划\n\n以下为 Dumpling 后续开发的一些计划与设想。目前 Dumpling 已经迁移到 tidb repo，欢迎大家在 [Dumpling Repo](https://github.com/pingcap/tidb) 一起交流讨论，参与开发。\n\n* **[支持导出更多种类的源数据库](https://github.com/pingcap/dumpling/issues/11)(issue#11)**\n\n一般来说，只要需要支持的数据库有对应的 database driver 或 client，比如 Oracle 数据库的 golang driver [godror](https://github.com/godror/godror)，都可以轻微改造导出语句和调用的 Go 代码库后就实现该数据库的导出支持。这里也欢迎社区的小伙伴们参与，帮助 Dumpling 支持导出更多类型的数据库。\n\n* **[支持导出 Sequence](https://github.com/pingcap/dumpling/issues/61)(issue#61)**\n\nDumpling 目前不支持导出 TiDB Sequence，支持该功能将使导出功能更完整。\n\n* **[支持 checksum 校验](https://github.com/pingcap/tidb/issues/30319)**\n\nDumpling 需要支持 checksum[4] [5]  校验来保证导出数据的正确性。\n\n* **[支持 checkpoint(issue#10)](https://github.com/pingcap/dumpling/issues/10)**\n\n支持 Dumpling 使用 snapshot 模式导出 TiDB 时部分导出后从断点继续导出。\n\n> 联    系：channel #sig-migrate in the tidbcommunity  slack workspace, you can join this channel through [this invitation link](https://slack.tidb.io/invite?team=tidb-community&channel=sig-migrate&ref=pingcap-community)。","date":"2022-01-04","author":"李淳竹","fillInMethod":"writeDirectly","customUrl":"dumpling-export-in-table-concurrency-optimisation","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}