NUMA 在 DB 上的一些问题

关于 NUMA (Non-Uniform Memory Access, 非一致性内存访问)的概念,可以看 MicroSHIT 的这篇文档。概括起来,NUMA 就是把 CPU 与特定的内存做绑定,这个在有好多物理 CUP 的情况下比较占优势。
而 NUMA 分为硬件的和软件的,硬件的有好几条系统总线,每条系统总线为一小组 CUP 服务,然后这一小组 CUP 有自己的内存;软件的跟软 RAID 类似,没有硬件的方式只能通过软件的方式实现了。
跟 NUMA 对应的是 SMP(Symmetric Multi-Processor, 对称多处理器),所有的 CUP 共享系统总线,当 CUP 增多时,系统总线会互相竞争强资源,自然就撑不住了,正常的 SMP 体系也就只能支持十来个 CPU,多了系统总线就会成为瓶颈。
现在还出现了个叫 MPP(Massive Parallel Processing) 的体系,比 NUMA 的扩展性更牛逼。
这里有张图比较形象的描述了这三者,可以参考一下。

说上面这段话是为了引出下文,在启动 mongo 的时候出现下面的 warning:
Thu Sep 7 15:47:30 [initandlisten] ** WARNING: You are running on a NUMA machine.
Thu Sep 7 15:47:30 [initandlisten] **          We suggest launching mongod like this to avoid performance problems:
Thu Sep 7 15:47:30 [initandlisten] **              numactl –interleave=all mongod [other options]

根据官方文档的解释,Linux, NUMA, MongoDB 这三者不是很和谐,如果当前硬件是 NUMA 的,可以把它给关了:
# numactl --interleave=all sudo -u mongodb mongod --port xxx  --logappend --logpath yyy --dbpath zzz

官方还建议将 vm.zone_reclaim_mode 设置为 0。系统给 NUMA node 分配内存,如果 NUMA node 已经满了,这时候,系统会为本地的 NUMA node 回收内存而不是将多出来的内存给 remote NUMA node,这样整体的性能会更好,但是在某些情况下,给 remote NUMA node 分配内存会比回收本地的 NUMA node 更好,这时候就需要将 zone_reclaim_mode 给关闭了。这篇文章记录了对于 web server/file server/email server 不设置为 0 的悲剧。这里还有一篇,描述了作者遇到的现象,因为遇到了内核 bug。

不过,真正起作用的其实还是 numactl,看了这篇文档所讲,抽取出来,NUMA 有几个内存分配的策略:

1. –localalloc, -l: Always allocate on the current node.

2. –preferred=node: Preferably  allocate  memory on node, but if memory cannot be allocated there fall back to other nodes.  This option takes only a single node number.  Relative notation may be used.
至于这个 node 怎么出来,可以通过下面这个命令看到:
$ numactl  --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14
node 0 size: 24576 MB
node 0 free: 23183 MB
node 1 cpus: 1 3 5 7 9 11 13 15
node 1 size: 24563 MB
node 1 free: 23153 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10

3. –membind=nodes, -m nodes: Only allocate memory from nodes.  Allocation will fail when there is not enough memory available on these nodes.  nodes may be  specified  as noted above.

4. –interleave=nodes, -i nodes: Set  a  memory interleave policy. Memory will be allocated using round robin on nodes.  When memory cannot be allocated on the current interleave target fall back to other nodes.  Multiple nodes may be specified on –interleave, –membind and –cpunodebind.  You may specify "all", which  means all nodes in the current cpuset.  nodes may be specified as N,N,N or  N-N or N,N-N or  N-N,N-N and so forth.  Relative nodes may be specifed as +N,N,N or  +N-N or +N,N-N and so forth. The + indicates that the node numbers are relative to  the  process'  set  of  allowed nodes  in  its  current cpuset.  A !N-N notation indicates the inverse of N-N, in other words all nodes except N-N.  If used with + notation, specify !+N-N.

除了上面的四种方式,还可以直接把程序绑定到指定的 node 上:
1. –cpunodebind=nodes, -N nodes: Only execute command on the CPUs of nodes.  Note that nodes may consist of multiple CPUs.  nodes may be specified as noted above.

2. –physcpubind=cpus, -C cpus: Only  execute  process  on cpus.  This accepts physical cpu numbers as shown in the processor fields of /proc/cpuinfo, or relative cpus as in relative to the current cpuset.  You may specify "all", which means all cpus in the current cpuset.  Physical cpus may be specified as  N,N,N or  N-N or N,N-N or  N-N,N-N and so forth.  Relative cpus may be specifed as +N,N,N or  +N-N or +N,N-N and so forth. The + indicates that the cpu numbers are relative to the process' set of allowed cpus in its current cpuset.  A !N-N notation indicates the inverse of N-N,  in  other words all cpus except N-N.  If used with + notation, specify !+N-N.
 
基本上,所有的 db 都是一个进程然后 N 多的线程,MySQL/Oracle/MongoDB 均如此:
$ ps axu | grep mongod | grep -v grep
mongodb  25412 7921 36.1 304482500 23904444 ?  Sl   Jul27 24280183:51 mongod –port xxx –logappend –logpath yyy –dbpath zzz
$ ps -eLf | grep mongod | wc -l
499

在默认的 NUMA 策略下,内存优先分配给 node0,然后是 node1,这个明显不是我们希望看到的,因此,可以使用轮询的策略,也就是 –interleave=all,这样就可以比较公平的在每个 node 上分配内存了。

zone_reclaim_mode 就是用来调整 NUMA 架构下内存回收策略的,为 0 的话就是不做回收,不过在 DB 上并没有多大的效果。而对于 KVM 而言很有必要关闭。真正起作用的还是上面的策略问题。除了策略,还有几点需要注意的事项,请看这里
最后总结起来一是要更新 NUMA 的策略,二是要关闭 zone_reclaim_mode。