暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

社区投稿 | NUMA架构与数据库的一些思考

巨杉数据库 2020-08-05
2885
前言
在很久之前,笔者被约稿希望能够分享一下关于 SequoiaDB 和操作系统 kernel 参数之间的关系,以及官网介绍的 Linux 优化参数的含义和对数据库的作用。去年整理发布过一篇实践分享,详见《常见问题参数调优实践(一)》,但是这个方面的内容一直由于别的事情,被耽误了。近期笔者又被催稿了,所以这次会认真记录一下自己的心得,希望能够为广大“色块er”提供一些有益的帮助。
SequoiaDB 对 NUMA 的建议
通常来说,我们在使用SequoiaDB时,关于 NUMA 架构的设置建议,是直接将 NUMA 特性关闭,以避免操作系统因为 NUMA 的特性而造成数据库有较大的性能影响。关闭 NUMA 特性的设置建议,很多初次接触 SequoiaDB 的用户都没有注意到这个关键的设置。实际上 NUMA 对操作系统的影响是巨大的,今天就让我们来好好针对 NUMA 相关内容进行介绍。
CPU 架构的故事

关于 CPU 的多核架构,常见的有三种主要的架构:SMP、MPP、NUMA,其设计的目的,都是为了能够让服务器或者集群拥有更好的处理性能。

1. SMP
SMP(Symmetric multiprocessing),一般被翻译为“对称多处理”架构,有时候也会被称为“均衡多处理”架构。在现代的服务器中,拥有超过一个处理器,并且所有处理器通过一个系统总线连接资源,每个处理器对资源的使用权限均等。
这种架构最大的优势在于“均等”,在操作系统的支持下,无论进程是处于用户空间,或是核心空间,都可以分配到任何一个处理器上运行。因此,进程可以在不同的处理器间移动,达到负载平衡,使系统的效率提升。
但是其缺点也非常明显,就是由于多处理器都共同使用一个系统总线,而系统总线的带宽是有限的,故服务器的处理器数目受限,且性能受限。

2. MPP

MPP(Massively Parallel Processor),一般被称为“大规模并行处理机”,也是大家日常所说的“并行计算技术”。它的表现形态就是通过多台服务器组成一个物理分布逻辑统一的超强处理器,一个复杂计算被拆分成多个进程在多个服务器上运行,每个进程均拥有独立的资源,多个进程通过消息传递的方式进行互相通信。
MPP 这个名词被 Hadoop 大数据技术所带火,其实它的设计思路非常的朴素,就是因为SMP 架构下的服务器无法集成更多的CPU 算力,所以人们就通过“堆”机器的形式,将集群中总的 CPU 算力增加到一个单服务器无法抗衡的规模,从而取得更加强大的计算能力。

3. NUMA

NUMA(Non-Uniform Memory Access)架构,中文翻译为“非统一内存访问架构”,是站在前人的肩膀上做出的优化设计。它融合了 SMP 和 MPP 架构的特点,能够让服务器提供更加出色的 CPU 计算能力。

随着摩尔定律的持续发挥作用,服务器 CPU 主频越来越高,并且开始往多核架构的方向狂奔。但是在 SMP 架构下,多处理器共用北桥来读取内存的设计,已经成为限制多处理器性能的瓶颈。

天才的设计师们于是就想到:如果将内存访问器也做拆分,问题不就迎刃而解吗?每个处理器分配独立的内存,就不会再有 SMP 架构下受北桥读取内存的性能影响。

NUMA 的影响与优化

1. NUMA 对系统的影响

在 NUMA 架构下,内存是直接 attach 到各个 CPU 上,CPU 访问自身内存资源的性能是访问别的 CPU 的内存资源的三倍。

