TiDB 的 TiFlash 怎么用 | TiFlash 的最佳场景 & 稳定性管理
作者: 有猫万事足原文来源:https://tidb.net/blog/772a4767
前言
经过之前的 4 篇,其实总体的报表相应时间已经从小时级别到了分钟级,有新的报表需求,基本很快都能解决。
https://tidb.net/blog/f6bc5537
https://tidb.net/blog/666ab16d
https://tidb.net/blog/15d0fbf6
https://tidb.net/blog/26695303
从老板的角度看原有的问题已经解决了,但我个人还是抱着尝试的心态,体验了一下 TiFlash。这一体验就再也没有停下。TiFlash 因为没有成本,被我和其他系统的测试服挤 2 台 4c8g。涉及 TiFlash 的主要是日志表,这种表比较大,相关的 AP 业务也比较多。当 mpp 运行需要用到其他小表的时候,再一点点的给小表添加 TiFlash 副本。
1,使用 TiFlash 的最佳场景
TiFlash 能让我用了就离不开的一个最主要的原因就是因为它对聚合计算的加速特别明显,能轻松比 TiKV+ 索引的方式快 2-10 倍。聚合计算体现在 sql 上,就是带 group by 的语句。可以说只要带 group by 的语句放到 TiFlash 上执行都会有个很大的提升。当然不带 group by 直接全表聚合,类似 select count(1) from t 这种,select 后面只带聚合函数的 sql 也是聚合计算。
而 TiFlash 不擅长的是扫描行数据,比较典型的情况是从一个 100+ 亿的表中根据条件找几百行的数据。这种任务 TiFlash 也可以做,但是不会有上面那种聚合计算的提升大。有的时候可能性能使不如 TiKV+ 合适的索引查的更快的。
还有一类大量行扫描的任务就是导出数据,一般有了 AP 系统以后,为了不影响 TP 系统稳定,需要大量导出的时候就想从 AP 系统上导出数据。
而类似的情况在 TiDB 的架构下就会考虑是否可以从 TiFlash 导出,我也这么尝试过,结果是非常困难。如果是有些导出后还要再计算的情况,不妨考虑直接写成 sql 在 TiFlash 上算,可能都比导出的时间短。如果数据很多,甚至没法从 TiFlash 上导出。
1.1,TiFlash 的 MPP 模式
对于 MPP 模式的详细介绍有很多。这个模式有一些运行的特点:多节点并行读取 / 计算,多个节点之间可以相互通信,交换数据。落实到一个具体的 sql 上,我认为最重要的一点,就是可以让 hashjoin 运行在 TiFlash 上。这是和原来 PD,TiDB,TiKV 这三件套组成的存算分离体系的一个显著的不同点。在原来的存算分析体系下,TiDB 负责计算,TiKV 负责存储。一个 hashjoin 的运行是在 TiDB 上,join 两边的行数据在 TiKV 上运行。而在 AP 业务中一个带 join 的聚合计算,往往要扫描大量的数据,和复杂的计算,在没有 MPP 模式的情况下,这会同时给 TiDB 和 TiKV 都带来显著的压力,对原有的 TP 系统的稳定性造成威胁。MPP 模式执行这类 sql,那么在这个模式下,TiDB 节点差不多是直接从 TiFlash 上拿到计算好的结果,只有很少的计算,资源占比很小。
1.2,TiFlash+MPP 情况下,好的执行计划的特征
要做到计算大部分下推到 TiFlash 上用 MPP 模式执行,TiDB 只有少量计算。
这种执行计划有个显著的特征:

