NUMA 问题曾经一直是困扰DBA的一个大问题,早在 2010 年, 就有人给MySQL报了Bug#57241, 指出了MySQL在x86系统下存在严重的 “swap insanity” 问题。在NUMA架构越来越普遍的今天,这个问题越来越严重。

MySQL的 swap insanity 问题

有同学专门翻译了Jeremy Cole关于 “swap insanity” 问题的文章,原文看这里

如果你没空看的话,这里简单描述一下,就是当你把主机大部分内存分配给InnoDB时,你会发现明明操作系统还有很多内存,但是却有很多内存被交换到了SWAP分区。

这里可以下载到一个测试的C代码,如果你有NUMA架构的服务器,可以测试下不同分配方式的性能差异:

  1. sudo -s
  2. echo 2048 > /proc/sys/vm/nr_hugepages
  3. echo 1000000000000 > /proc/sys/kernel/shmmax
  4. # Node local allocation
  5. for i in `seq 0 4 127`
  6. do
  7. ./latency2001 -a $i -c $i -l 128M
  8. done
  9. # Allocate on memory on CPU 0
  10. for i in `seq 0 4 127`
  11. do
  12. ./latency2001 -a 0 -c $i -l 128M
  13. done

有两个方式可以解决这个问题:

  1. 在Linux Kernel启动参数中加上numa=off(这样也会影响到其他进程使用NUMA);
  2. 在mysqld_safe脚本中加上“numactl –interleave all”来启动mysqld。

当然如果跑多实例,我也用过直接绑定mysqld进程到某个numa节点的方式,不过这要求每个实例分配的内存不超过每个NUMA节点管理的内存。脚本可以看这里

5年过去了,官方依然没有解决这个Bug。但好消息是,官方终于着手解决这个问题了,Stewart Smith 同学提交的Bug#72811,其Patch即将出现在MySQL 5.6.27, 5.7.9 版本之中。

代码层面解决NUMA问题

如果在代码层面彻底解决NUMA问题,那么我们需要解决两个问题:

  1. 全局内存应该采用interleave的分配方式分散在不同的numa node上;
  2. 线程内存应该采用local的分配方式分配在线程运行的numa node上。

Linux 提供了 set_mempolicy() 函数可以用来设置进程的内存分配策略,其中默认的MPOL_DEFAULT策略就是在当前运行的节点上分配内存,而MPOL_INTERLEAVE策略则是跨所有节点来分配内存。这个函数的说明可以看这里

因此对于MySQL Server和InnoDB引擎都需要做修改:

  1. mysqld_main()入口设置 set_mempolicy(MPOL_INTERLEAVE, NULL, 0) 启用全局分配方式;
  2. 在MySQL启动完成之后设置set_mempolicy(MPOL_DEFAULT, NULL, 0) 启用本地分配方式;
  3. 在InnoDB入口时设置 set_mempolicy(MPOL_INTERLEAVE, NULL, 0) 启用全局分配方式;
  4. 在Buffer Pool分配完成时设置 set_mempolicy(MPOL_DEFAULT, NULL, 0) 启用本地分配方式。

MySQL 5.6.27, 5.7.9 发布之后,将会增加一个 innodb_numa_interleave 参数来控制这个策略。innodb_numa_interleave 如果打开,那么将会按上面的策略来设置内存分配方式,如果关闭或者主机不支持NUMA,那么还是按原来的方式分配。

我们一起期待新版本的发布吧,妈妈再也不用担心我的NUMA了!