这个例子是我们与合作伙伴一起完成的课题,银行核心应用在分布式数据库和国产 ARM 服务器上联合优化的案例。系统的硬件采用的是 ARM 服务器,每台服务器有 16 个 Numa,每台机器有一个 NVMe 盘。银行核心应用的负载属于 “Read Heavy”,查询语句占比 66%。本次应用涵盖 4 支混合交易。
TPS 从 1 到 30
这个结果在合作伙伴的实验室跑起来之后,业务的 TPS 只有 1 左右,远低于预期。
业务端会有超时的报错 (Coprocessor task terminated due to exceeding the deadline)。通常这种情况都是执行计划不优化造成的,比如说缺少索引,导致需要全表扫描。从 TiDB 的 Dashboard 上面会看到数据库的 QPS 只有 100 左右,80、90-in-txn 的延迟超过一分钟,再看 Top SQL,可以看到有 Top SQL 因为缺失索引在走全表扫描的。
第一个 SQL 优化例子是解决索引缺失的问题,第二个 SQL 优化的例子是解决有索引却用不上的问题。因为业务系统上使用了 OR 条件,即使 OR 两端的过滤字段上都有索引,也默认走全面扫描。需要手工打开 index merge 功能 (set @@global.TiDB_enable_index_merge=on),执行计划才走索引。
优化这两类慢 SQL 之后之后,TPS 上升到了 30 以上。
TPS 从 30 到 320
接着为了提高资源利用率,我们检查了一下集群的拓扑。测试环境是六台 ARM 服务器,每台16 个 Numa,每个 Numa 是 8C 16GB。现有的拓扑部署了 3 个 TiDB + 3 个 TiKV。TiDB 是绑定到 0~4 的 Numa 上面,没有充分利用整个机器的能力。我们对这个组网的方式做了调整,部署了 36 个 TiDB + 6 个 TiKV,每个 TiDB 会绑两个 Numa ,每个 TiKV 有四个 Numa 。做了这个组网方式的修改之后,TPS 上升到了 320。
确认瓶颈不在数据库之后,我们对整体的火焰图和网络做了一些分析。由下方火焰图可见,整个系统的 CPU 20% 是消耗在一个叫 finish_task_switch 的,做进程切换,调度相关的系统调用上,说明系统在内核态存在资源争抢和串行点。因为有 16 个 Numa,每个 Numa 8 核,一共有 128 核,我们使用 mpstat -P ALL 5 命令对所有 CPU 的利用率进行确认,发现了一个比较有趣的现象 —— 所有的网卡的软中断(%soft),都打到了第一个 Numa(CPU 0-7)上。因为业务本身网络流量大,软中断处理(%soft)在 CPU 0-7 上使用率是 38% 到 94%。又因为我们在第一个 Numa 上面还跑着 TiDB、PD 和 HAProxy 等,用户 CPU (%usr)是 2% 到将近40%,第一个 Numa 的 CPU 都被打满了(%idle 接近 0)。其他的 Numa 使用率仅 55% 左右。跟 ARM 厂商机器的工程师聊过,确认 ARM 服务器默认出厂就会使用第一个 Numa 处理网卡软中断。网卡流量的处理瓶颈解释了 SQL 提交的间隔时间非常长的原因。
整个系统的火焰图
mpstat -P ALL 5 命令输出
另外,对于没有绑核的程序 —— PD 和 HAProxy,我们在火焰图里面观察到关于内存的访问或者内存的加锁等系统调用占比非常高。对于开启 Numa 的系统,其实 CPU 访问内存的速度是不平等的。通常访问远端 Numa 的内存延迟是访问本地 Numa 内存的十倍。硬件厂商也推荐应用最好不要进行跨 Numa 部署,因为在 ARM 服务器进行跨 Numa 的内存访问,延迟会更高,极大的影响程序执行性能。
select instance, count(*) from information_schema.cluster_slow_query where index_names like '%MQ_STATUS_INDEX%' group by instance;
select conn_id,instance, count(*) from information_schema.cluster_slow_query where index_names like '%MQ_STATUS_INDEX%' and digest = 'cca85ee01e54b3b37775c8b07c2808f306177d28fd0376b2d8c5dd5663f488ec' group by instance,conn_id;