一次TiDB GC阻塞引发的性能问题分析
背景
前不久从项目一线同学得到某集群的告警信息,某个时间段 TiDB duration 突然异常升高,持续时间6小时左右,需要定位到具体原因。
分析过程
第一招,初步判断
由于项目条件苛刻,历经苦难才拿到监控,在此之前只能靠现场同学的口述排查,oncall人太难了。。
既然是duration升高,那就先看看集群的心电图,试图找出一点线索。一般来说,duration升高会有以下两种情况。
第一种是高百分位(比如99、999)明显升高,类似这种:

它的特点是除了高百分位异常,低分位线没有明显波动,这是典型的长尾反应,分位线越高波动越大,说明集群那段时间慢SQL变多,重点关注慢查询辅助排查其他监控即可。
第二种是所有分位线都有明显升高,类似这种:

这说明集群内部受到了影响,慢SQL不是导致duration上升的根本原因,而是集群异常表现出来的结果。但是慢查询依然是我们着手排查的方向之一,慢日志里面记录了慢的一些主要原因,可以作为参考依据。
这个案例碰到的就是第二种。
第二招,用监控还原SQL流程
如果暂时没有什么明确排查目标,可以先按SQL读写流程看一下主要的监控。通常在一个业务系统里,读请求是明显要多于写请求的,所以可以从读流程开始排查,先是TiDB再是TiKV。
推荐参考社区的Trouble Shooting系列文章,非常实用:
详细流程不再赘述。把这一招用完,基本就能定位到问题了,再结合一些非数据库因素,查明真相指日可待。
针对本次案例中,说一下监控排查结论:
1、问题时间段TPS、QPS相较正常情况无明显波动,各项资源使用率平稳,排除TiDB Server的问题
2、问题时间段TiKV节点负载变大,CPU、磁盘、出口带宽使用率明显上升,可判定查询压力大
3、当前GC safe point无推进,阻塞在约4小时前,排查历史监控经常出现类似情况(TiKV Details -> GC -> TiKV Auto GC SafePoint)

初步断定GC运行异常导致历史版本堆积,引发查询效率变慢。
下一步找出证据佐证这个猜想。
第三招,确认非预期的操作
慢查询日志里面有两个和Coprocessor Task 相关的字段可以验证这个猜想,他们是:
Total_keys:表示 Coprocessor 扫过的 key 的数量。
Process_keys:表示 Coprocessor 处理的 key 的数量。相比 total_keys,processed_keys 不包含 MVCC 的旧版本。如果 processed_keys 和 total_keys 相差很大,说明旧版本比较多。
但是介入排查的时候离异常时间已经过去快一天了,用户只保留了30个日志文件,并且慢查询阈值调到了100ms,也就意味着当时产生的Slow Query Log已经没有了,难受。。
最后把希望寄托在tidb.log的 Expensive Query 上面,排查问题时间段发现了很多delete where limit 10000000这种操作,表数据量在千万级,和应用端确认后是由手动执行产生。(DBA看了这种SQL想抄起眼前的键盘。。)
在排查了多条 Expensive Query 后发现,Total_keys普遍在几亿数量级,而Process_keys在百万数量级,进一步验证了前面的判断。

下一步,要找出数据旧版本太多的原因。
第四招,日志分析
GC操作是由TiDB Server的gc worker模块发起的,这里排查起来就比较方便,只需要去TiDB Server搜索日志即可,最后定位到了如下的异常信息:

从上面可以判断,GC在正常发起(每隔10分钟打印一条日志),但是safepoint被一个运行中的会话阻塞了,并且给出了事务的开始时间戳(globalMinStartTS),根据这个信息我们可以找到具体的session:
select instance,id,time,state,info,txnstart from information_schema.cluster_processlist where txnstart like '%xxxxxx%'
到这里拿到session id以后可以根据实际情况判断是否需要kill,但是也别高兴的太早,因为有可能kill不掉。。。
关于kill不掉的问题在asktug吐槽的不少,据社区大佬说是kill成功了只是processlist查出来会残留显示,经过测试5.4.2彻底修复了。
如果真的真的真的kill了没效果,就只能等着事务自己提交或回滚,要不然就得上重启大法。
值得一提的是,上面的processlist查询结果并不一定能查到根源SQL,也就是说info那一列是空值,这可能是单纯由于事务卡着没有提交,而不是某条慢SQL导致,这种情况下就要从应用端着手排查了。
第五招,还原真相
整理一下所有思路,得出以下结论:
- duration升高的原因是数据的历史版本太多,前面说到的频繁delete操作导致tikv节点资源压力较大,从而影响其他SQL(从其他SQL的Coprocessor Task wait_time可以判定)
- 历史版本堆积是因为gc safepoint被阻塞,有一个长期未提交的事务(从safepoint监控图也可以验证)
至此就破案了,一个gc引发的惨案浮出水面。而且,这种问题还会带来一个连锁反应,一旦safepoint恢复推进,大量的历史版本瞬间被gc处理资源消耗极大,又要带来第二波性能抖动,本案真实发生。
预防方案
首先从根源上,应用端要避免大事务操作,或者发起长期不提交的事务,及时提交或回滚。
其次,可以对TiDB GC做限流,降低GC对系统的整体影响:

