HugePage:为数据库启用大页支持

如何为 PostgreSQL 集群分配精准的大页面?

内存大页的优缺点

对于数据库来说,启用大页有好处,也有缺点。

  • OLAP 场景下的显著性能收益:大数据量扫描与批量计算
  • 更可控的内存分配模型:启动时“锁定”需要的内存
  • 提升内存访问效率,减少 TLB miss
  • 降低内核页表维护开销

但也伴随着一些缺点:

  • 额外的配置与维护复杂度
  • 大页内存被锁定,对系统整体资源弹性要求高的环境来说缺乏灵活性
  • 小规模内存场景收益有限,甚至会适得其反

请注意,HugePage 和 Transparent HugePage (透明大页)是两个不同的概念, Pigsty 会强制关闭 Transparent HugePage 以遵循数据库最佳实践。


什么时候启用大页?

如果你的场景满足以下条件,我们建议启用大页:

  • OLAP 分析场景
  • 超过 几十GB 的内存
  • PostgreSQL 15+
  • Linux 内核版本 > 3.10 (> EL7, > Ubuntu 16)

Pigsty 默认不启用大页,但你可以通过简单的配置启用,并配置为 PostgreSQL 专属的内存。


分配节点大页

要为节点启用大页面,用户可以使用以下两个参数:

这两个参数二选一,你可以直接指定要分配的(2MB)大页数量,或指定分配为大页的内存比例(0.00 - 0.90 ),前者具有更高优先级。

node_hugepage_count: 0            # 精确指定 2MB 大页面数量,优先级要高于 node_hugepage_ratio
node_hugepage_ratio: 0            # 分配为 2MB 大页面的内存比例,优先级要低于 node_hugepage_count

应用生效:

./node.yml -t node_tune

本质上是在:/etc/sysctl.d/hugepage.conf 中写入了 vm.nr_hugepages 参数值并执行了 sysctl -p 应用生效。

./node.yml -t node_tune -e node_hugepage_count=3000    # 精确分配 5000 个 2MB 大页(10GB)
./node.yml -t node_tune -e node_hugepage_ratio=0.30    # 以大页形式分配 30% 的内存

请注意,以上参数只是为节点启用大页,不仅仅是 PostgreSQL 可以使用。

PostgreSQL 服务器默认会在启动时尝试使用大页,如果系统中可用的大页数量不足,PostgreSQL 会继续使用普通页面启动。

如果你尝试降低大页数量,只有未被使用与保留的大页(Free)会被释放,已经被使用的大页会在进程退出后释放。

Pigsty 最多允许分配 90% 的内存作为大页,但对于 PostgreSQL 数据库来说,合理的范围通常在 25% - 40% 的内存。

建议用户设置:node_hugepage_ratio=0.30,并在 PostgreSQL 启动后按需进一步调整大页数量。


查看大页状态

最直观的查看方法是使用 Pigsty 监控系统,这里给出了调整大页时的一个监控图表样例:

  1. 默认状态
  2. 启用大页,未使用
  3. 重启 PG ,使用/保留了一部分大页
  4. 进一步使用 PG,使用了更多大页
  5. 缩减大页数量,回收未使用的大页
  6. 重启 PG,彻底释放保留的大页

你可以直接 cat /proc/meminfo | grep Huge 查看大页状态。

$ cat /proc/meminfo  | grep Huge

默认情况下,没有启用大页面,大页面数量(Total)为 0:

AnonHugePages:      8192 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB

启用了大页面,总共有 6015 个大页面,全部空闲可用:

AnonHugePages:      8192 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:    6015
HugePages_Free:     6015
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:        12318720 kB

如果这时候重启 PostgreSQL (默认会尝试使用大页)

sudo su - postgres
pg-restart

那么 PostgreSQL 会使用 保留预定 (Rsvd,Reserved)所需的大页,用于共享缓冲区,例如这里保留了 5040 个。

AnonHugePages:      8192 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:    6015
HugePages_Free:     5887
HugePages_Rsvd:     5040
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:        12318720 kB

如果我们给 PostgreSQL 增加一些负载,比如 pgbench -is10 ,那么 PostgreSQL 会开始使用更多大页(Alloc = Total - Free)。