在这种特性的表现下,操作系统肯定要出来搞事情。
例如在 Linux 操作系统下,如果系统识别到了 NUMA 架构,默认的内存分配方案就是:优先尝试在请求线程当前所处的 CPU 的 Local 内存上分配空间。如果 Local 内存不足,优先淘汰 Local 内存中无用的 Page。
如果数据库运行在 Linux 系统下,结合着 NUMA 的技术特性和 Linux 对内存的分配原则,会对数据库的性能,甚至对操作系统的运行效率都会有直接的影响。在大多数的 Linux 系统版本中,默认设置下 NUMA 架构下对系统的影响
  • NUMA 特性被打开,线程申请内存优先从本 CPU 中获取内存资源;

  • 系统缺少内存动态分配策略,使得在某些 CPU 中出现内存倾斜;

  • 如果某个 CPU 中持续出现严重的内存资源紧张,则会触发系统的 kswapd 服务开始做内存回收操作;

  • 如果内存资源小于最小的 kernel 中的 min_free_kbytes 设置,则还会触发 Linux 系统的 OOM 机制,启动进程 Killer 操作。

2. NUMA 对数据库的影响

NUMA 架构,实际就是系统为了让 CPU 能够尽可能利用高性能的内存资源,而导致内存资源分配不均匀的问题。

NUMA 架构对于数据库的影响,存在以下负面影响:

  • 某些 CPU 单元的内存资源紧张,或者持续触发 kswapd 服务,影响性能;

  • 文件系统缓存不足,导致cache 数据被释放,降低数据库访问时的内存命中率;

  • 整体系统剩余大量的内存,但是数据库却遇到 OOM 错误,甚至被 Killer 强行退出服务的现象;

  • 操作系统出现内存不足问题,导致大量数据被暂时保存于Swap交换空间中,系统响应速度降低,甚至出现卡死现象;

3. NUMA 对内存分配的影响

