{
    "componentChunkName": "component---src-templates-blog-blog-detail-tsx",
    "path": "/blog/htap-read-flow-optimization-from-lixiang",
    "result": {"pageContext":{"blog":{"id":"Blogs_312","title":"理想汽车 HTAP 读流量优化指南丨 TiDB 优化实践","tags":["TiDB 性能调优"],"category":{"name":"案例实践"},"summary":"“没有任何一种数据库是银弹，业务场景的适配和降本增效永远是最重要的。” 数据库的性能优化能够帮助企业最大限度地利用系统资源，提高业务支撑能力和用户体验。本文为 TiDB 性能调优专题的第一篇，在这个专题中，我们将邀请更多 TiDBer 从实际的业务场景出发，分享 TiDB 优化的最佳实践。","body":"**作者介绍**\n\n郑赫扬（艺名：潜龙），理想汽车 DBA。负责公司分布式数据库的技术探索和业务场景落地，热爱开源。就职于金融、互联网教育、电商、新能源汽车等领域。\n\n理想汽车作为豪华智能电动车品牌，以 **“创造移动的家，创造幸福的家”** 为使命。随着电动汽车业务的不断发展，公司业务既有 OLTP 也有 OLAP 的需求，因此需要一款 HTAP 数据库帮助公司实现实时业务决策。在 TUG 企业行 —— 走进 58 同城活动中，来自理想汽车的郑赫扬老师为大家介绍了**理想汽车 HTAP 读流量在物理环境、业务环境、SQL 优化、热点问题、流量环境、版本及架构等方面的优化方案**。\n\n以下为演讲实录。\n\n## 理想汽车选择 TiDB 的理由\n\n**1）一栈式 HTAP：简化企业技术栈**\n\nTiDB 可以在一份数据源上同时支撑理想汽车 OLTP 和 OLAP 需求，不但能很好地支持实时数据落地存储，也能提供一体化的分析能力。另外，TiDB 也可以集成常见的大数据计算技术框架，比如 Spark、Flink 等等，可以使用其构建离线或者实时的数据仓库体系。\n\n**2）解决 MySQL 传统拆库拆表问题**\n\n随着数据量的激增，单机数据库存不下怎么办？传统关系型数据库再扩展性问题，如果业务上已经用了 MySQL，那只能去做分库分表，或者利用中间件去转化，业务层要把代码改个遍。而 TiDB 与 MySQL 完全兼容，MySQL 应用无需修改便可直接运行。支持包括传统 RDBMS 和 NoSQL 的特性，可以随着数据增长而无缝水平扩展，只需要通过增加更多的机器来满足业务增长需求。\n\n**3）简约但不简单的扩缩容**\n\n在经历产品更新的演进中，TiDB 4.0 和 5.0 版本基本上可以通过一些操作控制来灵活便捷地扩缩容，例如通过增加节点来线性扩展计算能力，存储节点同样可以根据存储需求不断进行扩容增加存储。在进行节点缩容时，对线上服务的影响也几乎无感知。\n\n**4）完善的生态工具**\n\n目前理想汽车正在使用 DM（TiDB Data Migration）、TiCDC、TiSpark 等工具，在实际操作中也很强烈的感受到 TiDB 生态工具非常全面，这些生态工具还在不停地演进发展中，我们也在和官方小伙伴一起探索。\n\n**5）丰富的场景支持**\n\nTiDB 在理想汽车的业务场景包括 OLAP 和 OLTP 两个维度。OLAP 包括离线数仓，实时数仓、DMP 平台（包括日常的决策调度、财务调度、报表等）；OLTP 类的商业前端、算法训练（包括线上派单、交付、信息上报）等等。\n\n**6）良好的社区环境**\n\n社区一直是培养 TiDB 不断发展的优渥土壤，在日常维护中一旦出现故障和难点，官方的技术人员就会马上处理，这也给理想汽车很大的信心把 TiDB 引入更多业务场景中使用。\n\n\n接下来跟大家介绍理想汽车针对读流量在以下 **7 个方面**的优化实践。\n\n## HTAP 读流量如何优化？\n\n**1）物理环境优化**\n\n![理想汽车 HTAP 01.webp](https://img1.www.pingcap.com/prod/HTAP_01_c7f9d9fd28.webp)\n\n理想汽车目前把 TiDB 和 PD 集群的配置从原来的 16 核 32G 升级成了 32 核 128G。对于 TiDB 来说，支持 AP 类的大 SQL 可能要跑 7 - 8G 左右的内存，对内存的需求更高。为了解决 PD 的算力问题，我们会预估一下数仓的数据级，预估之后我们会再调大，例如一个单点的话，一个 PD 在 50 万以上可能有调度问题。所以我们可以调大 Region，来横向避免 Region 数量过多的情况。  \n\nTiKV 刚开始使用的是 32C32G 2T 的百度云 SSD，现在和 TiFlash 一样全部采用 32 核 64G 的 4T NVMe 物理盘，这两个升级都是为了更好地统筹计算资源。\n\n**2）业务环境优化**\n\n目前在理想汽车主要的 TiDB 集群都由 DM 同步上游的 MySQL，针对 DM 集群管理做了大量优化。做完之后，就是现在所有的上游 MySQL 主要库在 TiDB 里面都会有副本，因此 TiDB 就具备了 MySQL 从库 + 业务主库 + DMP 平台结果库的功能。\n\n![理想汽车 HTAP 02.webp](https://img1.www.pingcap.com/prod/HTAP_02_2fbaaeb70b.webp)\n\n**关键业务库表 DDL 变更和业务变更**\n\n- 上游 MySQL DDL 变更是否允许都会做一个规范，防止同步任务中断。\n- 新业务完整的测试环境支持。\n- 上游 MySQL 刷业务数据会有大量写流量，DM-sync 线程扩容。刷数据之前大家需要提前自动化，调整到高峰以应对流量冲击，因为下游有很多重要的业务，数据延迟的话会很有影响。\n\n**分类开发规范:**\n\n- OLTP 开发规范：目前理想汽车的单个事务 SQL 结果集大小不能超过 20MB / 结果集 50W 以下，或者 TiDB 计算节点内存小于 120MB，因为 TiDB 要对结果集做一个处理，可能最大扩大至 6 倍。\n\n- OLAP 开发规范：\n1. 复杂 SQL 大表走 TiFlash （一般 2KW），小表走 TiKV。\n\n1. 结果集最大值小于 7KW 或者 TiDB 计算结果内存小于 8G。\n\n1. 不断探索 TiDB 的 OLAP 性能边界。\n\n\n**DM 优化：**\n\nDDL 的问题是不支持变更，假如下游读流量业务受到影响，例如公司上游挂了很多个 MySQL，你希望做 MySQL 同步关联，你只要同步在一个 TiDB 集群里面，你也可以做一个小的数仓，调整方法，首先调整 TiDB 支持 DDL 变更。\n\n\n解决方法:  \n\n(1)调整 TiDB 支持 DDL 变更  \n\n(2)上游新建表 --> 插入业务数据 --> rename 表名 --> 跳过报错（针对上游流量和数据量小，要做数据修补）  \n\n(3)下游新建表 --> 插入业务数据 --> rename 表名 --> 跳过报错（下游不会丢失数据）\n\n\n业务环境优化中，典型的 TP 类型的 SQL 对结果集和算子要求就是 20MB，比如需要考虑环境规划，我们要求结果集在多少，总共不能少过多少万行或者多少的内存，下图所示最大内存是 25.3MB。\n\n![理想汽车 HTAP 03.webp](https://img1.www.pingcap.com/prod/HTAP_03_7513d081c2.webp)\n\n再来看下 AP 类型的 SQL 实时数仓，最大内存是 8G，总共少了 7700 万的数据，执行时间是381 秒，是用 TiFlash 跑的。因为有的报表类或者是定时任务实时性不高，所以分类的话，还是 OK 的。我们现在好多类似的 SQL 在这么跑，TiFlash 5.0.2 集群版本，对于我们来说还可以，业务上比较稳定。\n\n![理想汽车 HTAP 04.webp](https://img1.www.pingcap.com/prod/HTAP_04_0a5609ddba.webp)\n\n**3）SQL优化**\n\n首先得定一下规则\n\n**SQL 索引：**\n\n- OLTP 类：决定查询速度，所有 SQL 必须走索引\n\n\ta. 注意不走索引的情况（where 条件中左侧等式函数，隐式转化，不等式)  \n\n\tb. 优化方式 MySQL 索引基本一致\n\n- OLAP 类：根据表的数量级和 SQL 复杂度  \n\ta. 行存 where 条件查一条数据，行存 + 索引更快。  \n\n\tb. 列存没有细粒度索引，扫描数据性能更好。\n\nDDL 大表（数量过 10 亿）索引上线可能会影响线上业务:  \n\n1.参数调整：业务高峰调小调低优先级  \n\ntidb_ddl_reorg_batch_size = 128\n\ntidb_ddl_reorg_worker_cnt = 2\n\ntidb_ddl_reorg_priority = PRIORITY_LOW\n\n\n\n2.业务低峰调大调高优先级（看监控观察业务）  \n\ntidb_ddl_reorg_batch_size = 1024\n\ntidb_ddl_reorg_worker_cnt = 16\n\ntidb_ddl_reorg_priority = PRIORITY_HIGH\n\n\n**SQL 执行计划：**\n\n读表算子 TiDB 和 MySQL 不一样。MySQL 的话是 Type、Reader 之类的，但是 TiDB 是有分成算子再往下去读像 TableReader，点查大于索引覆盖，相当于 MySQL 的索引覆盖，相当于 TiDB 普通索引。\n\n读表算子优劣：PointGet/BatchPointGet>IndexReader（MySQL覆盖索引）> IndexLookupReader（普通索引）> TableReader。对于 TP 类型 SQL 来说尽可能消除 TableReader 读表算子或者减少结果集。\n\n![理想汽车 HTAP 05.webp](https://img1.www.pingcap.com/prod/HTAP_05_a7a7dec88a.webp)\n\n**SQL 错误索引与统计信息：**\n\nTiDB 统计信息和表格健康度会直接影响你的索引，通常就不走了，所以你的业务突然就变慢了，只能说越来越小了。对于理想汽车来说，看表的健康度只要是大于 80% 的话，正确索引的概率基本上是可以保证的。\n\n\n解决方法：  \n\n手动或者自动更新表和索引统计信息  \n\n(1)自动更新条件\n- 表中至少 1000 行数据\n- 默认 1 分钟无 DML\n- modify_count > tidb_auto_analyze_ratio参数（默认 0.5，设置成 0.8）\n- tidb_build_stats_concurrency（默认 4，资源充足可以调大）\n- tidb_distsql_scan_concurrency（默认 15，AP 30，TP 10）\n- tidb_index_serial_scan_concurrency（默认 1，TP 不调，AP 调成 4）\n\n![理想汽车 HTAP 06.webp](https://img1.www.pingcap.com/prod/HTAP_06_ca07079cfd.webp)\n\n解决方法：  \n\n(1) 设置自动 analyze 时间  \n- tidb_auto_analyze_start_time（这里是 UTC 时间，如果是默认值00:00 + 0000，则早上 8 点开始执行）。所以建议设置时间为业务低峰 -8 小时，比如凌晨执行（16:00 + 0000），结束 tidb_auto_analyze_end_time 设置（23:59 + 0000）。\n\n(2) 如果仍然不准确，可以适当入侵绑定 SQL 计划或者强制走索引。  \n \n- CREATE [GLOBAL | SESSION] BINDING FOR BindableStmt USING BindableStmt;\n\n\n(3) 如果避开了自动 analyze 时间，则应该手动重新统计表信息。  \n\n- show stats_meta where table_name='xxx'\n\n- show stats_healthy where table_name='xxx'\n\n- show STATS_HISTOGRAMS where table_name='xxx' analyze table xxx\n\n**SQL 逻辑优化**\n\nTiFlash 函数下推，大家一定要看一下，因为 TiFlash 不是所有的函数都下推。假如你走 TiFlash 数据量很大，又没有函数下推，代表你会在 TiDB 里面计算，这个过程可能就会比较耗时。建议对准官方的支持列表，（下图为部分截取）按照下推列表去规范 SQL。\n\n![理想汽车 HTAP 08.webp](https://img1.www.pingcap.com/prod/HTAP_08_f5044ac4c0.webp)\n\n\n**4）热点问题优化**\n\n业务报慢 SQL，监控看单个节点 Coprocessor CPU（Coprocessor 是 TiKV 中读取 数据并计算的模块）异常飙升。（v3.0.14 未开启 Unified read pool，面板指标 TiKV - Details -> Thread CPU）\n\n![理想汽车 HTAP 09.webp](https://img1.www.pingcap.com/prod/HTAP_09_4698eedc25.webp)\n\n看读热点，可以基于命令行去做一个脚本，比如 pt-ctl，看 hot read 和 hot write。  \n\n(1) 去看 region 对应的表是哪张，获取热点的单个 region。  \n\n(2) 4.0 以上 dashboard 热力图。  \n\n(3) TiDB_HOT_REGIONS 表中记载了是那张表，有多少个字节。\n\n![理想汽车 HTAP 10.webp](https://img1.www.pingcap.com/prod/HTAP_10_29260c8318.webp)\n\n然后手动 split Region，然后 hot read schedule 进行调度，CPU 使用资源下降，单个读热点 region 就消失了。\n\n![理想汽车 HTAP 11.webp](https://img1.www.pingcap.com/prod/HTAP_11_f9cb40675f.webp)\n\nV4 版本有一个参数 Load base Split，默认 10 秒内 3000 次查询，或者是流量超过了 30MB/秒自动分类。每一家的业务都不一样，每一家的集群也不一样，默认只是说是一个挺好的配置，但是大多数的部署 TiDB 可能用的不是 NVMe，可能用的是云盘的 SSD 或者是普通的 SSD，每一家的读流量标准应该根据各自的硬盘配置标准，设置一个正常流量均衡的 Region。\n\n这个可以通过热力图，也可以通过抓取的方式去看，可以做成自动化监控。所以 TiDB 还有个好处就是 Information Schema 里面几乎涵盖了你所有的信息，比 MySQL 更全。\n\n![理想汽车 HTAP 12.webp](https://img1.www.pingcap.com/prod/HTAP_12_521706cb24.webp)\n\n像监控、命令可以看最高的读取热点，每天都可以看，用命令行做一个筛选，找到对应的库表切割，切割完之后或者自动切割都可以，然后再观察。但是这个 Load base split 有的时候也有可能，也可以每天跑一遍检验它的结果。\n\n![理想汽车 HTAP 13.webp](https://img1.www.pingcap.com/prod/HTAP_13_57045acb77.webp)\n\n\n**5）流量环境优化**\n\n第一，强制 SQL 里面走一些执行计划，执行内容可以限定一个参数，比如说 10 秒，或者内存 20M。我们针对线上 TP 类型是有的，比如下图这个业务的 SQL，就是点击类的报表系统，因为点击类的报表系统由于业务人员选的维度不同，最后的数据量级也不同，你可以设置一个限制，否则可能他点很多次的话，一个大的流量就会把你的集群冲击掉。\n\n![理想汽车 HTAP 14.webp](https://img1.www.pingcap.com/prod/HTAP_14_4dcead2728.webp)\n\nOptimizer Hints 熔断：\n- 执行时间: MAX_EXECUTION_TIME(N)\n- 执行内存: MEMORY_QUOTA(N)\n- 线上 TP SQL\n\n\n第二，Global 参数配置确保资源不溢出和连接质量。\n\n- 长连接控制: pt-kill 控制杀掉 Sleep 线程或者重启 TiDB-Server（慎用）\n- 执行内存: tidb_mem_quota_query\n\n![理想汽车 HTAP 15.webp](https://img1.www.pingcap.com/prod/HTAP_15_3baae8b95d.webp)\n\n第三，理想汽车 AP 和 TP 读流量是分流的，TP 走的是 TiKV，AP 走的是 TiFlash，线上默认所有库表都加了 TiFlash 副本，有 3000 多个同步任务，大概是 3000 多张表，做一个流量分流。有的时候可能走 TiKV 更快，但是有的时候是一个整体切割，如下图所示，现在是两个流量分流了。\n\n![理想汽车 HTAP 16.webp](https://img1.www.pingcap.com/prod/HTAP_16_620977d8af.webp)\n\n- AP 和 TP 的流量均摊，可以更改执行计划，但是我们用 Hint 更改执行计划，发现对 AP 类型非常不友好，测试的时候可能还会走到 TiKV 上面。SELECT /*+ READ_FROM_STORAGE(TIFLASH[test1.t1,test2.t2]) */ t1.a FROM test1.t t1, test2.t t2 WHERE t1.a = t2.a;\n\n- Session 会话控制，需要配合 rollback 回滚参数使用，强行走 TiFlash。set @@session.tidb_isolation_read_engines = \"tiflash\";\n\n- TiFlash 下面有一个参数一定要设置，就是如果 TiFlash 发生查询错误，业务语句返回 TiKV 查询数据。确保万一 TiFlash 副本不能用了，还有读取数据的机会。set global tidb_allow_fallback_to_tikv='tiflash';\n\n**6）版本优化：**\n\n5.0 版本因为有 MPP 功能，我们去尝试从轻 AP 类型转到 AP 类型，看是否可以免除业务迁移的麻烦。我们的测试结果拿 2 台 TiFlash 做 MPP 测试作为参考，结果如下：\n\n- 2 台 TiFlash MPP 业务整体提升 35%。\n- 业务 SQL 比较复杂，5.0.2 之后官方做了更多的下推优化。\n\n下图可以看出优化前后的运行时间对比。这些都是线上业务，线上的数仓 SQL 特别复杂，都是一两千行的大 SQL，打开文本看大概有 1400 行，所以这个优化效果还是很棒的。\n\n![理想汽车 HTAP 17.webp](https://img1.www.pingcap.com/prod/HTAP_17_02b3295d0c.webp)\n\n生产环境中，我们用 5 个 TiFlash node 跑，这是前后的流量对比图，整体有 32 秒，后来基本都降到了 1 秒以下。\n\n![理想汽车 HTAP 18.webp](https://img1.www.pingcap.com/prod/HTAP_18_297cfa3260.webp)\n\n但是这个图很骗人的，大家千万不能相信这个 999，你真正需要优化的，可能就不在这个 999 里面，全在那个 0.01 里面。但是整体优化的效果还是很明显的，包括事务延迟执行 lantency，都降低了很多。整体的响应更具说服力，以下是一天时间内的对比图，大于 30 秒的 SQL 少了 87%，大于 60 秒的 SQL 也降低了 87%，大于 2 分钟的 SQL 同比降低了 99%，同样的流量，5.0.2 对我们整体的优化提升还是非常大的。\n\n![理想汽车 HTAP 19.webp](https://img1.www.pingcap.com/prod/HTAP_19_2227acea95.webp)\n\n**7）架构优化**\n\n架构优化的核心思想是用技术架构来转化 SQL 压力，下图是实时数仓的技术架构。上面是 DM 同步 MySQL 的数据源写入到 TiDB，TiDB 做一个 ODS 层之后，再导入到 TiCDC，之后通过分区打入到 Kafka，再分批消费进入 Flink，然后数据回写回 TiDB，提供实时数据和物化视图功能。因为 TiDB 本身不支持物化视图，可以在 Flink 里面解决这个技术难题，打造一个流批一体的实时数仓。\n\n![理想汽车 HTAP 20.webp](https://img1.www.pingcap.com/prod/HTAP_20_c615568346.webp)\n\n考虑到 OLAP 业务 SQL，我们选择了 **TiKV 存储引擎**，这个时候在 Flink 做完计算的表再写回 TiDB 的话，有一些 AP 的 SQL 就可以变成 TP 了，像 Table Reader 的算子有的时候可以变成 PointGet 或者是 BatchPointGet（点查或者表查），范围查询就会少很多，这样就在侧面缓解了压力。选择 TiFlash 就可以完成各种维度的聚合操作，实现离线报表和在线统计这些功能。这些都是我们已经实施上线的，但是还有几个使用问题，例如：\n\n- 在理想汽车的 DM 场景下，对表做了 rename 后 TiCDC 就无法识别了。因为 TiCDC 默认是以 Table ID 算的，表名一样，但是里面的 Table ID 已经变了，TiCDC 识别不了，就只能重启；\n- 新增的 Kafka 分区，流量增大时增加 Kafka 分机，TiCDC 无法识别，也需要重启；\n- V4.0.14 版本以前的 TiCDC 稳定性存在问题。\n\n\n## 总结\n\n业务发展推动技术革新，目前理想汽车的发展非常快。TiDB 的读流量优化是个**全局视角**，除了 SQL 本身外，官方提供了非常全面的优化手段，包括引擎、架构、执行计划、参数控制等。大家可以去按照自己的业务发展去做各种不同的尝试。\n\n\n当然，优化都不能只看表面，TiDB Duration SQL 999 线是最常看的指标，但是也有欺骗性，但有时候最需要优化的是那 0.01%。\n\n最后，**没有任何一种数据库是银弹，业务场景的适配和降本增效永远是最重要的**。TiDB 更像是集百家之长，而不是专精于一技，在解决分库分表的基础上，基本覆盖所有场景和生态支持。理想汽车也希望能和 TiDB 一起走得更远。","date":"2021-11-04","author":"郑赫扬","fillInMethod":"writeDirectly","customUrl":"htap-read-flow-optimization-from-lixiang","file":null,"relatedBlogs":[]}}},
    "staticQueryHashes": ["1327623483","1820662718","3081853212","3430003955","3649515864","4265596160","63159454"]}