TiDB 性能优化实践
作者: Hacker_URrvEGHH 原文来源:https://tidb.net/blog/277ab318
【是否原创】是
【首发渠道】TiDB 社区 or 其他平台
【首发渠道链接】其他平台首发请附上对应链接
【目录】
环境配置
测试负载及初始结果
gogc 参数优化
NUMA 绑定优化
结论
【正文】
1. 环境配置
测试使用了三台物理机,每个节点上都部署 PD、TiKV 和 TiDB,并在其中一个节点上部署 alertmanager、grafana 和 prometheus。
| | TiDB | TiKV | PD | altertmanager | grafana | prometheus || —– | —- | —- | – | ————- | ——- | ———- || node1 | 1 | 1 | 1 | 1 | 1 | 1 || node2 | 1 | 1 | 1 | | | || node3 | 1 | 1 | 1 | | | |
服务器硬件配置:
NVMe SSD 2TB x 1|
|网络|10 Gbps|
软件配置:
参考官方文档,使用 tiup 安装、配置 TiDB 集群,确实很方便。
2. 测试负载及初始结果
由于我们测试的主要目的是为了解 TiDB 对 CPU 的使用情况。为了排除磁盘 I/O 对性能的影响,我们选择 sysbench oltp_read_only 作为测试负载。文档有关于使用 Sysbench 对 TiDB 进行测试的介绍。
同时,我们测试使用的数据集也比较小,目的是确保测试过程中数据可以被缓存在操作系统的 page cache 中,而不需要实际读取磁盘。我们测试使用了 30 张表,每张表 1,000,000 条数据。
初始测试时,我们使用了 TiDB 默认的参数,并且使用 sysbench 只对其中一个 TiDB 节点进行测试。得到的 Sysbench OLTP_Read_Only 结果是 TPS: 4365, QPS: 69838 。
这是我们使用的测试脚本:
sysbench –config-file=sysbench-config oltp_read_only –threads=512 –tables=30 –table-size=1000000 run
sysbench-config 文件的配置为:
mysql-host=10.10.10.207
mysql-port=4000
mysql-user=root
mysql-password=p0o9i8u&
mysql-db=sbtest
time=240
report-interval=10
db-driver=mysql
下一步,我们将基于这个配置对性能进行优化。
3. gogc 参数优化
使用文档的方法收集火焰图,用来分析程序执行的热点。执行命令:
go-torch -u http://localhost:10080 -t 30 -f tidb_default.svg
得到的火焰图是这样子滴:
通过火焰图,我们发现 runtime.gcBgMarkWorker 占比 15.33%。通过查阅文档,我们了解到这个函数和 golang 的 gc 实现有关。可以通过调整参数 gogc 的值来优化它,gogc 值越大,gc 的间隔越长,一般来说可以提升 go 应用的性能,付出的代价是需要占用更多的内存。使用默认 gogc 参数 (100) 时,tidb 占用的内存还不到 2GB,系统还有大量的空闲内存。因此,尝试增大 gogc 的值。通过反复试验,发现在我们的测试环境中 gogc=1000 可以得到比较好的结果,此时 TiDB 的内存占用大约是 16GB。如果设置的值再大,虽然可以少许提高 TPS 值,但性能的抖动会变的很大。调整 gogc 参数后,得到的 Sysbench OLTP_Read_Only 结果是 TPS: 7205, QPS: 115274 ,相比默认配置,性能提升了 65%。
这时的火焰图是下面这样子滴,runtime.gcBgMarkWorker 的占比下降到 2.43%。
4. NUMA 绑定优化
通过我们的分析工具,我们发现 TiDB 访问 local NUMA memory 的比例只有 50% 左右,这说明 golang 或者 TiDB 没有对 NUMA 访问进行优化。由于访问 remote NUMA memeory 的延时一般是 local NUMA memory 的 1.5 倍左右。高比例的 remote NUMA memory 访问,对内存延时敏感的应用的性能会有较大影响。
因此,我们尝试通过修改 TiDB 的启动脚本 run_tidb.sh,用 numactl 将 TiDB 绑定到一个 NUMA node 上,来观察性能的变化。
我们在 run_tidb.sh 中添加了 “numactl -N 0”,修改后的脚本,变成了下面这样:
需要说明的是,使用 numactl 的方式,可以使 TiDB 对内存的访问全部变成 local NUMA memory access,但付出的代价是 TiDB 只能使用机器上一半的 CPU 核。
再次使用 Sysbench OLTP_Read_Only 进行测试,得到的测试结果是 TPS: 8635, QPS: 138154 。相比默认配置,性能提升了 98%;相比只调整 gogc 参数优化的结果,提升了 20%。同时,相比只调整 gogc 参数时,大约 85% 的 cpu 利用率,绑定 NUMA 的方式的 cpu 利用率降低到 69% (虽然 TiDB 只能使用一半的 cpu 核,但 TiKV、PD、Kernel 还可以使用另一半的核)。
再次抓取火焰图,对比之前调整 gogc 的火焰图,两者基本差不多。
那么有没有函数明显受益于 NUMA 绑定呢?通过使用 perf top 抓取运行时的热点,发现在绑定 NUMA 之前,函数 runtime.heapBitsSetType 的占比是 17.25%。
而在绑定 NUMA 后,runtime.heapBitsSetType 的占用降低到 6.45%。
这说明 runtime.heapBitsSetType 函数对 NUMA 访问非常敏感。它应该是 TiDB 性能无法在多 Socket 架构上进行扩展的一个关键因素。
这个问题在火焰图中看不出来,是因为在火焰图中 runtime.heapBitsSetType 的占比没有按照汇总的方式统计,它被分散在了 62 个不同的地方,每处的占比都很小,导致没有被发现。
5. 结论
通过这次的优化实践,我们发现调整 gogc 参数和进行 NUMA 绑定,能够提升 TiDB 的性能。因此,一个比较好的部署方式是在一台两路的服务器上部署两个 TiDB 实例,每个 TiDB 实例部署在一个 Socket 上以获得更好的性能。
此外,如何优化 runtime.heapBitsSetType 方法,使之对 NUMA 访问友好,从而使 TiDB 能够在多 Socket 架构上进行性能扩展,还需要做进一步的研究。
参考文档:
如何做 TiDB BenchmarkSQL 测试: 如何做 TiDB BenchmarkSQL 测试
TiDB 性能测试最佳实践:TiDB 性能测试最佳实践
如何用 sysbench 测试 TiDB: https://docs.pingcap.com/zh/tidb/dev/benchmark-tidb-using-sysbench
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/4f7368bc9965af566b44051e0】。文章转载请联系作者。
评论