去年发过一篇文章,MySQL5.6的服务器通过修改mysqld_safe文件,增加"/usr/bin/numactl--interleave all"启动的方法解决了由于NUMA Node间内存分配不均导致的swap的问题。对于MySQL5.7.23版本的服务器,我们使用了innodb_numa_interleave选项,但问题并没有彻底解决。
【使用MySQL5.7新增innodb_numa_interleave选项的问题】
在开启innodb_numa_interleave选项的服务器中,仍然会存在NUMA Node间内存分配不均衡的问题,会导致swap产生。针对这个问题做了进一步分析:
1、MySQL 版本为5.7.23,已经开启了innodb_numa_interleave
2、使用命令查看mysqld进程的内存使用情况,numastat-mn `pidof mysqld`
可以看出Node 0使用了约122.5G内存,Node1使用了约68.2G内存,其中Node0上的可用空间只剩566M,如果后面申请Node 0节点分配内存不足,就可能产生swap
Per-node process memory usage (in MBs) for PID 1801 (mysqld)
Node 0 Node1 Total
--------------- --------------- ---------------
Huge 0.00 0.00 0.00
Heap 0.00 0.00 0.00
Stack 0.01 0.07 0.09
Private 125479.61 69856.82 195336.43
---------------- --------------- ------------------------------
Total 125479.62 69856.90 195336.52
3、是innodb_numa_interleave没有生效吗,通过分析/proc/1801/numa_maps文件可以进一步查看mysqld进程的内存分配情况
以其中一条记录为例,
7f9067850000表示内存的虚拟地址
interleave:0-1表示内存所用的NUMA策略,这里使用了Interleave方式
anon=5734148匿名页数量
dirty=5734148脏页数量
active=5728403活动列表页面的数量
N0=3607212 N1=2126936 节点0、1分配的页面数量
kernelpagesize_kB=4 页面大小为4K
7f9067850000 interleave:0-1 anon=5734148 dirty=5734148active=5728403 N0=3607212 N1=2126936 kernelpagesize_kB=4
4、通过解析上面文件,对Node 0和Node 1节点分配的页面数量做统计,可以计算出Node 0通过interleave方式分配了约114.4G内存,Node 1通过interleave方式分配了约64.7G内存
说明innodb_numa_interleave开关是实际生效的,但是即使mysql使用了interleave的分配方式,仍然存在不均衡的问题
5、通过innodb_numa_interleave相关的源码,可以看出当开关开启时,MySQL调用linux的set_mempolicy函数指定MPOL_INTERLEAVE策略跨节点来分配内存set_mempolicy(MPOL_INTERLEAVE,numa_all_nodes_ptr->maskp, numa_all_nodes_ptr->size)
当开关关闭时,set_mempolicy(MPOL_DEFAULT, NULL, 0),使用默认的本地分配策略
my_bool srv_numa_interleave = FALSE;
#ifdef HAVE_LIBNUMA
#include <numa.h>
#include <numaif.h>
struct set_numa_interleave_t
{
set_numa_interleave_t()
{
if (srv_numa_interleave) {
ib::info() << "Setting NUMA memory policy to"
" MPOL_INTERLEAVE";
if (set_mempolicy(MPOL_INTERLEAVE,
numa_all_nodes_ptr->maskp,
numa_all_nodes_ptr->size) != 0) {
ib::warn() << "Failed to set NUMA memory"
" policy to MPOL_INTERLEAVE: "
<< strerror(errno);
}
}
}
~set_numa_interleave_t()
{
if (srv_numa_interleave) {
ib::info() << "Setting NUMA memory policy to"
" MPOL_DEFAULT";
if (set_mempolicy(MPOL_DEFAULT, NULL, 0) != 0) {
ib::warn() << "Failed to set NUMA memory"
" policy to MPOL_DEFAULT: "
<< strerror(errno);
}
}
}
};
【测试对比开启innodb_numa_interleave开关和numactl –interleave=all启动mysqld进程两种方式NUMA节点的内存分配情况】
场景一、numactl --interleave=all启动mysqld进程的方式
1、修改systemd配置文件,删除my.cnf中innodb_numa_interleave=on开关配置,重启MySQL服务
/usr/bin/numactl --interleave=all usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid$MYSQLD_OPTS
2、运行select count(*) from test.sbtest1语句,这个表中有2亿条记录,运行14分钟,会将表中的数据读到buffer pool中
3、运行结束后,分析numa_maps文件可以看到mysqld进程采用了interleave跨节点访问的分配方式,两个Node间分配的内存大小基本一致
7f9a3c5b3000 interleave:0-1 anon=1688811dirty=1688811 N0=842613 N1=846198 kernelpagesize_kB=4
7f9a3c5b3000 interleave:0-1 anon=2497435dirty=2497435 N0=1247949 N1=1249486 kernelpagesize_kB=4
4、mysqld进程总的分配也是均衡的
场景二、开启innodb_numa_interleave的方式
1、增加my.cnf中innodb_numa_interleave=on开关配置,重启MySQL服务,执行与场景一相关的SQL语句
2、运行结束后,分析numa_maps文件可以看到mysqld进程采用interleave方式分配的在不同Node间是基本平衡的
7f71d8d98000 interleave:0-1 anon=222792 dirty=222792 N0=111652N1=111140 kernelpagesize_kB=4
7f74a2e14000 interleave:0-1 anon=214208 dirty=214208 N0=107104N1=107104 kernelpagesize_kB=4
7f776ce90000 interleave:0-1 anon=218128 dirty=218128 N0=108808N1=109320 kernelpagesize_kB=4
3、不过仍有部分内存使用了default的本地分配策略,这部分内存全部分配到了Node 0上
7f31daead000 default anon=169472 dirty=169472 N0=169472 kernelpagesize_kB=4
4、最终mysqld进程分配的内存Node 0 比Node 1大了约1G
【MySQL5.7.23启用numactl–interleave=all的方法】
MySQL5.7版本不再使用mysqld_safe文件,所以启用numactl –interleave=all的方式,与MySQL 5.6的方法不同,总结如下:
1、修改vim /etc/my.cnf文件,删除innodb_numa_interleave配置项
2、修改systemd 的本地配置文件,vim /usr/lib/systemd/system/mysqld.service,增加/usr/bin/numactl --interleave=all命令
# Start main service
ExecStart=/usr/bin/numactl --interleave=all /usr/sbin/mysqld--daemonize --pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS
3、停止MySQL服务
systemctl stop mysqld.service
4、重新加载配置文件
systemctl daemon-reload
5、写入硬盘,防止数据丢失
sync;sync;sync
6、延迟10秒
sleep 10
7、清理pagecache、dentries和inodes
sysctl -q -w vm.drop_caches=3
8、启动MySQL服务
systemctl start mysqld.service
9、验证是否生效,
首先确认show global variables like ' innodb_numa_interleave';开关为关闭状态
正常情况下mysqld进程会全部采用interleave跨节点访问的分配方式,如果可以查询到其他访问方式的信息,表示interleave方式没有正常生效
less /proc/`pidof mysqld`/numa_maps|grep -v 'interleave'
【结论】
numactl –interleave=all启动mysqld进程的方式NUMA不同Node间分配的内存会更加均衡。
这个差异是与innodb_numa_interleave参数执行的策略有关,开启后,全局内存采用了interleave的分配方式,但线程内存采用了default的本地分配方式。
而如果使用numactl –interleave=all启动mysqld进程,所有内存都会采用interleave的分配方式。