以 Linux Centos 7.6 为例,如果服务器的 CPU 存在 48 cores,总内存大小为256GB,如果一个 SequoiaDB 的数据节点 PID=175039,在没有对 NUMA 做任何的调整,通过社区中的Jeremy Cole (https://blog.jcole.us/) 提供的 numa-maps-summary.pl 程序  (http://jcole.us/blog/files/numa-maps-summary.pl) 可以看到每个 CPU zone 对内存的分配极其不均衡。

    $> 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 40 42 44 46
    node 0 size: 130976 MB
    node 0 free: 1415 MB
    node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47
    node 1 size: 131072 MB
    node 1 free: 946 MB
    node distances:
    node 0 1
    0: 10 21
    1: 21 10

    $> perl numa-maps-summary.pl < proc/175039/numa_maps
    N0 : 2118702 ( 8.08 GB)
    N1 : 8729319 ( 33.30 GB)
    active : 1327629 ( 5.06 GB)
    anon : 428874 ( 1.64 GB)
    dirty : 428875 ( 1.64 GB)
    kernelpagesize_kB: 8232 ( 0.03 GB)
    mapmax : 906 ( 0.00 GB)
    mapped : 10419213 ( 39.75 GB)

    4. NUMA 架构下优化策略

    资深的 DBA 们,或多或少对 NUMA 架构都有曾经被统治的恐惧,但如果明白了 NUMA 架构下的调优策略,其实可以避免很多不必要的烦恼。在关于 NUMA 和数据库之间的爱恨情仇,网上广泛流传两篇神作,就是 Jeremy Cole 发表的分析 NUMA 架构的作用和影响 (https://blog.jcole.us/2010/09/28/mysql-swap-insanity-and-the-numa-architecture/),以及NUMA架构下对MySQL的调优方式 (https://blog.jcole.us/2012/04/16/a-brief-update-on-numa-and-mysql/)

    针对 NUMA 架构的原理和其对 Linux 的影响,Jeremy Cole 建议:

    1. 使用 numactl --interleave=all CMD 启动数据库服务,以实现数据库进程无视 NUMA 关于 CPU 内存分配的策略,可以使得各个 CPU 区域的内存均匀分配;

    2. 在启动数据库进程前,采用清空操作系统的环境方式,以释放更多的内存资源;

    在 SequoiaDB 信息中心中,对 NUMA 架构的建议,更加简单、直接,“关闭 NUMA 设置”。

    以 Linux Centos 7.6环境为例,如果服务器拥有 CPU 48 cores,总内存大小为256GB,并且关闭了 NUMA 设置,假设 SequoiaDB 的数据节点 PID=243123,通过检查数据节点的内存分配,可以发现服务器的内存资源以一个大内存池形式进行管理和分配。

      $> numactl --hardware
      available: 1 nodes (0)
      node 0 cpus: 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 40 41 42 43 44 45 46 47
      node 0 size: 262044 MB
      node 0 free: 3094 MB
      node distances:
      node 0
      0: 10

      $> perl numa-maps-summary.pl < proc/243123/numa_maps
      N0 : 3238724 ( 12.35 GB)
      active : 381544 ( 1.46 GB)
      anon : 89479 ( 0.34 GB)
      dirty : 89479 ( 0.34 GB)
      kernelpagesize_kB: 1348 ( 0.01 GB)
      mapmax : 1056 ( 0.00 GB)
      mapped : 3149310 ( 12.01 GB)
      所以读者们可以看到,通过关闭 NUMA 设置的方式,就可以有效避免系统对内存资源分配不均匀的问题。
      关闭 NUMA

      1. 检查NUMA设置

      由于 NUMA 的设置是关于多个 CPU 对多个内存区进行非均匀的内存分配,所以如果整个系统默认就只有一个内存区,则无需做调整了。
      以 Linux Centos 7.6 系统为例,通过以下命令可以查看 CPU 和内存的分配情况
        $> numactl --hardware
        available: 1 nodes (0)
        node 0 cpus: 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 40 41 42 43 44 45 46 47
        node 0 size: 262044 MB
        node 0 free: 3094 MB
        node distances:
        node 0
        0: 10

        如果 available = 1 nodes,则证明无需做其他 NUMA 的设置。

        如果出现 available > 1 nodes,则证明存在多个内存区域,需要主动关闭 NUMA 设置。

          $> 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 40 42 44 46
          node 0 size: 130976 MB
          node 0 free: 1415 MB
          node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47
          node 1 size: 131072 MB
          node 1 free: 946 MB
          node distances:
          node 0 1
          0: 10 21
          1: 21 10

          2. 关闭NUMA

          以 Linux Centos 7.6 系统为例,使用 root 用户对 etc/default/grub 文件进行编辑,在  GRUB_CMDLINE_LINUX 参数的末尾增加 :numa=off,例如:

            GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=vg_root/root rd.lvm.lv=vg_root/swap rhgb quiet numa=off"
            • 如果系统中存在/etc/grub2.cfg 文件(MBR 分区表),则执行

              grub2-mkconfig -o etc/grub2.cfg
              • 如果系统中存在/etc/grub2-efi.cfg 文件(EFI + GPT分区表),则执行

                grub2-mkconfig -o etc/grub2-efi.cfg
                • 然后重启服务器,再检查系统的启动日志,可以发现系统已经主动关闭了NUMA的设置。

                  $> grep -i numa var/log/dmesg

                  [ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-3.10.0-693.21.1.el7.x86_64 root=/dev/mapper/vg_root-root ro crashkernel=auto rd.lvm.lv=vg_root/root rd.lvm.lv=vg_root/swap rhgb quiet numa=off
                  [ 0.000000] NUMA turned off
                  [ 0.000000] Kernel command line: BOOT_IMAGE=/vmlinuz-3.10.0-693.21.1.el7.x86_64 root=/dev/mapper/vg_root-root ro crashkernel=auto rd.lvm.lv=vg_root/root rd.lvm.lv=vg_root/swap rhgb quiet numa=off

                  同理,通过 numactl --hardware 命令检查,同样也会显示 available = 1 nodes。

                  后记
                  本次就以 NUMA 架构进行了介绍,希望能够让大家了解关于它更多的知识。笔者也希望各位 DBA 在部署 SequoiaDB 时,千万不要遗漏关闭 NUMA 的设置,否则在复杂的生产环境中,天知道什么时候会遇到 NUMA 的大坑。

                  附录

                    $ cat numa-maps-summary.pl

                    #!/usr/bin/perl

                    # Copyright (c) 2010, Jeremy Cole <jeremy@jcole.us>

                    # This program is free software; you can redistribute it and/or modify it
                    # under the terms of either: the GNU General Public License as published
                    # by the Free Software Foundation; or the Artistic License.
                    #
                    # See http://dev.perl.org/licenses/ for more information.

                    #
                    # This script expects a numa_maps file as input. It is normally run in
                    # the following way:
                    #
                    # # perl numa-maps-summary.pl < proc/pid/numa_maps
                    #
                    # Additionally, it can be used (of course) with saved numa_maps, and it
                    # will also accept numa_maps output with ">" prefixes from an email quote.
                    # It doesn't care what's in the output, it merely summarizes whatever it
                    # finds.
                    #
                    # The output should look something like the following:
                    #
                    # N0 : 7983584 ( 30.45 GB)
                    # N1 : 5440464 ( 20.75 GB)
                    # active : 13406601 ( 51.14 GB)
                    # anon : 13422697 ( 51.20 GB)
                    # dirty : 13407242 ( 51.14 GB)
                    # mapmax : 977 ( 0.00 GB)
                    # mapped : 1377 ( 0.01 GB)
                    # swapcache : 3619780 ( 13.81 GB)
                    #

                    use Data::Dumper;

                    sub parse_numa_maps_line($$)
                    {
                    my ($line, $map) = @_;

                    if($line =~ /^[> ]*([0-9a-fA-F]+) (\S+)(.*)/)
                    {
                    my ($address, $policy, $flags) = ($1, $2, $3);

                    $map->{$address}->{'policy'} = $policy;

                    $flags =~ s/^\s+//g;
                    $flags =~ s/\s+$//g;
                    foreach my $flag (split / , $flags)
                    {
                    my ($key, $value) = split /=/, $flag;
                    $map->{$address}->{'flags'}->{$key} = $value;
                    }
                    }

                    }

                    sub parse_numa_maps()
                    {
                    my ($fd) = @_;
                    my $map = {};

                    while(my $line = <$fd>)
                    {
                    &parse_numa_maps_line($line, $map);

                    }
                    return $map;
                    }

                    my $map = &parse_numa_maps(\*STDIN);


                    my $sums = {};


                    foreach my $address (keys %{$map})
                    {
                    if(exists($map->{$address}->{'flags'}))
                    {
                    my $flags = $map->{$address}->{'flags'};
                    foreach my $flag (keys %{$flags})
                    {
                    next if $flag eq 'file';
                    $sums->{$flag} += $flags->{$flag} if defined $flags->{$flag};
                    }
                    }
                    }


                    foreach my $key (sort keys %{$sums})
                    {
                    printf "%-10s: %12i (%6.2f GB)\n", $key, $sums->{$key}, $sums->{$key}/262144;
                    }

                    往期技术干货
                    社区投稿 | 巨杉数据库对接数仓数据实践与体验
                    分布式数据库的数据备份/恢复,这些你一定要了解

                    社区投稿 | SequoiaDB监控与开发实践分享

                    巨杉Tech | 谈谈数据库内核调优
                    巨杉内核笔记 | MVCC多版本控制原理
                    巨杉内核笔记 | 分布式事务漫谈
                    巨杉内核笔记 | 会话(Session)

                    巨杉Tech | SequoiaDB高可用原理详解

                    巨杉Tech | 分布式数据库负载管理WLM实践

                    巨杉Tech | 巨杉数据库的HTAP场景实践

                    巨杉Tech | SequoiaDB SQL实例高可用负载均衡实践

                    巨杉Tech | 并发性与锁机制解析与实践

                    巨杉Tech | 几分钟实现巨杉数据库容器化部署

                    巨杉Tech | “删库跑路”又出现,如何防范数据安全风险?

                    巨杉Tech | 分布式数据库千亿级超大表优化实践

                    社区分享 | SequoiaDB + JanusGraph 实践

                    巨杉Tech | 巨杉数据库的并发 malloc 实现



                    点击阅读原文,获取更多精彩内容~


                    文章转载自巨杉数据库,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                    评论