另外,从TiDB v5.1开始(建议使用5.1.3及以上),可以开启 GC in Compaction Filter 特性(默认开启,简单来说就是做Compaction的时候顺带把GC给做了),能够有效降低GC带来的性能抖动问题。
总结
生产环境保留事故现场非常重要,对于日志文件和监控数据条件允许的情况下尽量保留时间长一些,这对于后期排查问题起决定性作用。
另外,这个案例也告诉TiDB DBA,在对集群做巡检的时候,GC也是一个重要关注指标,一方面要确保GC的相关参数符合预期(比如gc_life_time临时调大后忘了调回去),另一方面要确保GC运行正常,以免发生上述的性能问题。
一次TiDB GC阻塞引发的性能问题分析的更多相关文章
- Mysql中where条件一个单引号引发的性能损耗
日常写SQL中可能会有一些小细节忽略了导致整个sql的性能下降了好几倍甚至几十倍,几百倍.以下这个示例就是mysql语句中的一个单引号('')引发的性能耗损,我相信很多朋友都遇到过,甚至还在这样写. ...
- 【性能测试】常见的性能问题分析思路(二)案例&技巧
上一篇介绍了性能问题分析的诊断的基本过程,还没看过的可以先看下[性能测试]常见的性能问题分析思路-道与术,精炼总结下来就是,当遇到性能问题的时候,首先分析现场,然后根据现象去查找对应的可能原因,在通过 ...
- linux性能调分析及调优
转:https://blog.csdn.net/luokehua789789/article/details/53007456 Linux 性能分析以及调优介绍 写在前面:计算机要解决的基本问题之一是 ...
- 浅谈C++之冒泡排序、希尔排序、快速排序、插入排序、堆排序、基数排序性能对比分析之后续补充说明(有图有真相)
如果你觉得我的有些话有点唐突,你不理解可以想看看前一篇<C++之冒泡排序.希尔排序.快速排序.插入排序.堆排序.基数排序性能对比分析>. 这几天闲着没事就写了一篇<C++之冒泡排序. ...
- SQL SERVER 查询性能优化——分析事务与锁(五)
SQL SERVER 查询性能优化——分析事务与锁(一) SQL SERVER 查询性能优化——分析事务与锁(二) SQL SERVER 查询性能优化——分析事务与锁(三) 上接SQL SERVER ...
- ArrayList和LinkedList的几种循环遍历方式及性能对比分析
最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...
- Web服务器性能监控分析与优化
Web服务器性能监控分析与优化 http://www.docin.com/p-759040698.html
- ArrayList和LinkedList遍历方式及性能对比分析
ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayLis ...
- list 、set 、map 粗浅性能对比分析
list .set .map 粗浅性能对比分析 不知道有多少同学和我一样,工作五年了还没有仔细看过list.set的源码,一直停留在老师教导的:"LinkedList插入性能比Array ...
- Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]
Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...
随机推荐
- Docker MySql 查看版本的三种方法
目录 Docker MySql 查看版本的三种方法 1.mysql -V命令查看版本 2.status命令查看版本 3.version命令查看版本 Docker MySql 查看版本的三种方法 1.m ...
- H5调用微信支付
这里用的是 vue项目; 首先在mounted中判断是否有openId,如果没有,则去获取 let openid = localStorage.getItem('openid'); if (!open ...
- WPF开发经验-实现Win10虚拟触摸键盘
一 引入 项目有个需求,需要实现纯触控操作进行键盘输入.项目部署在Win10系统上,考虑有两种方案来实现. 通过调用Win10自带的触摸键盘来实现: 通过WPF实现一个触摸键盘来实现: 二 调用Win ...
- OpenDataV低代码平台增加自定义属性编辑
上一篇我们讲到了怎么在OpenDataV中添加自己的组件,为了让大家更快的上手我们的平台,这一次针对自定义属性编辑,我们再来加一篇说明.我们先来看一下OpenDataV中的属性编辑功能. 当我们拖动一 ...
- 支付宝沙箱服务 (结合springboot实现,这里对接的是easy版本,工具用的是IDEA,WebStrom)
一:打开支付宝开发平台,登录,然后点击控制台 https://open.alipay.com/ 二:滚动到底部,选着沙箱服务 三:获取到对接要用的appId和公钥私钥 四:打开IDEA导入所需的xml ...
- C语言下for循环的一点技巧总结
for循环是普遍应用与各种计算机语言的一种循环方式. 一般情况下, for循环规则:for(条件一:条件二:条件三) 条件一为满足条件,也就是条件一为1时,进入这个for循环.条件二为循环条件,也就是 ...
- gets,fgets,puts,fputs,scanf,printf的作用,联系和区别
转载: https://blog.csdn.net/lc10915819/article/details/12747943
- ubuntu20.04详细安装教程
1.运行虚拟机,加载一段时间后弹出"安装"界面.语言栏倒数第三个是中文.点击[安装 Ubuntu]. 2.键盘布局,[继续] 3.更新和其他软件,取消[安装Ubuntu时下载更新] ...
- JUC(7)四大函数式接口
文章目录 1.四大函数式接口(必须掌握) 1.1 function 1.2 Predicate 1.3 Consumer 1.4 Supplier 1.四大函数式接口(必须掌握) 1.lambda表达 ...
- 在mybatis中#{}和${}的区别
文章目录 1.第一个#{} 2.第二个${} 3.区别 1.第一个#{} 解释: 使用#{}格式的语法在mybatis中使用preparement语句来安全的设置值 PreparedStatement ...