
Linux 大页技术
首先简单回顾一下 Linux 的内存映射和页表技术。
物理内存大小是有限的,操作系统为每个程序分配了独立的更大的虚拟内存空间。每个进程的虚拟内存跟物理内存有个映射关系。这个映射保存在页表里,CPU 访问虚拟内存时查页表并转换为物理内存,这个在硬件 MMU 内部完成。
内存映射是以内存页为单位,默认大小是 4KB 。在 64位系统中,Linux 只使用了 64位虚拟地址中的前 48 位,并且分为 5 部分。最后一部分长度为 12 ,对应的是物理内存页内偏移地址(容量 4KB)。前面 4 部分设计主要是降低页表管理成本,不是本文重点。

现在物理内存都很大,4KB 的页表就显得非常小,导致页表项非常多,占用的内存也很大。所以才有更大的内存页出现了。如 2MB 的页表(据说还有支持1GB,没见过不讨论)。此时虚拟内存地址只需要 4 部分,最后一部分长度 21 位,是内存大页内的偏移地址。

大页的好处就是:
减少 TLB(Translation Lookaside Buffer) 的失效情况。
减少 页表 的内存消耗。
减少 PageFault(缺页中断)的次数。
要使用大页(HugePage)需要修改Linux 内核参数 vm.nr_hugepages ,并且应用代码也要支持。技术细节可能是使用 mmap 申请 2MB 对齐的内存映射到匿名的 Hugetlb 文件系统。
Redhat 6 推出了透明大页技术,可以自动根据应用内存申请需求分配出大页。为了满足这个需求,内核尝试申请物理地址连续的内存。如果找不到就会由后台进程 khugepaged 周期性的将零散的小内存页替换为内存大页。内核的这个技术就是透明大页技术(THP),但并不适合数据库场景。所以基本上所有关系数据库都会要求关闭透明大页。
下面我们看一个测试环境。
透明大页示例
默认情况下 Linux 或 CentOS 都会开启透明大页,关闭大页。查看下面文件可以确认这点。并且有内核进程 [khugepaged] 。
[root@OB03 soft]# tree sys/kernel/mm/transparent_hugepage//sys/kernel/mm/transparent_hugepage/├── defrag├── enabled├── khugepaged│ ├── alloc_sleep_millisecs│ ├── defrag│ ├── full_scans│ ├── max_ptes_none│ ├── pages_collapsed│ ├── pages_to_scan│ └── scan_sleep_millisecs└── use_zero_page1 directory, 10 files[root@OB03 soft]# cat sys/kernel/mm/transparent_hugepage/enabled[always] madvise never[root@OB03 soft]# cat sys/kernel/mm/transparent_hugepage/defrag[always] madvise never[root@OB03 soft]# ps -ef | grep khugepaged |grep -v greproot 24101 2 0 06:21 ? 00:00:00 [khugepaged]
当前测试环境是一台 OB 服务器(虚拟机),内存只有 24 GB ,OB 进程 observer 大概使用了 16GB 。
[root@OB03 soft]# cat proc/meminfo | grep AnonHugePagesAnonHugePages: 4298752 kB[root@OB03 soft]# cat proc/meminfo | grep PageTablePageTables: 49576 kB[root@OB03 soft]# free -htotal used free shared buff/cache availableMem: 23G 15G 2.9G 497M 5.1G 2.1GSwap: 0B 0B 0B
从上面查询知 THP 分配的 匿名大页有 4298752 kB(4 GB),页表的大小约 49576 kB(48MB)。
再查看一下 OB 进程 observer 的透明大页使用量,能够看到一部分进程的内存有一部分是用匿名大页。
[root@OB03 soft]# awk '/AnonHugePages/ { if($2>4){print FILENAME " " $0; system("ps -fp " gensub(/.*\/([0-9]+).*/, "\\1", "g", FILENAME))}}' proc/*/smaps/proc/898/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDpolkitd 898 1 0 2022 ? 00:04:01 usr/lib/polkit-1/polkitd --no-debug/proc/898/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDpolkitd 898 1 0 2022 ? 00:04:01 usr/lib/polkit-1/polkitd --no-debug/proc/909/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDroot 909 1 0 2022 ? 00:30:19 usr/sbin/NetworkManager --no-daemon/proc/9629/smaps AnonHugePages: 366592 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:49:29 home/admin/oceanbase/bin/observer/proc/9629/smaps AnonHugePages: 22528 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:49:29 home/admin/oceanbase/bin/observer/proc/9629/smaps AnonHugePages: 88064 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:49:29 home/admin/oceanbase/bin/observer
禁用透明大页 THP
透明大页可以在运行时禁用,这只影响于后续的内存分配。已经分配的匿名大页并不会回收。
[root@OB03 soft]# echo never > /sys/kernel/mm/transparent_hugepage/defrag[root@OB03 soft]# echo never > /sys/kernel/mm/transparent_hugepage/enabled[root@OB03 soft]# cat /sys/kernel/mm/transparent_hugepage/defragalways madvise [never][root@OB03 soft]# cat /sys/kernel/mm/transparent_hugepage/enabledalways madvise [never][root@OB03 soft]# ps -ef | grep khugepaged |grep -v grep[root@OB03 soft]#[root@OB03 soft]# awk '/AnonHugePages/ { if($2>4){print FILENAME " " $0; system("ps -fp " gensub(/.*\/([0-9]+).*/, "\\1", "g", FILENAME))}}' /proc/*/smaps/proc/898/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDpolkitd 898 1 0 2022 ? 00:04:01 /usr/lib/polkit-1/polkitd --no-debug/proc/898/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDpolkitd 898 1 0 2022 ? 00:04:01 /usr/lib/polkit-1/polkitd --no-debug/proc/909/smaps AnonHugePages: 2048 kBUID PID PPID C STIME TTY TIME CMDroot 909 1 0 2022 ? 00:30:19 /usr/sbin/NetworkManager --no-daemon/proc/9629/smaps AnonHugePages: 366592 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:53:29 /home/admin/oceanbase/bin/observer/proc/9629/smaps AnonHugePages: 22528 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:53:29 /home/admin/oceanbase/bin/observer/proc/9629/smaps AnonHugePages: 90112 kBUID PID PPID C STIME TTY TIME CMDadmin 9629 1 99 2022 ? 143-02:53:29 /home/admin/oceanbase/bin/observer
要永久禁用 THP 有两个方法。
方法一:修改开机启动文件
vim + /etc/rc.d/rc.localif test -f /sys/kernel/mm/transparent_hugepage/enabled; thenecho never > /sys/kernel/mm/transparent_hugepage/enabledfiif test -f /sys/kernel/mm/transparent_hugepage/defrag; thenecho never > /sys/kernel/mm/transparent_hugepage/defragfi
修改启动文件权限。
chmod +x /etc/rc.d/rc.local
最后一步很重要,否则有可能不会生效。
方法二:修改
grub.conf
备份 grub.conf 文件。
cp /etc/default/grub /etc/default/grub.`date +%F`.bak
修改 grub.conf 文件。
vim /etc/default/grubGRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet numa=off elevator=deadline transparent_hugepage=never"
执行命令 生成 grub.cfg 配置文件。
grub2-mkconfig
上面修改需要重启主机才生效。不过重启之前还建议同时设置一下大页(HugePage)。
设置大页 HugePage
首先判断一下是否开启了大页。
查看系统的大页使用量
[root@OB03 ~]# grep HugePages /proc/meminfoAnonHugePages: 2048 kBHugePages_Total: 0HugePages_Free: 0HugePages_Rsvd: 0HugePages_Surp: 0
HugePages_Total 为 0 表示没有开启大页。
[root@OB03 ~]# numastat -p `pidof observer`Per-node process memory usage (in MBs) for PID 5034 (observer)Node 0 Node 1 Total--------------- --------------- ---------------Huge 0.00 0.00 0.00Heap 0.00 0.00 0.00Stack 0.06 0.14 0.20Private 6825.28 4455.12 11280.39---------------- --------------- --------------- ---------------Total 6825.34 4455.25 11280.59[root@OB03 ~]#[root@OB03 ~]# grep -B 11 'KernelPageSize: 2048 kB' /proc/`pidof observer`/smaps \| grep "^Size:" \| awk 'BEGIN{sum=0}{sum+=$2}END{print sum/1024}'0
这里使用了 NUMA 的命令 numastat ,可以看到两个 NUMA node 的内存分配信息。在这个测试环境里,默认开启了 NUMA 。通常数据库都是建议关掉 NUMA 。这是另外一个话题,不在这里展开。我们暂时忽略这个。NUMA 只是可能会影响性能,并不会影响大页的使用。
开启大页的方法很简单,就是修改内核参数文件 /etc/sysctl.conf ,指定大页内存大小。由于大页的页大小是 2MB,所以实际大页数量是大页内存总大小除以页大小的值。这里我分配 16GB+ 大页内存。比实际 observer 进程需要的内存稍大一些。
[root@OB03 ~]# vim + /etc/sysctl.confvm.nr_hugepages = 8200
在修改内核参数后不要轻易的运行 sysctl -p 命令生效。首先要判断一下主机的可用内存是否足够,否则可能会导致服务器 Hang。
通常做法是重启服务器。
OB 使用大页示例
再次运行命令 确认操作系统大页设置生效了。
[root@OB03 ~]# grep HugePages /proc/meminfoAnonHugePages: 2048 kBHugePages_Total: 8200HugePages_Free: 8200HugePages_Rsvd: 0HugePages_Surp: 0
HugePages_Total 值大于 0 ,并且是之前的设置值,说明大页生效了。此时 OB 进程还没有启动,所以还没有程序使用大页。这里还看到 AnonHugePages 也有值,其原因是有一个程序还使用了透明大页,由于很小可以忽略不计。
[root@OB03 ~]# free -htotal used free shared buff/cache availableMem: 23G 16G 5.1G 8.9M 1.4G 2.7GSwap: 0B 0B 0B[root@OB03 ~]#
从 free 命令看出,当开启大页后, used 内存就会包含大页的内存,这部分属于专用内存。
前面说了,光操作系统支持大页还不够,还要应用程序支持,也就是看 OB 进程 observer 是否支持大页。从 OB 官网文档看,从企业版 V2.2.50 开始才支持大页,参数名:use_large_pages ,默认值是 false ,即不使用内存大页。

