一、背景
MongoDB日志报警显示如下:
1 2 3 |
WARNING: You are running on a NUMA machine. We suggest launching mongod like this to avoid performance problems: numactl --interleave=all mongod [other options] |
和NUMA报警可能会同时出现的报警:
1 2 3 |
WARNING: /proc/sys/vm/zone_reclaim_mode is 1 We suggest setting it to 0 http://www.kernel.org/doc/Documentation/sysctl/vm.txt |
解决方案也很简单:
1)在原启动命令前面加numactl指令
1 |
$ numactl --interleave=all |
由于目前MongoDB在numa架构下工作的不是很好,而--interleave=all
就是禁用NUMA为每个核单独分配内存的机制,改用交叉共享内存分配模式。
2)修改内核参数
1 |
$ echo 0 > /proc/sys/vm/zone_reclaim_mode |
zone_reclaim_mode这是一个内核参数,意义如下:
当某个节点可用内存不足时,如果为0的话,那么系统会倾向于从其他节点分配内存。如果为1的话,那么系统会倾向于从本地节点回收Cache内存多数时候。而Cache对性能很重要,所以0是一个更好的选择。(官方参考:http://www.kernel.org/doc/Documentation/sysctl/vm.txt)
不光是MongoDB有这个问题,就连MySQL、Oracle、Tomcat等都有这个问题。所以不光要知道怎么去解决,也要了解NUMA是什么?以及这样设置带来的好处和坏处,下面就开始进入正文了,网上摘的文章。
参考:官方文档
二、服务器三大体系:SMP、NUMA、MPP
从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构(SMP:Symmetric Multi-Processor),非一致存储访问结构(NUMA:Non-Uniform Memory Access),以及海量并行处理结构(MPP:Massive Parallel Processing)。
随着科学计算、事务处理对计算机性能要求的不断提高,SMP(对称多处理器)系统的应用越来越广泛,规模也越来越大,但由于传统的SMP系统中,所有处理器都共享系统总线,因此当处理器的数目增大时,系统总线的竞争冲突加大,系统总线将成为瓶颈,所以目前SMP系统的CPU数目一般只有数十个,可扩展能力受到极大限制。NUMA技术有效结合了SMP系统易编程性和MPP(大规模并行)系统易扩展性的特点,较好解决了SMP系统的可扩展性问题,已成为当今高性能服务器的主流体系结构之一。目前国外著名的服务器厂商都先后推出了基于NUMA架构的高性能服务器,如HP的Superdome、SGI的Altix 3000、IBM的 x440、NEC的TX7、AMD的Opteron等。随着Linux在服务器平台上的表现越来越成熟,Linux内核对NUMA架构的支持也越来越完善,特别是从2.5开始,Linux在调度器、存储管理、用户级API等方面进行了大量的NUMA优化工作,目前这部分工作还在不断地改进,如新近推出的2.6.7-RC1内核中增加了NUMA调度器。
下面分别介绍一下这几种架构。
SMP(Symmetric Multi-Processor)
CPU欢快的朝着频率越来越高的方向发展。受到物理极限的挑战,又转为核数越来越多的方向发展。所谓对称多处理器结构,是指服务器中多个CPU对称工作,无主次或从属关系。各CPU共享相同的物理内存,每个CPU访问内存中的任何地址所需时间是相同的,因此SMP也被称为一致存储器访问结构(UMA:Uniform Memory Access)。对SMP服务器进行扩展的方式包括增加内存、使用更快的CPU、增加CPU、扩充I/O(槽口数与总线数)以及添加更多的外部设备(通常是磁盘存储)。
SMP服务器的主要特征是共享,系统中所有资源(CPU、内存、I/O等)都是共享的。也正是由于这种特征,导致了SMP服务器的主要问题,那就是它的扩展能力非常有限。对于SMP服务器而言,每一个共享的环节都可能造成SMP服务器扩展时的瓶颈,而最受限制的则是内存。由于每个CPU必须通过相同的内存总线访问相同的内存资源,因此随着CPU数量的增加,内存访问冲突将迅速增加,最终会造成CPU资源的浪费,使CPU性能的有效性大大降低。实验证明,SMP服务器CPU利用率最好的情况是2至4个CPU。
下图是SMP服务器CPU利用率状态:
NUMA(Non-Uniform Memory Access)
由于SMP在扩展能力上的限制,人们开始探究如何进行有效地扩展从而构建大型系统的技术,NUMA就是这种努力下的结果之一。在SMP架构下由于所有CPU Core都是通过共享一个北桥来读取内存,随着核数的发展,北桥在响应时间上的性能瓶颈越来越明显(CPU规模因摩尔定律指数级发展,而总线发展缓慢,导致多核CPU通过一条总线共享内存成为瓶颈)。于是,聪明的硬件设计师们,先到了把内存控制器(原本北桥中读取内存的部分)也做个拆分,平分到了每个内存控制器上。于是NUMA就出现了!
利用NUMA技术,可以把几十个CPU(甚至上百个CPU)组合在一个服务器内。其CPU模块结构如下图所示:
NUMA服务器的基本特征是具有多个CPU模块(不多于4个),每个模块有自己的内存控制器及内存插槽,并且具有独立的本地内存、I/O槽口等。由于其节点之间可以通过互联模块(如称为Crossbar Switch)进行连接和信息交互,因此每个CPU可以访问整个系统的内存(这是NUMA系统与MPP系统的重要差别)。显然,访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问NUMA的由来。CPU访问自己模块上所插的内存时速度快,而访问其他CPU所关联的内存(称Remote Access)的速度相较慢三倍左右。
由于这个特点,为了更好地发挥系统性能,开发应用程序时需要尽量减少不同CPU模块之间的信息交互。利用NUMA技术,可以较好地解决原来SMP系统的扩展问题,在一个物理服务器内可以支持上百个CPU。比较典型的NUMA服务器的例子包括HP的Superdome、SUN15K、IBMp690等。
但NUMA技术同样有一定缺陷,由于访问远地内存的延时远远超过本地内存,因此当CPU数量增加时,系统性能无法线性增加。如HP公司发布Superdome服务器时,曾公布了它与HP其它UNIX服务器的相对性能值,结果发现,64路CPU的Superdome(NUMA结构)的相对性能值是20,而8路N4000(共享的SMP结构)的相对性能值是6.3。从这个结果可以看到,8倍数量的CPU换来的只是3倍性能的提升。
我们需要为NUMA做什么?
假设你是Linux教父Linus,对于NUMA架构你会做哪些优化?下面这点是显而易见的:
- 既然CPU只有在Local-Access时响应时间才能有保障,那么我们就尽量把该CPU所要的数据集中在他local的内存中就OK啦~
没错,事实上Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。如果local内存不足,优先淘汰local内存中无用的Page(Inactive,Unmapped)。那么,问题来了。。。
NUMA的“七宗罪”
MySQL – The MySQL “swap insanity” problem and the effects of the NUMA architecture
PostgreSQL – PostgreSQL, NUMA and zone reclaim mode on linux
Oracle – Non-Uniform Memory Access (NUMA) architecture with Oracle database by examples
Java – Optimizing Linux Memory Management for Low-latency / High-throughput Databases
究其原因几乎都和:“因为CPU亲和策略导致的内存分配不平均”及“NUMA Zone Claim内存回收”有关,而和数据库种类并没有直接联系。
MPP(Massive Parallel Processing)
和NUMA不同,MPP提供了另外一种进行系统扩展的方式,它由多个SMP服务器通过一定的节点互联网络进行连接,协同工作,完成相同的任务,从用户的角度来看是一个服务器系统。其基本特征是由多个SMP服务器(每个SMP服务器称节点)通过节点互联网络连接而成,每个节点只访问自己的本地资源(内存、存储等),是一种完全无共享(Share Nothing)结构,因而扩展能力最好,理论上其扩展无限制,目前的技术可实现512个节点互联,数千个CPU。目前业界对节点互联网络暂无标准,如NCR的Bynet,IBM的SPSwitch,它们都采用了不同的内部实现机制。但节点互联网仅供MPP服务器内部使用,对用户而言是透明的。
在MPP系统中,每个SMP节点也可以运行自己的操作系统、数据库等。但和NUMA不同的是,它不存在异地内存访问的问题。换言之,每个节点内的CPU不能访问另一个节点的内存。节点之间的信息交互是通过节点互联网络实现的,这个过程一般称为数据重分配(Data Redistribution)。
但是MPP服务器需要一种复杂的机制来调度和平衡各个节点的负载和并行处理过程。目前一些基于MPP技术的服务器往往通过系统级软件(如数据库)来屏蔽这种复杂性。举例来说,NCR的Teradata就是基于MPP技术的一个关系数据库软件,基于此数据库来开发应用时,不管后台服务器由多少个节点组成,开发人员所面对的都是同一个数据库系统,而不需要考虑如何调度其中某几个节点的负载。
NUMA与MPP的区别?
从架构来看,NUMA与MPP具有许多相似之处:它们都由多个节点组成,每个节点都具有自己的CPU、内存、I/O,节点之间都可以通过节点互联机制进行信息交互。那么它们的区别在哪里?通过分析下面NUMA和MPP服务器的内部架构和工作原理不难发现其差异所在。
首先是节点互联机制不同,NUMA的节点互联机制是在同一个物理服务器内部实现的,当某个CPU需要进行远地内存访问时,它必须等待,这也是NUMA服务器无法实现CPU增加时性能线性扩展的主要原因。而MPP的节点互联机制是在不同的SMP服务器外部通过I/O 实现的,每个节点只访问本地内存和存储,节点之间的信息交互与节点本身的处理是并行进行的。因此MPP在增加节点时性能基本上可以实现线性扩展。其次是内存访问机制不同。在NUMA服务器内部,任何一个CPU可以访问整个系统的内存,但远地访问的性能远远低于本地内存访问,因此在开发应用程序时应该尽量避免远地内存访问。在MPP服务器中,每个节点只访问本地内存,不存在远地内存访问的问题。
从NUMA架构来看,它可以在一个物理服务器内集成许多CPU,使系统具有较高的事务处理能力,由于远地内存访问时延远长于本地内存访问,因此需要尽量减少不同CPU模块之间的数据交互。显然,NUMA架构更适用于OLTP事务处理环境,当用于数据仓库环境时,由于大量复杂的数据处理必然导致大量的数据交互,将使CPU的利用率大大降低。相对而言,MPP服务器架构的并行处理能力更优越,更适合于复杂的数据综合分析与处理环境。
三、NUMA分配策略
NUMA最大的特点是引入了node和distance的概念。对于CPU和内存这两种最宝贵的硬件资源,NUMA用近乎严格的方式划分了所属的资源组(node),而每个资源组内的CPU和内存是几乎相等。资源组的数量取决于物理CPU的个数(现有的PC server大多数有两个物理CPU,每个CPU有4个核);distance这个概念是用来定义各个node之间调用资源的开销,为资源调度优化算法提供数据支持。
系统中的每个进程(或线程)都会从父进程继承NUMA策略,并分配有一个优先node。如果NUMA策略允许的话,进程可以调用其他node上的资源。
NUMA的CPU分配策略有:cpunodebind、physcpubind两种
- cpunodebind:规定进程运行在某几个node之上。
- physcpubind:可以更加精细地规定运行在哪些CPU核上。
NUMA的内存分配策略有:localalloc、preferred、membind、interleave四种
- localalloc(默认):规定进程从当前node上请求分配内存(当前进程运行的节点上,进程会一直在各个核心上流动)。
- preferred(优先):比较宽松地指定了一个推荐的node来获取内存,如果被推荐的node上没有足够内存,进程可以尝试别的node。
- membind(绑定):可以指定若干个node,进程只能从这些指定的node上请求分配内存。
- interleave(交叉):规定进程从指定的若干个node上以RR算法交织地请求分配内存。
四、NUMA与MySQL swap
可能大家已经发现了,NUMA的默认内存分配策略对于进程(或线程)之间来说,并不是公平的。在现有的Linux中,localalloc(从当前node上请求分配内存)是默认的NUMA内存分配策略,这个配置选项导致资源独占程序很容易将某个node的内存用尽,而当某个node的内存耗尽时,这时swap就妥妥地产生了。尽管此时还有很多page cache可以释放,甚至还有很多的free内存;而在Linux中,Reclaim默认策略优先淘汰/Swap本模块上的内存,使得大量有用内存被换出,当被换出页被访问时问题就以数据库响应时间飙高甚至阻塞的形式出现了。
MySQL是单进程多线程架构数据库,当NUMA采用默认内存分配策略时,MySQL进程会被并且仅仅会被分配到NUMA的一个节点上去。假设这个节点的local内存为1GB,而MySQL配置2GB内存,超出节点本地内存部分时,Linux会使用SWAP而不是其他节点的物理内存,在这种情况下,能观察到虽然系统总的可用内存还未用完,但是MySQL进程已经开始使用SWAP了。
虽然NUMA的原理相对复杂,实际上解决swap却很简单:只要在启动MongoDB&MySQL之前使用numactl --interleave
来修改NUMA策略即可。值得注意的是,numactl这个命令不仅仅可以调整NUMA策略,也可以用来查看当前各个node的资源是用情况,是一个很值得研究的命令。
五、NUMA基本操作
1. 查看是否支持NUMA架构
1 2 3 4 |
$ dmesg | grep -i numa NUMA: Using 31 for the hash shift. pci_bus 0000:00: on NUMA node 0 (pxm 0) pci_bus 0000:80: on NUMA node 1 (pxm 1) |
2. 查看NUMA具体信息
1 2 3 4 5 6 7 8 |
$ numastat node0 node1 numa_hit 441775596 510835045 numa_miss 0 0 numa_foreign 0 0 interleave_hit 1222622 1291318 local_node 440914687 510702522 other_node 860909 132523 |
numa_hit:本来该在此节点上分配内存,最后也确实从这个节点分配内存的次数。
num_miss:本来该在此节点上分配内存,最后却从其他节点分配内存的次数。
num_foregin:本来该在其他节点分配内存,最后却从这个此节点分配内存的次数。
interleave_hit:是采用interleave策略最后从该节点分配的次数。
local_node:该节点上的进程在该节点上分配的次数。
other_node:是其他节点进程在该节点上分配的次数。
3. lscpu命令可以看到两个node的cpu归属
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 40 On-line CPU(s) list: 0-39 Thread(s) per core: 2 Core(s) per socket: 10 Socket(s): 2 NUMA node(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 79 Stepping: 1 CPU MHz: 2200.060 BogoMIPS: 4399.36 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 25600K NUMA node0 CPU(s): 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38 NUMA node1 CPU(s): 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39 |
4. numactl --hardware
命令则会返回不同节点的内存总大小,可用大小,以及node distance等信息
1 2 3 4 5 6 7 8 9 10 11 12 |
$ numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 node 0 size: 32674 MB node 0 free: 22792 MB node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 node 1 size: 32768 MB node 1 free: 27667 MB node distances: node 0 1 0: 10 21 1: 21 10 |
5. 查看各个CPU负载情况,使用命令:mpstat -P ALL(需要安装sysstat包)
1 |
$ mpstat -P ALL |
6. 查看某个进程使用某个CPU,使用命令:top、htop、ps等
1 2 3 |
$ ps -o pid,psr,comm -p 20216 PID PSR COMMAND 20216 11 mysqld |
输出表示进程的PID为20216(名为”mysqld”),目前在CPU内核1上运行着。如果该过程没有被固定,PSR列会根据内核可能调度该进程到不同内核而改变显示,是动态的。
7. 查看NUMA基本信息
1 2 3 4 5 6 7 |
$ numactl -s policy: default preferred node: current physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 cpubind: 0 1 nodebind: 0 1 membind: 0 1 |
Linux上使用numactl设定进程的numa策略。常见的情况是,数据库Daemon进程(MongoDB,MySQL)可能会吃掉很多内存,而一个numa节点上的内存很有限,内存不够时虚拟内存频繁与硬盘交换数据,导致性能急剧下降(标识是irqbalance进程top中居高不下),这时应该采用interleave的numa策略,允许从其他节点分配内存。
各个内存的访问延迟如何呢?在numactl man中的example提供了参考,我在服务器上测了一下(40核,64G):
写速度:
1 2 3 4 5 6 7 8 |
$ numactl --cpubind=0 --membind=0 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB) copied, 0.511139 s, 2.1 GB/s $ numactl --cpubind=0 --membind=1 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB) copied, 0.600305 s, 1.8 GB/s |
六、重新审视问题
如果本文写到这里就这么结束了,那和搜索引擎结果中大量的Step-by-Step科普帖没什么差别。虽然我们用了各种参数调整减少了问题发生概率,那么真的就彻底解决了这个问题么?问题根源究竟是什么?让我们回过头来重新审视下这个问题:
NUMA Interleave真的好么?为什么Interleave的策略就解决了问题?
借用两张Carrefour性能测试的结果图,可以看到几乎所有情况下Interleave模式下的程序性能都要比默认的亲和模式要高,有时甚至能高达30%。究其根本原因是Linux服务器的大多数workload分布都是随机的:即每个线程在处理各个外部请求对应的逻辑时,所需要访问的内存是在物理上随机分布的。而Interleave模式就恰恰是针对这种特性将内存page随机打散到各个CPU Core上,使得每个CPU的负载和Remote Access的出现频率都均匀分布。相较NUMA默认的内存分配模式,死板的把内存都优先分配在线程所在Core上的做法,显然普遍适用性要强很多。
也就是说,像MySQL这种外部请求随机性强,各个线程访问内存在地址上平均分布的这种应用,Interleave的内存分配模式相较默认模式可以带来一定程度的性能提升。
此外各种论文中也都通过实验证实,真正造成程序在NUMA系统上性能瓶颈的并不是Remote Acess带来的响应时间损耗,而是内存的不合理分布导致Remote Access将inter-connect这个小水管塞满所造成的结果。而Interleave恰好,把这种不合理分布情况下的Remote Access请求平均分布在了各个小水管中。所以这也是Interleave效果奇佳的一个原因。
那是不是简简单单的配置个Interleave就已经把NUMA的特性和性能发挥到了极致呢?
答案是否定的,目前Linux的内存分配机制在NUMA架构的CPU上还有一定的改进空间。例如:Dynamic Memory Loaction, Page Replication。
Dynamic Memory Relocation
我们来想一下这个情况:MySQL的线程分为两种,用户线程(SQL执行线程)和内部线程(内部功能,如:flush,io,master等)。对于用户线程来说随机性相当的强,但对于内部线程来说他们的行为以及所要访问的内存区域其实是相对固定且可以预测的。如果能对于这把这部分内存集中到这些内存线程所在的core上的时候,就能减少大量Remote Access,潜在的提升例如Page Flush,Purge等功能的吞吐量,甚至可以提高MySQL Crash后Recovery的速度(由于recovery是单线程)。
那是否能在Interleave模式下,把那些明显应该聚集在一个CPU上的内存集中在一起呢?
很可惜,Dynamic Memory Relocation这种技术目前只停留在理论和实验阶段。我们来看下难点:要做到按照线程的行为动态的调整page在memory的分布,就势必需要做线程和内存的实时监控(profile)。对于Memory Access这种非常异常频繁的底层操作来说增加profile入口的性能损耗是极大的。在 关于CPU Cache程序应该知道的那些事的评论中我也提到过,这个道理和为什么Linux没有全局监控CPU L1/L2 Cache命中率工具的原因是一样的。当然优化不会就此停步。上文提到的Carrefour算法和Linux社区的Auto NUMA patch都是积极的尝试。什么时候内存profile出现硬件级别,类似于CPU中PMU 的功能时,动态内存规划就会展现很大的价值,甚至会作为Linux Kernel的一个内部功能来实现。到那时我们再回过头来审视这个方案的实际价值。
Page Replication
再来看一下这些情况:一些动态加载的库,把他们放在任何一个线程所在的CPU都会导致其他CPU上线程的执行效率下降。而这些共享数据往往读写比非常高,如果能把这些数据的副本在每个Memory Zone内都放置一份,理论上会带来较大的性能提升,同时也减少在inter-connect上出现的瓶颈。实时上,仍然是上文提到的Carrefour也做了这样的尝试。由于缺乏硬件级别(如MESI协议的硬件支持)和操作系统原生级别的支持,Page Replication在数据一致性上维护的成本显得比他带来的提升更多。因此这种尝试也仅仅停留在理论阶段。当然,如果能得到底层的大力支持,相信这个方案还是有极大的实际价值的。
究竟是哪里出了问题?NUMA的问题?
NUMA本身没有错,是CPU发展的一种必然趋势。但是NUMA的出现使得操作系统不得不关注内存访问速度不平均的问题。
Linux Kernel内存分配策略的问题?
分配策略的初衷是好的,为了内存更接近需要他的线程,但是没有考虑到数据库这种大规模内存使用的应用场景。同时缺乏动态调整的功能,使得这种悲剧在内存分配的那一刻就被买下了伏笔。
数据库设计者不懂NUMA?
数据库设计者也许从一开始就不会意识到NUMA的流行,或者甚至说提供一个透明稳定的内存访问是操作系统最基本的职责。那么在现状改变非常困难的情况下(下文会提到为什么困难)是不是作为内存使用者有义务更好的去理解使用NUMA?
七、总结
其实无论是NUMA还是Linux Kernel,亦或是程序开发他们都没有错,只是还做得不够极致。如果NUMA在硬件级别可以提供更多低成本的profile接口;如果Linux Kernel可以使用更科学的动态调整策略;如果程序开发人员更懂NUMA,那么我们完全可以更好的发挥NUMA的性能,使得无限横向扩展CPU核数不再是一个梦想。
<相关文章地址>
http://cenalulu.github.io/linux/numa/
http://www.ibm.com/developerworks/cn/linux/l-numa/