上篇:PostgreSQL 标准大页

1. 为什么要大页:页表会吃掉内存

Linux 默认内存页 4 KB。进程把 共享内存(PostgreSQL 的 shared_buffers 等)映射进自己的虚拟地址空间时,内核要为这些映射维护 页表(Page Table)

粗算(64 位、4 KB 页,教材常用 每页约 8 字节 页表开销):

  • 单进程映射 24 GB shared_buffers
    (24G / 4K) × 8B ≈ 48 MB 页表/进程
  • 500 个 backend(一连接一进程):
    500 × 48 MB ≈ 24 GB 页表(量级上可与 shared_buffers 相当)

页表不是连接瞬间一次性占满,而是访问共享页时 逐步分配;跑久了仍可能涨到很大,表现为 MemAvailable 莫名下降、甚至 OOM

标准大页(显式 Huge Pages):在 OS 预留 2 MB1 GB 一页,同样 24GB 映射所需页表项数量大约按页大小成比例下降(相对 4KB 常差 两个数量级以上)。


2. 标准大页是什么(Hugetlbfs)

说明
机制 管理员通过 vm.nr_hugepages 等在启动前 预留 一块「大页池」
大小 常见 2 MBHugepagesize: 2048 kB);部分机器还有 1 GB
分配特点 启动前预留、大小固定;不够则 PG huge_pages=on启动失败
PostgreSQL 共享段尽量从 Hugetlb 分配;参数 huge_pages

与「普通 4KB 页 + 内核自动合并」的 透明大页(THP) 不是同一套机制(见下篇)。


3. PostgreSQL 参数与容量计算

postgresql.conf

行为
try(常见默认) 能用大页就用,不够则退回 4KB
on 必须用足大页,否则 拒绝启动
off 不用大页

启动前查需要多少页官方文档 18.4.5):

1
2
postgres -D $PGDATA -C shared_memory_size_in_huge_pages
grep ^Hugepagesize /proc/meminfo

示例:shared_memory_size_in_huge_pages = 3170Hugepagesize = 2048 kB → 约需 3170 个 2MB 大页(≈ 6.2GB 大页池,且要覆盖 整段共享内存,不只 shared_buffers 一项)。

OS 预留(2MB 页):

1
2
3
4
5
# 临时
sysctl -w vm.nr_hugepages=3170

# 或按尺寸写 sysfs
echo 3170 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

写入 /etc/sysctl.conf 持久化。若碎片导致一次分配失败,可 重试或重启后再设(重启后普通页释放,便于凑连续大页)。

可选:vm.hugetlb_shm_groupulimit -l(lock mem)按环境配置。

1GB 大页:设置 huge_page_size = 1GB 时,用 hugepages-1048576kB 路径预留,并重算页数。


4. 验证

1
2
3
4
# 已分配的大页数
grep -E 'HugePages_Total|HugePages_Free|Hugepagesize' /proc/meminfo

cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

实例启动后,在日志或 shared_memory_size_in_huge_pagesHugePages_Free 变化上确认是否走大页。


下篇:透明大页 vs 标准大页

1. 对照表

标准大页(Hugetlb / 显式) 透明大页 THP
谁分配 管理员 nr_hugepages 预留 内核 khugepaged自动 把 4KB 合并为 2MB
是否需改应用 PG 设 huge_pages;OS 预留 应用 无感,默认可能已开
大小 2MB / 1GB 固定 多为 2MB 透明合并
不足时 on → 启动失败;try → 4KB 静默退回 4KB 或 延迟合并
可预测性 (池子固定) (合并时机、拆分会带来延迟抖动)
数据库常见态度 PG 推荐在 Linux 上用(try/on) MySQL 官方/实践多建议关闭

2. 透明大页在做什么

THP 目标:让 普通程序 不修改代码也能减少 TLB miss、降低页表体积。

过程大致是:进程仍按 4KB 申请;内核在后台把 连续、合适 的 4KB 物理页 合并 成 2MB 大页。

对数据库不利点(MySQL 文档与社区反复提及):

  1. 合并/拆分与业务竞争 CPU,可能造成 延迟尖刺(p99 变差)。
  2. 内存紧凑方式不可控,与 大块预分配、长期驻留 的 Buffer Pool / shared_buffers 模型不匹配。
  3. 内存压力大 时拆大页,可能触发 额外 stall

因此:不是「大页不好」,而是 「自动、不可控的 THP」不适合做低延迟数据库默认项


3. 标准大页在做什么(再强调)

管理员先划好 固定大页池 → PostgreSQL 启动时 明确请求 Hugetlb → 映射从池里来,无 khugepaged 后台合并

适合 PG 的原因:

  • shared_buffers + 其它共享段 体积大、生命周期长、多进程重复映射(页表问题突出)。
  • 容量可 事先算清shared_memory_size_in_huge_pages)。
  • 行为可 复现,利于容量规划。

4. 关闭透明大页(Linux)

检查:

1
2
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag

常见生产设置(重启后仍生效需写 udev/systemd/rc 或 grub,按发行版为准):

1
2
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

部分系统用 madvise 而非 never,数据库主机仍建议偏向 never,避免 InnoDB/PG 共享内存被意外 THP 影响。


MySQL:单进程与「关 THP、少谈标准大页」

1. 和 PostgreSQL 模型不同

PostgreSQL MySQL(InnoDB)
架构 一连接一进程,各映射 整段 shared_buffers 单 mysqld 多线程一份 地址空间映射 Buffer Pool
页表压力 连接数 × 每进程页表(教材 500×48MB 场景) 主要是 单进程 映射超大 Buffer Pool,不乘连接数
标准大页 收益大、文档完善huge_pages + nr_hugepages 可用 Hugetlb 配 Buffer Pool,但运维重点往往是 先关 THP

所以:不是 MySQL「不能用」标准大页,而是 页表被连接数放大 的典型痛点在 PG;MySQL 更常先处理 THP 带来的抖动

2. MySQL 为何不推荐透明大页

Oracle / MySQL 8.0 性能文档与 Troubleshooting InnoDB 等均建议:在 Linux 上 禁用 THP,由 InnoDB 自己管理 Buffer Pool 页,而不是让内核异步合并。

实践检查:

1
2
3
4
# 启动警告里可能出现 Transparent page size 相关提示
mysqld --verbose --help 2>/dev/null | head -1

grep -i huge /proc/$(pidof mysqld)/smaps 2>/dev/null | head

3. MySQL 与标准大页(了解即可)

若坚持 Hugetlb:需在 OS 预留足够 2MB/1GB 大页,再让 InnoDB 在支持的路径 使用(与版本、分配方式有关)。容量规划要按 整池 innodb_buffer_pool_size 算,步骤类似 PG,但 社区默认叙事仍是:关 THP + 调好 Buffer Pool,除非有明确 TLB/页表压测数据再上标准大页。

不建议:在 MySQL 上 只开 THP、不关 never,却指望替代标准大页调优。


对照小结

主题 PostgreSQL MySQL
页表放大 多进程 × 大 shared_buffers 单进程大 Buffer Pool
首选大页策略 OS 预留 标准大页 + huge_pages=try/on 关闭 THP
透明大页 生产常 关 THP,与 PG 标准大页 并行 明确不建议 THP
标准大页 案例主场景 可选,非入门必做

参考

相关:PG 与 MySQL 执行层内存对照 · InnoDB 进程内存总图