就当前重启的这台服务器而言,OB 进程还没有拉起。实际上应该无法拉起。因为主机大部分内存已经分配给大页了,而 OB 进程默认是不使用内存大页的,所以 OB 进程启动大概率是会报内存不足错误。
下面在命令行下启动 OB 进程,增加参数使用内存大页。
[admin@OB03 ~]$ cd oceanbase/[admin@OB03 oceanbase]$ bin/observer -o "use_large_pages=true"bin/observer -o use_large_pages=trueoptstr: use_large_pages=true[admin@OB03 oceanbase]$ ps -ef|grep observer |grep -v grepadmin 13167 1 99 14:50 ? 00:00:23 bin/observer -o use_large_pages=true[admin@OB03 oceanbase]$
为了观察 OB 对大页内存使用效果,我单独开个窗口对 这个节点上的 OB 租户跑 BenchmarkSQL TPC-C 压测程序。大家可以通过下面观察的结果再跟之前分享的 ORACLE 内存的观察效果进行对比。
首先观察 free 命令的输出。这一块跟之前没有大的变化。OB 的内存计算在 used 内,不在 buff/cache 中。
[root@OB03 ~]# grep HugePages /proc/meminfoAnonHugePages: 2048 kBHugePages_Total: 8200HugePages_Free: 705HugePages_Rsvd: 704HugePages_Surp: 0[root@OB03 ~]# free -htotal used free shared buff/cache availableMem: 23G 17G 4.0G 8.9M 1.6G 1.7GSwap: 0B 0B 0B[root@OB03 ~]#
然后观察 atop 命令的输出。可以看到两个 NUMA node 的内存使用情况。其中字段 hptot 有值就表示开启了大页,字段 hpuse 表示实际大页内存的使用大小,这个随着 OB 业务读写而变化。