请注意,大页一旦被(分配或者预定),即使你将系统的 vm.nr_hugepages 参数调小,这些页面也依然会被保留,直到使用完毕。 因此,如果你想要真正回收这些大页,需要重启 PostgreSQL 服务。

./node.yml -t node_tune -e node_hugepage_count=3000    # 分配 3000 大页

精准分配大页

在 PostgreSQL 启动前,您需要分配 足够多的 大页,否则 PostgreSQL 将无法使用这些大页。

在 Pigsty 中,默认使用的 SharedBuffer 不超过内存的 25% ,所以您可以分配 26% ~ 27% 的内存作为大页,以确保 PostgreSQL 可以使用大页。

node_hugepage_ratio: 0.27  # 先分配 27% 内存作为大页,肯定够 PG 用了

如果不在乎少量资源浪费,您可以直接分配 27% 左右的内存作为大页。


回收脚本

PG 启动后,使用以下 SQL 可以查询到 PostgreSQL 实际使用的大页数量:

SHOW shared_memory_size_in_huge_pages;

最后,您可以精确指定所需的大页数量:

node_hugepage_count: 3000   # 精确分配 3000 个 2MB 大页(6GB)

然而,要精准的一个不漏的统计所需的大页数量,通常要等到 PostgreSQL 服务器启动后才能获取。

所以折中的办法是,提前超量分配大页启动 PostgreSQL 后,从 PG 中查询得到所需的精准大页数量,然后再精确修改所需大页的数量。


让PG独占大页

默认情况下,所有进程都可以去使用大页,如果用户希望仅允许 PostgreSQL 数据库使用大页,可以修改 vm.hugetlb_shm_group 内核参数

你可以调整 node_sysctl_params 参数,将 PostgreSQL 的 GID 填入。

node_sysctl_params:
  vm.hugetlb_shm_group: 26
node_sysctl_params:
  vm.hugetlb_shm_group: 543

注意 EL/Debian PostgreSQL UID/GID 默认值不同,分别为 26, 543 (可以显式通过 pg_dbsu_uid 修改)

想要移除此变更:

sysctl -p /etc/sysctl.d/hugepage.conf

快速调整脚本

浪费的大页部分可以使用 pg-tune-hugepage 脚本对其进行回收,不过此脚本仅 PostgreSQL 15+ 可用。

如果你的 PostgreSQL 已经在运行,你可以使用下面的办法启动大页(仅 PG15+ 可用):

sync; echo 3 > /proc/sys/vm/drop_caches   # 刷盘,释放系统缓存(请做好数据库性能受到冲击的准备)
sudo /pg/bin/pg-tune-hugepage             # 将 nr_hugepages 写入 /etc/sysctl.d/hugepage.conf
pg restart <cls>                          # 重启 postgres 以使用 hugepage

执行 pg-tune-hugepage 的样例输出:

$ /pg/bin/pg-tune-hugepage
[INFO] Querying PostgreSQL for hugepage requirements...
[INFO] Added safety margin of 0 hugepages (5168 → 5168)
[INFO] ==================================
PostgreSQL user: postgres
PostgreSQL group ID: 26
Required hugepages: 5168
Configuration file: /etc/sysctl.d/hugepage.conf
[BEFORE] ================================
Current memory information:
AnonHugePages:      8192 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:   10025
HugePages_Free:     9896
HugePages_Rsvd:     5039
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:        20531200 kB
Current sysctl settings:
vm.hugetlb_shm_group = 26
vm.nr_hugepages = 10025
vm.nr_hugepages_mempolicy = 10025
[EXECUTE] ===============================
Writing new hugepage configuration...
Applying new settings...
vm.nr_hugepages = 5168
vm.hugetlb_shm_group = 26
[AFTER] =================================
Updated memory information:
AnonHugePages:      8192 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:    5168
HugePages_Free:     5039
HugePages_Rsvd:     5039
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:        10584064 kB
Updated sysctl settings:
vm.hugetlb_shm_group = 26
vm.nr_hugepages = 5168
vm.nr_hugepages_mempolicy = 5168
[DONE] ==================================
PostgreSQL hugepage configuration complete.

Consider adding the following to your inventory file:
node_hugepage_count: 5168
node_sysctl_params: {vm.hugetlb_shm_group: 26}

参考