几年前我曾经写过一篇关于数据库与NUMA的文章,主要是当时有DBA问我是不是在LINUX上关闭了NUMA,就不会有NUMA引发的性能问题了。当时我用UMA和NUMA架构的两张图来说明其实不完全如此。
早期的SMP架构的服务器CPU和内存配置都不多,因此CPU通过总线访问内存就可以了,随着硬件的发展,内存从MB级别已经达到了TB级别,而CPU也从单颗CPU1-2核变成了数十核,甚至上百核的庞然大物。因此传统的UMA中的总线就成为瓶颈了。因此NUMA架构开始兴起。
NUMA架构中,CPU通过高性能的内存通道与内存相连,而不再通过系统总线。每个CPU上都有内存控制单元和数个内存通道,CPU通过内存管理单元来通过QPI总线访问本地内存。这种架构的好处是CPU访问内存的性能得到了保证,坏处是当系统中存在多颗CPU的时候,内存就分为了本地内存与远程内存两种。比如CPU0访问Memory0-3的时候,可以直接访问,性能最 佳,而如果要访问Memory4-7的时候,就需要通过总线,由CPU1代理访问,其效率就不如直接访问本地内存了。
可以看出上面的这个例子中一共有2个NUMA节点,每个NUMA节点管理了32GB的内存。node distances是距离单位,这个值并不是一个具体指,数值越小说明内存离CPU的距离越近,访问性能越好。对于INTEL CPU来说,本地内存与远程内存的距离大约是2.1倍,访问性能差不多也是接近2倍的差距。在一个ARM服务器中,这个距离差距可能是5倍,甚至更高。因此不管ARM服务器的CPU性能有多强,在跑一些数据库之类的负载的时候,似乎会不如看上去跑分还没有自己强的INTEL CPU了。实际上数据库负载与内存、存储系统的访问性能关系很大,不仅仅和CPU自身的性能指标相关。
另外要注意的一点是远程内存与本地内存的访问性能差异是一个物理限制,而不是逻辑限制,因此这个问题并不会因为在操作系统启动时关闭NUMA而得到改善的。
讨论完上面的预备知识,下面就要谈谈NUMA架构会如何影响数据库了。首先一个问题就是有时候明明看到物理内存还有不少空闲,但是SWAP已经使用了不少了。这时候我们可以通过numactl -H来看看是不是多个NUMA节点上的内存分配不均衡,有的NUMA节点上还有很多空闲内存,而有些NUMA节点上的内存已经耗尽了。zone_reclaim_mode、overcommit_memory等操作系统参数的设置也会影响多个NUMA节点上内存的分配策略。
为了避免这种 情况出现,很多数据库厂商的服务人员都会建议关闭SWAP,从而避免在物理内存还有足够空闲的时候出现不必要的换页,从而影响数据库的性能。达梦、Oceanbase等数据库厂商都在自己的官方建议里提出过此类建议。不过我是不太建议不分青红皂白关闭SWAP的,因为关闭SWAP虽然可以避免因为不必要的换页引发的性能问题,但是可能会因为真正内存不足使导致更为严重的问题,比如因为物理内存确实不够用了而导致数据库实例直接被操作系统oom killer杀掉,导致数据库宕机。针对达梦、Oceanbase这样的单进程多线程架构的数据库,一旦出现物理内存不足,数据库服务被清理的概率肯定是最大的。
要想避免这类问题,数据库实例启动后,应该调整oom_score参数的值,比如在LINUX 3.1以上的环境中,直接设置为-1000,这样oom killer在找需要释放内存的进程的时候,会避开数据库服务进程。
上述的解决方案能解决关闭SWAP后的大部分内存不足引发的数据库实例宕机问题,不过并不是能解决所有问题的。如果内存缺得厉害了,系统可能会因为物理内存周转不足而引发十分严重的性能问题,甚至导致OS僵死,重启等。因此是否关闭SWAP是一个需要综合考虑的问题。其实如果不关闭SWAP,在操作系统启动时关闭NUMA就可以避免此类问题,关闭NUMA后,操作系统将不对内存分区,内存分配是全局性的。因此在大多数场景中,我是建议不关闭SWAP,而是在操作系统启动时关闭NUMA。
有朋友看到这里会说,老白,你上来直接就说这种配置就行了,有必要洋洋洒洒写一千多字才抛出这个小技巧吗?实际上问题还没有彻底解决。关闭NUMA只是解决了内存分区导致的不必要的SWAP问题。本地内存与远程内存的访问效率问题还是没有解决,这个问题实际上是解决不了的。要想解决,必须在数据库内核上去解决。目前已经有一些数据库在针对NUMA进行优化了,包括Oracle在内。很多国产数据库也把基于NUMA优化当成一个自己的亮点。
数据库基于NUMA优化实际上目的也是尽可能使用本地内存这点思路。进程或者线程启动后绑定到某个CPU组中,并且尽可能优先使用本CPU组的本地内存,从而提高数据库的整体性能。对于INTEL CPU的服务器来说,远程内存性能问题还不是很严重,不过如果我们要使用某些信创CPU,那么这个差异可能对性能的影响就很大了。因此说数据库针对NUMA做核心优化还是很必要的。只不过目前的绝大多数国产数据库在针对NUMA内存的优化上都只是做了些皮毛,对跑BENCHMARK可能比较有效,但是针对实际的应用场景来说,效果甚微。这是因为数据库内存中,最需要提升性能的是共享内存的访问性能,而这部分内存针对NUMA架构的优化技术难度还是很高的。