PostgreSQL与OOM杀手:为何必须使用严格的内存超分配
所有博客文章 2026年4月27日 · 10分钟阅读 Burak Yucesoy 首席软件工程师 我们的团队成员在过去15年中构建和运营了五个托管的PostgreSQL服务。在所有这些服务中,有一个配置始终保持不变:严格的内存超分配。在这篇博文中,我们将解释严格的内存超分配如何保护您的数据库免受灾难性的OOM(内存不足)杀死。我们还将分享一个三字符的内核错误如何迫使我们暂时禁用此设置。最后,我们将解释如何确定合适的内存超分配限制的启发式方法。希望这些内容能帮助您为您的工作负载找到合适的设置。 为什么PostgreSQL无法容忍OOM杀手 Linux允许进程分配超过物理内存可用量的虚拟内存。当一个进程分配内存时,例如通过malloc(),内核为其保留虚拟地址空间。然而,内核并不会立即用物理内存来支持这段空间。只有当进程实际访问内存时,物理页面才会被消耗。内核依赖于一个假设,即并非所有分配的内存会在同一时间被积极使用。通常情况下,这个假设是成立的。当这个假设不成立时,内核调用OOM杀手通过终止一个进程来释放内存。对于大多数进程,处理OOM杀死是很简单的:进程重新启动,重新连接,并恢复到它离开的地方。PostgreSQL则不同。PostgreSQL的postmaster(其主要监督进程)为每个连接分叉一个后端进程。这些后端共享内存段,其中包含共享缓冲区、WAL缓冲区、锁表和其他共享状态。OOM杀手并不理解这种架构。它只是根据启发式方法(通常是选择使用最多内存的进程)来选择一个进程并终止它。如果该后端正在修改共享内存段,则该段可能会处于不一致状态。共享内存在操作系统级别没有事务保障。共享缓冲区中的半写页面意味着数据静默损坏。PostgreSQL的postmaster对此非常清楚。当它检测到任何子进程被杀死时,它假设情况最糟:共享内存可能已损坏。当共享内存被损坏时,也有腐化存储数据的风险。为了防止这种情况,Postmaster终止所有剩余的后端。所有活动连接都被断开。所有进行中的事务都被中止。在下次启动时,数据库会经过崩溃恢复。这是正确的行为。PostgreSQL正在保护您的数据。但是这意味着单个OOM杀死不仅影响一个连接。它会使服务器上的每个连接都受到影响。此外,如果写入量很大,重新播放所有WAL文件以进行崩溃恢复可能需要很长时间。这意味着单个内存不足案例可能会导致长时间的中断。 严格超分配:尽早失败,而不是灾难性失败 可以配置内核在进程请求内存时的行为。Linux通过vm.overcommit_memory提供三种超分配策略: 模式0(启发式):默认值。内核拒绝任何单个分配大于系统实际能够提供的大小(大致是空闲内存 + 交换 + 可回收页面缓存和 slabs),但其他方面则自由允许超分配。在实际操作中,这仅仅阻止如某个进程请求的内存超过整个系统内存这样的荒谬请求。 模式1(始终):内核从不拒绝分配请求,无论其大小或已经分配的内存有多少。每个malloc()和mmap()都成功。如果进程稍后请求的物理内存超过系统实际上能够提供的内存,则OOM杀手会介入,通过终止进程来释放内存。 模式2(严格):内核跟踪所有进程的已分配虚拟内存总量在Committed_AS中,并强制施加一个称为CommitLimit的上限。任何会使Committed_AS超过CommitLimit的分配请求都会立即被拒绝,返回ENOMEM。当分配因ENOMEM错误码失败时,PostgreSQL将优雅地处理。无法分配内存的后端向客户端报告错误,取消事务并继续。Postmaster保持运行。其他连接不受影响。这是一个常规错误,而不是灾难。权衡在于,严格的超分配将后期的破坏性故障转换为早期的、优雅的故障。当机器专用于PostgreSQL和一小组已知的旁路进程时,此权衡效果最好。在这种情况下,已分配的内存轮廓是可预测的,限制可以自信地进行调整。在运行多样工作负载的共享机器上,已分配的内存变得更难预测。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