PostgreSQL用户经常发现,服务端在连接数较大的情况下,会出现系统内存消耗过多的情况,严重者可能会造成OOM。但是服务端配置的共享内存(shared_buffers,wal_buffers等)是一定的,为什么内存会持续增加呢?这就与PostgreSQL的多进程架构有关了,下面我们来分析下。
1. 大规格PG实例内存使用率较高分析
为了保证物理内存能得到充分的利用,避免内存空间浪费,Linux把进程当前使用的内存部分加载到物理内存里,而不使用的部分则暂不加载。PostMaster进程注册共享内存时,系统只是分配一个虚拟的地址空间,并不直接分配物理内存。当有实际的内存访问时,CPU才会将虚拟地址映射到物理内存的一个地址上。维护这个映射关系的就是PageTable,它负责将虚拟内存地址转换成物理内存地址。
Linux的内存管理采取的是分页存取机制:把较大的物理内存分为了一个个固定大小(4kB)的内存页进行管理。每块内存页通过PageTable中的一个元组来维护虚拟/物理内存之间的映射。CPU为了提高虚拟/物理内存之间的转换效率,也会在TLB中缓存一定量的Page Table元组。
对于PostgreSQL这种多进程架构程序来说,当服务端使用的共享内存较大,且并发连接数较多时,由于操作系统对于每个进程都要维护单独的内存映射,PageTable中的元组数目将会变得非常多,所占用的内存大小也会特别大。
2. Huge Page改善措施
Linux为了应对这种场景,降低多进程下PageTable的内存消耗。自从2.6及以上内核版本提供了内存页大小为2MB的管理方式,称为Huge Page。如果使用Huge Page的话,相同物理内存使用量的情况下内存页的数目变少,减少了PageTable元组的条目个数,从而降低了系统的内存占用。
作为世界上最先进的开源数据库,PostgreSQL也适配了Linux的Huge Page特性,服务端在注册共享内存时,会通过配置参数huge_pages来决定是否申请大页内存。
postgresql.conf:
huge_pages = on -- 注册共享内存时必须使用大页
huge_pages = try -- 注册共享内存时首先考虑大页,若系统提供的大页内存不足时,则全部使用普通页
huge_pages = off -- 注册共享内存时不使用大页
真实应用场景:某PG用户将实例(shared_buffers = 64GB)部署在一台内存为256GB的ECS上,业务繁忙时ECS内存使用率为85%,PageTable占用内存120GB。而开启Huge Page后相同业务场景的内存使用率降低到50%以下,PageTable大小仅300M!
3. PG实例开启Huge Page操作步骤
(1)查看操作系统的Huge Page大小grep Hugepage /proc/meminfo
(2)估算PostgreSQL实例需要的Huge Page使用量:128GB/2MB * 1.2 = 78643
(3)/etc/sysctl.conf中添加:vm.nr_hugepages = 78643
(4)重新加载系统配置参数:sysctl –p
(5)确认是否配置成功。可以看到Huge Page总数为78643
(6)确认PG配置文件打开huge_pages
(7)启动PostgreSQL服务端,可以看到系统中的空闲Huge Page已经减少,部分大页已经被共享内存使用。
4. Huge Page使用建议
虽然Huge Page在一定场景下可以改善服务端内存使用过高的情况,但不是鼓励所有的PG实例都使用大页,盲目的开启Huge Page可能引起服务端的性能下降。下面我们根据Huge Page的优缺点来分析下使用场景。
Huge Page优势:
(1)CPU的TLB可以缓存的物理地址空间更大,从而提升TLB的命中率,降低CPU负载;
(2)Huge Page使用的内存是不可交换(swap)的,没有内存空间换入/换出的开销;
(3)极大的减少了系统维护PageTable的内存开销。
Huge Page劣势:
(1)Huge Page使用的内存需要预先分配;
(2)Huge Page使用固定大小的内存区域,不会被释放;
(3)对于写密集型的场景,Huge Page会加大Cache写冲突的发生概率。
所以强烈推荐PG实例开启Huge Page的场景:共享内存使用较大(>=8GB)且连接数较多(>= 500),并且热点数据分散。不推荐PG实例开启Huge Page的场景:写业务密集,热点数据集中且内存使用较小。
5.PG开启Huge Page时的注意事项
(1)当配置参数huge_pages设置为on时,若PG启动时需要注册的共享内存大于操作系统提供的Huge Page大小时,数据库将无法启动。推荐将huge_pages参数设置为try,在此种场景下,PostMaster将会改为申请普通内存。
(2)修改shared_buffers/wal_buffers等共享内存相关的GUC参数时,需要重新计算操作系统所需的Huge Page数,以防服务端无法启动或者部分大页内存没有被使用且无法释放而造成浪费。