本文从NUMA的介绍引出常见的NUMA使用中的陷阱,继而讨论对于NUMA系统的优化方法和一些值得关注的方向。mysql
文章欢迎转载,但转载时请保留本段文字,并置于文章的顶部 做者:卢钧轶(cenalulu) 本文原文地址:http://cenalulu.github.io/linux/numa/linux
NUMA简介
这部分将简要介绍下NUMA架构的成因和具体原理,已经了解的读者能够直接跳到第二节。git
为何要有NUMA
在NUMA架构出现前,CPU欢快的朝着频率愈来愈高的方向发展。受到物理极限的挑战,又转为核数愈来愈多的方向发展。若是每一个core的工做性质都是share-nothing(相似于map-reduce的node节点的做业属性),那么也许就不会有NUMA。因为全部CPU Core都是经过共享一个北桥来读取内存,随着核数如何的发展,北桥在响应时间上的性能瓶颈愈来愈明显。因而,聪明的硬件设计师们,先到了把内存控制器(本来北桥中读取内存的部分)也作个拆分,平分到了每一个die上。因而NUMA就出现了!github
NUMA是什么
NUMA中,虽然内存直接attach在CPU上,可是因为内存被平均分配在了各个die上。只有当CPU访问自身直接attach内存对应的物理地址时,才会有较短的响应时间(后称Local Access
)。而若是须要访问其余CPU attach的内存的数据时,就须要经过inter-connect通道访问,响应时间就相比以前变慢了(后称Remote Access
)。因此NUMA(Non-Uniform Memory Access)就此得名。算法
咱们须要为NUMA作什么
假设你是Linux教父Linus,对于NUMA架构你会作哪些优化?下面这点是显而易见的:sql
既然CPU只有在Local-Access时响应时间才能有保障,那么咱们就尽可能把该CPU所要的数据集中在他local的内存中就OK啦~数据库
没错,事实上Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。若是local内存不足,优先淘汰local内存中无用的Page(Inactive,Unmapped)。 那么,问题来了。。。缓存
NUMA的“七宗罪”
几乎全部的运维都会多多少少被NUMA坑害过,让咱们看看究竟有多少种在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内存回收”有关,而和数据库种类并无直接联系。因此下文咱们就拿MySQL为例,来看看重内存操做应用在NUMA架构下到底会出现什么问题。
MySQL在NUMA架构上会出现的问题
几乎全部NUMA + MySQL
关键字的搜索结果都会指向:Jeremy Cole大神的两篇文章
- The MySQL “swap insanity” problem and the effects of the NUMA architecture
- A brief update on NUMA and MySQL
大神解释的很是详尽,有兴趣的读者能够直接看原文。博主这里作一个简单的总结:
- CPU规模因摩尔定律指数级发展,而总线发展缓慢,致使多核CPU经过一条总线共享内存成为瓶颈
- 因而NUMA出现了,CPU平均划分为若干个Chip(很少于4个),每一个Chip有本身的内存控制器及内存插槽
- CPU访问本身Chip上所插的内存时速度快,而访问其余CPU所关联的内存(下文称Remote Access)的速度相较慢三倍左右
- 因而Linux内核默认使用CPU亲和的内存分配策略,使内存页尽量的和调用线程处在同一个Core/Chip中
- 因为内存页没有动态调整策略,使得大部份内存页都集中在
CPU 0
上 - 又由于
Reclaim
默认策略优先淘汰/Swap本Chip上的内存,使得大量有用内存被换出 - 当被换出页被访问时问题就以数据库响应时间飙高甚至阻塞的形式出现了
解决方案
Jeremy Cole大神推荐的三个方案以下,若是想详细了解能够阅读 原文
numactl --interleave=all
- 在MySQL进程启动前,使用
sysctl -q -w vm.drop_caches=3
清空文件缓存所占用的空间 - Innodb在启动时,就完成整个
Innodb_buffer_pool_size
的内存分配
这三个方案也被业界广泛承认可行,同时在 Twitter 的5.5patch 和 Percona 5.5 Improved NUMA Support 中做为功能被支持。
不过这种三合一的解决方案只是减小了NUMA内存分配不均,致使的MySQL SWAP问题出现的可能性。若是当系统上其余进程,或者MySQL自己须要大量内存时,Innodb Buffer Pool的那些Page一样仍是会被Swap到存储上。因而又在这基础上出现了另外几个进阶方案
- 配置
vm.zone_reclaim_mode = 0
使得内存不足时去remote memory分配
优先于swap out local page
echo -15 > /proc/<pid_of_mysqld>/oom_adj
调低MySQL进程被OOM_killer
强制Kill的可能- memlock
- 对MySQL使用Huge Page(黑魔法,巧用了Huge Page不会被swap的特性)
从新审视问题
若是本文写到这里就这么结束了,那和搜索引擎结果中大量的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核数再也不是一个梦想。
- Percona NUMA aware Configuration
- Numa system performance issues – more than just swapping to consider
- MySQL Server and NUMA architectures
- Checking /proc/pid/numa_maps can be dangerous for mysql client connections
- on swapping and kernels
- Optimizing Linux Memory Management for Low-latency / High-throughput Databases
- Memory System Performance in a NUMA Multicore Multiprocessor
- A Case for NUMA-aware Contention Management on Multicore Systems