再单独观察一下 OB 进程的内存使用情况,这个也是随着 OB 读写活动而变化。
[root@OB03 ~]# numastat -p `pidof observer`Per-node process memory usage (in MBs) for PID 31722 (observer)Node 0 Node 1 Total--------------- --------------- ---------------Huge 7936.00 2124.00 10060.00Heap 0.00 0.00 0.00Stack 0.02 0.17 0.20Private 285.88 746.56 1032.43---------------- --------------- --------------- ---------------Total 8221.90 2870.73 11092.63[root@OB03 ~]#[root@OB03 ~]# grep -B 11 'KernelPageSize: 2048 kB' /proc/`pidof observer`/smaps \> | grep "^Size:" \> | awk 'BEGIN{sum=0}{sum+=$2}END{print sum/1024}'11786
从上面也可以看出开启 NUMA 后,两个 NUMA node 实际分配的内存并不一样。由于 OB 数据库是单进程程序,没必要做 NUMA CPU 绑定,内存的这个行为肯定是会影响 OB 的最大性能。所以 OB 部署也是建议禁用 NUMA 特性(需要从主机 BIOS 里禁用,具体要咨询服务器厂商)。
更多阅读
以上是理论分析加功能测试,学术界此前也有论文(附件4)说大页在 NUMA 架构下可能对性能有害。实际效果如何还是要对比测试看看。
据了解蚂蚁业务 OB 集群使用内存大页的,但是企业版 OCP 部署的 OB 集群好像默认并没有开启使用内存大页的参数。官方文档目前还没有最佳实践,欢迎有用过的朋友留言交流经验。
关于传统数据库内存的分析可以参考下面文章:
Linux HugePages(大内存页) 原理与使用 - 知乎 (zhihu.com) https://zhuanlan.zhihu.com/p/366702339
https://www.usenix.org/system/files/conference/atc14/atc14-paper-gaud.pdf