主要看执行计划中 task 这一栏,上面是几行 root——代表 TiDB 做的少量计算,下面几行全是 mpp[tiflash]——代表大部分计算是通过 TiFlash+MPP 模式来做的。
1.3 如何实现这个优化
我们现在有了一个带 join 的聚合计算 sql,也了解到通过 TiFlash+MPP 的方式可以让这个 sql 运行的更快。那么该如何做到这一点?和 MPP 计算相关的参数有 2 个:tidb_allow_mpp 和 tidb_enforce_mpp 当这两个参数是 OFF 的情况下就是任何情况都不使用 mpp 模式。当 tidb_allow_mpp=ON,tidb_enforce_mpp=OFF 的时候,就是交给优化器判断是否可以使用 mpp 模式。如果两个参数都是 ON,则是要求优化器只要 sql 涉及到的表有 TiFlash 副本,就会使用 MPP 模式执行。tidb_allow_mpp=OFF,tidb_enforce_mpp=ON, 这个奇怪的组合也是可以的,但是这种参数设置组合等同于 2 个参数都是 OFF,即在任何情况下都不使用 MPP 模式。
如果一个 sql 我们判断它属于聚合计算,又发现它没有走 mpp 的执行计划。这个时候需要定位不能 MPP 的原因,我们需要把这两个参数全部设置为 ON,再去 explian 这个 sql,得到一个不走 mpp 的执行计划,再 show warnings,就能看到不走 mpp 的原因。
特别需要注意的是,因为这两个参数对现有系统的影响比较大,如果你不小心设置 global 级别 tidb_allow_mpp=ON,tidb_enforce_mpp=ON,会让原本系统中不该走 TiFlash MPP 的 sql 也走 TiFlash MPP。所以在定位问题的时候一定要 seesion 级别设置这 2 个变量。不然会对现有系统的稳定造成较大的冲击。
在导致不能 MPP 的原理里面,我想重点提一下动态分区裁剪这个参数
set @@session.TiDB_partition_prune_mode = ‘dynamic’
我在论坛碰到的大部分不能 MPP 的主要原因都是这个参数,有些老的系统升级上来的时候发现动态分区裁剪会导致原有的执行计划发生变化,为了保险往往会关闭动态分区裁剪,而 MPP 模式恰好需要动态分区裁剪,调和这种矛盾的办法也是像上面这 session 级打开一下,或者通过set_var 这个 hint 在 sql 语句执行的时候,临时修改一下这个参数。set_var 这个 hint 需要 7.5 版本的支持。
2,TiFlash 自身的稳定性管理
MPP 模式在执行速度上会有明显的优势,但往往有更利的矛,你就想尝试用它对付更坚固的盾。在一个需求刚开始做的时候,以及在临时发起的 adhoc 类查询中,首先需要面对的,就是内存容易 OOM 的问题。
2.1 内存占用控制
执行速度的加快,会让人想尝试更大范围的聚合,大范围的聚合往往也会带来内存占用的快速上升。为了让这类 sql 能运行过去,主要的办法是使用算子落盘。有两种算子落盘的方式:算子级落盘 (7.1 版本),查询级落盘 (7.5 版本)。算子级落盘需要单独设置 3 个内容占用高的算子的内存上限,这 3 个算子是 sort,group by,join。查询级落盘,则是根据真个语句占用的内存来计算内存上限。明显查询级的落盘更好,我也主要介绍这种算子落盘的方式。语句级的算子落盘主要涉及 2 个参数:tiflash_mem_quota_query_per_node——用于控制单个查询在单个 TiFlash 节点中的内存上限,超过这个限制 TiFlash 会报错并终止。tiflash_query_spill_ratio——用于控制语句级落盘的阈值。当使用的内存超过 tiflash_mem_quota_query_per_node*tiflash_query_spill_ratio,TiFlash 会触发落盘。
当然如果你想告诉编写这些业务查询人员一个简单的数据量估算,可以推荐他们每次执行 sql 的时候先看看执行计划。执行计划中有一栏 estRows——这代表这个 sql 根据统计信息预估的某个算子需要处理的数据行数。假如你的统计信息是靠谱的,那么这个推算大致是比较符合预期的,起码不会有数量级的偏差。让业务查询人员做这类查询的时候先看看 estRows 中的最大值,是个比较简单有效的快速预估处理数据规模的办法。在我这个 4c8g 2 台 TiFlash 做 MPP 的情况下,我给业务人员推荐的 estRows 数值的最大值是 6 亿,如果 estRows 这一栏中最大的数字超过 6 亿——预估处理行数的超过 6 亿行,目前的机器配置不靠落盘是大概率运行不过去的。
2.2 并发控制
翻过了内存控制的山,下一个容易碰到的问题就是并发控制。常见的现象,比如某个 sql 明明单独执行很快,但是生产系统上使用效果并不好。如果发现是 TiFlash+MPP 的这种执行计划(上文 1.2 部分的内容),那就需要关注一下并发是不是高了。TiFlash+MPP 对聚合计算的加速效果很明显,但是并发一高性能退化的也快。解决这个问题,主要手段是查询结果物化 。
前面已经说了在 TiFlash+MPP 执行的情况下,负载主要在 TiFlash 这个列存节点上进行。查询结果物化的意思就是,让列存节点(TiFlash)中计算完成的结果保存到行存节点(TiKV)中。后续的访问就可以通过行存 TiKV 来访问这些结果。这个思路和物化视图很像,差别可能仅在需要手动刷新,执行的速度由查询和插入 2 个时间组成。就查询而言,优化方式上面已经写了,就插入时间的优化,主要是防止 insert into 的表有写入热点。如果 select 中的数据在聚合后还是很大,有几万行以上,那么是需要考虑 insert into 的表是否存在写入热点的。另外文档种这个查询结果物化中的例子是有点狭窄的。insert into … select … 这个语句第一次建立对应查询的物化结果的时候是有用的。但后续刷新就没法再用了。
经过个人测试和发帖确认,replace into 也可以做到从查询结果物化

