云环境下PostgreSQL的Cgroup内存管理实践

📅 2026/7/16 18:34:28
云环境下PostgreSQL的Cgroup内存管理实践
本文整理于 HOW 2026 演讲内容演讲者刘智龙平安科技PG数据库专家。一、Cgroup内存管理概述1.1 Cgroup的基本原理CgroupControl Group是Linux内核提供的进程组资源管理机制可对一组进程的CPU时间、物理内存、IO、网络等资源进行配额限制、隔离与使用统计。在云环境中同一物理主机上通常运行多个PG实例每个实例通过Cgroup进行资源管理——这是各家云厂商的常见做法容器技术同样基于Cgroup实现资源约束。1.2 CPU管理与内存管理的本质差异Cgroup对CPU和内存的管理逻辑有根本性的不同CPU控制的核心是时间分配。CPU通过时间片轮转实现共享即使某Cgroup的CPU上限达到任务只是运行变慢仍可继续执行。内存控制的核心是Page计数。内存必须即时可用一个任务占用的工作内存无法被其他任务复用共享内存除外。当内存达到上限时不存在降速运行的选项只能进行回收若回收失败则进程被终止。关键区别在于CPU上限打满任务还能跑只是慢一些内存上限打满任务面临的是回收或死亡。1.3 云环境下的超卖现象在共享云主机上多个Cgroup实例并发运行。若将所有Cgroup的限额简单相加可能远超主机的物理内存即超卖。但对于PG来说超卖并不必然意味着实际内存不足——因为内存中的Cache部分是可以回收的。实际内存占用可分为三类私有内存不可回收、共享内存如PG的Shared Buffers由多进程共享不可回收、Cache文件缓存可回收。多个实例的Cache存在大量空闲或可回收空间时即使限额之和超过物理内存实际使用量可能并未超限。但若进行扩容或新增实例导致OS内存水位线超标系统会频繁回收Cache。此时回收谁的内存成了运气问题可能波及关键实例引发性能抖动。1.4 PG在OS上的内存层次PG进程的内存占用可分解为三层结构RSS常驻内存分为私有内存和共享内存通常无法回收Page Cache文件缓存可回收Kernel层内核自身的内存开销在Cgroup层面各实例拥有独立的LRU列表回收时仅影响本Cgroup实现了资源隔离。二、Pages的计算与统计口径辨析2.1 关键接口文件Cgroup内存管理涉及的核心文件包括memory.stat包含各类内存指标的详细统计是观测内存使用的主要入口memory.usage_in_bytes官方不推荐直接使用准确用量需自行计算cgroup.procs需确保所有PG进程postmaster、checkpointer、bgwriter、walwriter、backend等均加入此文件memory.limit_in_bytes定义Cgroup的内存上限2.2 memory.stat 的核心指标解读以一个实际案例为例PG实例的shared_buffers64GBshared_memory_typemmap客户端约800个。memory.stat中的关键指标包括cachePage Cache的大小rss匿名页和Swap内存注意这里的RSS与OS进程的RSS含义不同mapped_file文件共享内存大小PG的共享内存在此项中统计pgpgin / pgpgoutRSSCache的charge/uncharge页数inactive_anon / active_anon匿名页和Swap缓存的LRU分布inactive_file / active_file文件缓存的LRU分布2.3 统计口径的核心偏差通过手动计算可以发现Cgroup v1中的统计混乱问题shared_mem_mapped inactive_anon active_anon - rss cache - shared_mem_mapped inactive_file active_file rss mapped_file inactive_anon active_anon inactive_file active_file rss mapped_file rss cacheCgroup v1的RSS统计不包含file map类型的共享内存PG的共享内存无论mmap还是sysv方式均被归类到mapped_file下大页场景下共享内存甚至不出现在任何统计指标中包括rss_huge2.4 进程RSS与Cgroup RSS的差异查看PG各进程的RSS排序通常checkpointer和bgwriter的RSS最大均达60GB级别这是因为这些进程频繁操作Shared Buffers实际占用了大量共享内存。postmaster的RSS则小得多因为它只需开辟共享内存的虚拟地址空间fork给子进程使用。查看/proc/[pid]/smaps会发现checkpointer、bgwriter和postmaster的共享内存虚拟地址完全一致但RSS不同。fork出的子进程虽然继承了相同的虚拟地址映射但只有实际访问了对应物理页面的进程其RSS才会增长。2.5 如何正确模拟Cgroup OOM由于PG采用Double Buffer架构Shared Buffers Page Cache在shared_buffers 1/4 × cg_mem的典型配置下Page Cache最多可占3/4的Cgroup内存。正常业务中私有内存占用不会太多即使Cgroup内存打满系统也可从Page Cache中回收内存因此实例未必会OOM。想要有效触发Cgroup OOM最直接的方法是创建大量占用私有内存的会话如大量排序、Hash Join等而非通过持续写入填满Page Cache。后者只会触发Cache回收不会导致真正的内存耗尽。三、Cgroup OOM的行为与影响3.1 OOM触发机制Cgroup OOM可分为两种情况OOM Killer On内核根据OOM Score选择得分最高的进程终止通常是Backend用户进程发送SIGKILL信号。PG的postmaster检测到子进程异常终止后若共享内存未被破坏会自动拉起新进程业务可能短暂中断但实例整体可恢复。系统日志dmesg和PG日志中均有相关记录。OOM Killer Off不触发内核Kill但PG进程可能因内存分配失败而Hang住状态变为D等待事件为mem_cgroup_oom。关键进程如walwriter若无法获得内存实例可能直接崩溃。这种情况下即使OOM Killer关闭实例仍然可能挂掉——不是因为被杀而是跑死了。3.2 关键区别Cgroup OOM ≠ 系统级VM OOM。系统级VM OOM由vm.overcommit机制独立判断Cgroup v1的memory.oom_control关闭时OOM Killer不启动但进程仍可能因内存不足而Hang或崩溃四、Cgroup v1的缺陷与v2的改进4.1 v1的统计盲区缺少Page Table统计大量进程可导致Page Table占用数十GBv1无法观测缺少Slab统计内核Slab内存开销未纳入统计缺少HugePage统计大页内存完全不被计入形成统计黑洞无法区分异步/同步回收pgscan_kswapd与pgscan_direct在v1中无法按Cgroup粒度区分共享内存统计混乱shmem和file_mapped混在一起RSS统计口径不一致Cgroup RSS与进程RSS含义不同4.2 v2的改进Cgroup v2Linux 4.5正式发布在内存管理上提供了显著增强管理层面层级结构更清晰配置更直观新增memory.min/low/high水位线参数可更精细地控制回收行为更好地应对突刺负载移除直接关闭OOM Killer的接口改用更柔和的控制方式新增memory_hugetlb_accounting终于将大页纳入统计观测层面新增Slab、Page Table、pgscan_kswapd/pgscan_direct/pgsteal_kswapd等指标新增Socket、Vmalloc、透明大页、Zswap、全零页交换等专项统计共享内存shmem与文件映射file_mapped指标分离不再混淆大页信息终于可观测五、大页Huge Pages的管理与挑战5.1 大页的优势大页对PG的稳定性和性能有多方面好处略微提升TPS减少TLBTranslation Lookaside Buffer刷新压力降低CPU缓存竞争显著减少Page Table在主内存中的大小。这不仅节约内存还能在内存回收时加速通过物理地址查找虚拟地址的映射过程大页的物理连续性带来更好的内存访问局部性非大页的共享内存理论上可被OS回收大页不会对于内存碎片和Cgroup内直接内存回收问题大页有非常好的缓解效果。5.2 Shared Buffers与大页的配置建议将shared_buffers设置为Cgroup内存的1/4似乎是行业标准但实际情况更为复杂。调小shared_buffers可略微增加Page Cache即增大整体缓存容量调大shared_buffers则略微减小整体缓存但提升了Shared Buffers命中率。两种方向各有利弊。若shared_buffers太小PG自身可用的工作内存不足相当于把内存管理责任推给OS而OS回收Page Cache本身就有性能开销若shared_buffers太大不仅挤占Page Cache还需同步调整bgwriter相关参数如bgwriter_delay、bgwriter_lru_maxpages否则刷脏效率跟不上写入负载。基于实际压测与生产运维经验一个更靠谱的建议值至少比简单的1/4公式更可靠不开大页shared_buffers min(1/4 × 总内存, 20GB)开大页shared_buffers min(1/4 × 总内存, 60GB)读多写少的场景可适当调大但必须同步调整刷脏参数。5.3 大页带来的管理难题统计黑洞在Cgroup v1中大页完全不进入任何统计指标。一个实例的Cgroup限额设为20GB若使用大页实际可能已占用30GB物理内存但监控显示一切正常。RDS等监控系统的内存指标在大页场景下可能全面失真。资源规划困难大页需在主机启动时预先分配无法按需动态扩展。问题在于同一主机可能混合部署PG、MySQL等多种数据库不同数据库对大页的使用策略不同不知道主机上要部署多少实例、什么规格的实例大页大小难以预先确定若大页分配过大后续实例分配不到大页只能退而使用普通页违背了启用大页的初衷若分配过小则造成大量内存浪费示例场景第一个PG实例获得充足大页第二个实例要上线时大页已耗尽无法满足需求。若试图为一个大实例分配过多大页则主机上只能运行这一个实例资源利用率极低。六、内存故障模型与排查思路6.1 故障分类内存问题大致可分为四个层面操作系统层面内存碎片、Swap换入过高。注意启用大页可缓解碎片问题但可能略微加剧Swap换入问题需权衡。PG内核层面元数据膨胀、版本Bug等。Cgroup层面Cgroup内内存回收引发的性能抖动通常由Cgroup打满触发。启用大页对此有显著改善效果。私有内存层面个别Backend进程占用过多私有内存如大结果集排序、Hash Join导致Cgroup整体内存紧张。6.2 监控要点系统层/proc/buddyinfo查看碎片/proc/meminfo的si/soSwap In/Out判断换入换出压力/proc/vmstat中的pgscan_kswapd和pgscan_direct区分异步/同步回收Cgroup层memory.stat中的各项指标注意v1和v2的统计差异进程层ps查看各进程RSS/PSS计算私有内存关注进程等待事件如mem_cgroup_oom数据库层pg_stat_bgwriter的checkpoint和buffers分配等待事件中的IPC类事件Buffer命中率方面Read次数比Hit Rate更值得关注。Hit Rate很高但Read也很高时意味着即使命中率高仍然产生了大量Buffer读取操作可能因扫描范围太大这是无效的缓存竞争仅看命中率会被误导。6.3 故障先导信号常见的前置信号包括page allocation failure、kswapd长时间高CPU、direct reclaim频繁触发、进程状态频繁切换为D不可中断睡眠、malloc相关等待事件增多等。OOM则属于最终结果往往伴随着明显的进程消失和日志记录。总结云环境下PG的Cgroup内存管理比单机场景复杂得多。几个核心结论迁移到Cgroup v2可解决v1中共享内存统计、大页统计、回收观测等多方面盲区大页能提升稳定性和性能但需配合v2使用否则形成统计黑洞资源规划需谨慎Cgroup OOM的on/off各有风险on时Backend进程可能被杀但实例可恢复off时关键进程可能Hang住导致实例不可用Shared Buffers不宜超过60GB开大页时超过后刷脏压力和非大页内存管理成本急剧上升内存监控的核心是掌握真实使用量尤其要理解Cgroup RSS与进程RSS的区别、共享内存的统计归属