可以看到查询结果也是 task 这一栏上面是几行 root,下面全部是 mpp[TiFlash].
假如你负责写入的这个表的指标设计的比较复杂,单独一列的计算都是一个复杂的聚合计算,如果要做到按列刷新,可以使用 insert into … select …on duplicate key update 的形式来按列更新。执行计划如下:

可以看到也是上面这个形式。
对于这个功能,我觉得是因为文档举例介绍的范围有点窄,所以是被低估的。在 insert into … select …on duplicate key update 这种按列更新也能支持的情况下,可以做很多事。哪怕不用于对付高并发,做报表的跑批更新也能用到这个功能,而且完全发挥了列存和行存这两者的优点。做到列存算,行存插入 / 查讯。可以说是手动支持了物化视图的刷新。使用确实没有物化视图那么友好,但大致的物化视图功能,通过手动刷新麻烦一点是马马虎虎能做到的。
3,TiFlash 在整个 TiDB 架构中的一些思考
TiFlash 是通过 raft learner 的角色加入原有的 TiKV raft 组,从 TiKV 同步数据到 TiFlash 的。

所以显而易见的一个问题是 TiFlash 稳定性依赖 TiKV 的稳定性。即,在整个 TiDB 的体系中,应该先保证 TP 业务的稳定,在考虑 AP 业务。如果 TP 业务本身已经不怎么稳定的情况下,想通过添加 TiFlash 节点解决 TP 业务面临的一些问题,对于新手来说是比较困难的,我在论坛上经常见到的很多对 TiFlash 的负面评价都来源于此。本身的 TP 业务就不太稳定,已经面临各种各样的慢查询的,希望通过添加 TiFlash 这种列存节点来尝试优化,如果是聚合计算这种 TiFlash 这种本身擅长的场景,可能确实有用。不过造成这种情况的大部分用户,不能很好的区分这点,那么眉毛胡子一把抓,TP 和 AP 业务混合负载没有在各自擅长的节点上运行的结果,往往会使得情况恶化,优化的难度也变高了。给人的感觉就像是添加了 TiFlash 反而做了负优化。对 TiFlash 而言这是个比较冤枉的评价。
3.1 渐进式的尝试 TiFlash 是个很好的实践
其实我自己的实践,无意中就是这种渐进的尝试。
我首先是从建立好了 PD TiDB TiKV 这三件套,通过资源管控等一系列优化,保证了这三件套运行的稳定,然后才开始尝试 TiFlash 节点。TiFlash 节点在没有任何负载(0 副本 0 请求的初始状态下)的时候只要几百 M 的内存就能运行起来,而我使用的 4c8g 的配置也远低于文档要求的最低配置。我当然不是鼓励大家都不按照文档推荐的来做,我强调的重点在于如果数据集小于 1T,大致在 300g 左右,那么从试用 / 尝鲜 / 体验的角度讲,4c8g 这种低成本的机器就可以开始用 TiFlash 了。大家可以低成本的用起来,按我上面说的自己参数针对聚合计算的 sql 尝试着做一下,具体运行的速度能提升多少,大家也就有数了。那么后续无论是继续尝试更大数据集的聚合计算,还是写报告向老板申请更好的资源,都有依据。不必因为成本的原因,没有办法进行从 0 开始的第一步。
AP 节点的扩展能进行这种渐进式的尝试,意味着无论是在工程上,还是在公司内部的政治上,都能降低背锅的概率。这对于初次尝试新组件 / 新技术的 DBA 来说尤为重要。
3.2 更好的实践推荐
推荐李文杰老师 ( @Jellybean ) 的一篇文章:《 构建稳定高效 TiDB 多租户系统的方法与实践 》

这是我看了以后很有启发的一篇文章。可以看到每组 TP/AP 业务都有自己独立的 LB,有自己独立的一组 TiDB 节点,并且 TP 业务不访问 TiFlash。而 AP 业务可以访问 TiKV 也可以访问 TiFlash。这样设置的结果,我自己总结了一下,就是让合适节点运行合适的负载。TP 业务就应该运行在原有的 PD,TiDB,TiKV 的三件套上,把 AP 业务中聚合计算的部分,通过 MPP 大部分下推给 TiFlash 做。有些需要查询结果物化之后的表,存在 TiKV 中,如果没有多层的聚合,对于最顶层的聚合结果来说这部分查询其实是扫描任务而不是聚合计算了,直接从 TiKV 上读取更高效。
当然小公司 / 小集群可能没有这么财大气粗,各种业务有独立的 LB,和一组独立的 TiDB 节点。但让 TP 和 AP 业务分别运行在各自适合的节点上这个思路仍然是可以实现的。
我的 TP 业务主要 dm 同步上游的 mysql 写入。通过在所有实例上,同时设置 global 级别的 tidb_allow_mpp=OFF,tidb_enforce_mpp=OFF. 关闭 MPP 下推。那只要连接来的业务不做特殊处理都是无法访问 TiFlash 的。这就保证了现有 TP 业务不会访问 TiFlash。AP 业务主要是通过 metabase 连接来的这部分查询,而在 metabase 连接 TiDB 的 jdbc 连接串上,我设置了参数 TiDB_allow_mpp=ON,TiDB_enforce_mpp=ON,这就保证了通过 metabase 建立的连接,只要有 TiFlash 副本,基本都是 MPP 下推到 TiFlash 的。个别需要调整的可以通过在 sql 中添加 hint set_var 语句级的修改变量,达到不做 mpp 下推的效果。
当合适的负载运行在合适的节点上的时候,就会产生 1+1>2 的效果,即使 AP 计算量很大导致不稳定,因为这些聚合计算设置了强制 MPP 下推也不会威胁到原有 TP 系统(PD,TiDB,TiKV 这三件套)的稳定。这是对 TP 系统的托底,是最悲观的情况。
如果没有碰上最悲观的情况,那么聚合计算在 TiFlash 上运行的更快。两边都做了自己擅长做的事,还能各自独立扩容,也是扩展性和 HTAP 特性的体现。
4,总结
4.1 优点
4.1.1 速度
TiFlash+MPP 对聚合计算的加速效果非常明显,可以轻松比 TiDB+TiKV 的方式快 2-10 倍。即使没有成本,我也没法拒绝 TiFlash,要想办法用,这是最重要的一点。大家可以看我生成列的那篇,那篇我用了生成列把一个 sql 从 1 分钟左右优化到了 10s 左右。但我用上了 TiFlash,这个 sql 不通过生成列优化,基本上也就是 3-4s。我不能说生成列这圈折腾没有意义,毕竟我之前也说过优先保证 TP 业务的稳定再考虑 AP 是推荐的做法。但如果我下次再做的话,显然这圈折腾我是不会重来一次的。我会直接考虑 TiFlash+MPP 来解决聚合计算速度慢的问题,只有发现这个 sql 是个扫描任务的时候才会考虑是否用生成列。
4.1.2 数据同步
在我这个 5-600G 的数据规模,能提供的成本也就这点的情况下,如果要再找机器弄专门的 AP 分析库,是基本不可能做到的。而且存在同步链路,那么这个同步链路其实是日常维护的大头。这点我相信做 AP 分析的应该深有体会。数据只要进到库里,无非多聚合几次肯定能算出来。同步链路出问题导致报表出不来是日常维护的大头,而且很不可控,都是突然袭击。在 TiDB 体系下,这个同步的问题交给 raft 协议来保证,还能提供一致性读取,TP 和 AP 业务如果运行在各自擅长的节点上,还能实现 2 种业务的适当隔离和单独扩展,简直不要太友好。
4.1.3 数据核查的简化
能提供一致性读取,也就意味着从 TP 到 AP 的数据核查会变得更简单。如果是从 TiDB 同步数据到一个专门的 AP 库上,那么出现数据核查的问题,要从 AP 库上查起,检查同步问题,最后对到 TP 的库上,这样查一遍才算有个结论。TiDB 体系下能提供一致性读取对数据核查就非常友好了,如果怀疑 TiFlash 的计算结果有问题,我只需要去掉 MPP 下推,让这个聚合计算的 sql 去 TiKV 上算一下,看看结果如何,起码就能大致解决 TP 到 AP 之间的核查问题了。对于已经已经运行了一段时间的老报表这个问题可能还不算突出,但是新需求新报表,当看报表的人向你提出核查的时候,其实你很难拒绝。毕竟报表的“报”字,就意味着看报表的人,往往决策能力比你强,在具体业务上的话语权也比你大。
4.2 缺点
4.2.1 资源压力大
算的快了就会尝试更大的数据集,更复杂的计算。这是人性,会不自觉地开始这么做。我也是这样。那么很容易就会遇到内存 oom 的问题。落盘还是执行计划种的 estRows 估算,这种两种方法都可以酌情使用。方便写这些 sql 的人能理解,他们正在处理一个多大规模的数据。另外还要对 sql 运行的并发程度有个预估,其实跑批的聚合计算任务通过查询结果物化 + 算子落盘是会有个比较好的运行结果的。怕的是某些本来可以做一些缓存的偏向 TP 的任务,发现执行速度快了以后也不做缓存了,直接高并发的下推给 TiFlash 做。对并发数还是需要一个预估的。不能预估初期就优先尝试查询结果物化 + 定时刷新。或者干脆定期查好了放 redis 之类的缓存里面去。
4.2.2 混合部署后的管理难度很大
我配置虽然很低,但是能用的一个主要原因,我觉得就是存储引擎无论 TiKV 还是 TiFlash,他们之间都没有混布。假如配置很低,存储引擎无论如何不要混布,TiKV 和 TiFlash 最好都独享一台机器。TiDB 和 PD 可以放一起。这样能较好的保证整个集群的稳定性。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/593744602a52125eefe9b7653】。文章转载请联系作者。
评论