PostgreSQL启动失败排障:共享内存与postmaster.pid冲突

发布时间:2026/10/8 3:09:44
PostgreSQL启动失败排障:共享内存与postmaster.pid冲突 PostgreSQL 启动失败这个问题平时不遇到就算了一遇到就是业务方连环夺命call。上个月我就处理了一个典型的案例PostgreSQL 15 实例跑在 Ubuntu 22.04 上32G 内存业务量不大跑了半年一直很稳。结果某个工作日下午后台突然开始刷 connection refused应用监控一片红。登录服务器一看postgres 主进程没了systemd 状态是 failed。我第一反应不是急着 restart而是先打开日志。为什么因为重启会把排障证据冲得一干二净尤其这种“启动失败”的问题日志里一定藏着直接原因。这次故障最后被拆成了两条线共享内存冲突加上 postmaster.pid 文件丢失。两个原因叠在一起单修哪一个都救不回来。这篇就当一次完整复盘把排查思路、命令和踩过的坑都写出来。1. 故障现场与排障第一步先别急着重启1.1 故障现场告警响起数据库拒绝连接先说当时的直观现象。业务侧从某个时间点开始连接服务端口的请求几乎全部失败应用日志里的报错集中在 connection refused。我用得很顺手的几组命令第一轮检查就直观地暴露了问题状态systemctl status postgresql15-main服务处于 failed 状态ps -ef | grep postgres没有任何 postmaster 或者 backend 进程在跑pg_isready -h /var/run/postgresql -p 5432直接 no response。这时候容易有个误解systemd 显示 failed不代表它没尝试过拉起实例。实际上 systemd 会在启动超时或进程退出后把状态标记为 failed而 PostgreSQL 真正无法启动的原因往往记录在它自己的日志文件里systemd 的日志反而比较粗糙。所以我当时没有盯着journalctl -u postgresql15-main看太久而是直接打开了数据目录对应的运行日志。Debian 系默认路径是/var/log/postgresql/postgresql-15-main.logRHEL 系通常会在$PGDATA/log下按天生成日志文件。日志尾部两行信息让这次故障的性质变得很清晰FATAL: could not create shared memory for segment PostgreSQL: No space left on device FATAL: lock file postmaster.pid already exists第一行是共享内存分配失败第二行是锁文件冲突。当时我的第一反应是“不对劲”共享内存报错和锁文件报错同时出现说明启动失败的原因不是一层而是两层。单处理一个另一个仍然会把实例卡死。1.2 排障第一步先冻结现场而不是先重启很多新手遇到数据库起不来第一反应是systemctl restart postgresql先拉起来再说。这个习惯在故障场景里非常危险。首先重启会把当前日志顶掉尤其是日志轮转策略不合理的系统旧日志被覆盖后根因线索直接消失。其次如果 PID 文件残留或者共享内存参数确实有问题重启大概率还是起不来只是多刷几条报错。我自己的做法是“先冻结现场再动手”把当前日志、内核参数、进程状态、端口监听状态、磁盘使用情况全部记录一遍然后再考虑下一步操作。当时我执行的一个小批量命令基本就是这个思路mkdir -p /tmp/pg_排障_$(date %Y%m%d%H%M) cp /var/log/postgresql/postgresql-15-main.log /tmp/pg_排障_$(date %Y%m%d%H%M)/ cat /proc/sys/kernel/shmmax cat /proc/sys/kernel/shmall free -h df -h /var/lib/postgresql ipcs -m这些命令不会改变任何状态纯粹是给服务器“拍 X 光片”。等诊断完了再决定改什么是排障的基本纪律。1.3 日志里的两条关键线索先说第一条线索“could not create shared memory”。这个报错直译是共享内存段创建失败系统提示 No space left on device。生产环境里遇到这个报错优先怀疑三件事物理内存或 swap 耗尽、内核kernel.shmmax/kernel.shmall配置过小、huge pages 配置异常。稍后我会在第二章仔细展开。第二条线索“lock file postmaster.pid already exists”。这个报错意味着 PostgreSQL 启动时发现数据目录下已经存在 postmaster.pid 文件而这个文件是“实例正在运行”的标志。但问题在于ps里根本就没有任何 postgres 进程。一个文件在进程却没了这就是典型的残留 PID 文件也叫 stale pid file。一条报错指向资源不足一条报错指向锁文件残留。我当时就意识到这次故障的处置顺序很重要如果只调大共享内存就启动残留锁文件会再次挡住如果只删锁文件就启动共享内存不足会再次报错。必须先完整确认两边的状态再按顺序处理。下一章开始我先把共享内存这条线讲透因为它是这次故障最深层的根因。2. 第一个元凶共享内存冲突到底是怎么一回事2.1 PostgreSQL 的共享内存模型为什么它一启动就要一大块内存PostgreSQL 采用的是多进程架构一个 postmaster 主进程负责管理下面派生一堆 backend 进程来处理连接。这些进程之间要协作就必须有一块大家都看得见、都能读写的公共区域也就是共享内存。共享内存里放的东西很多最核心的是shared_buffers也就是数据页的缓存。PostgreSQL 会把表数据和索引页读进这块缓存backend 进程要读一个数据页时先看 shared_buffers 里有没有没有再去磁盘拉。这块公共区域还包含 WAL 缓冲区、CLOG、锁表、进程信号、统计信息等。打个比方共享内存就像公司的一块公用白板所有工位的人都要往上面写东西。白板尺寸设得太小大家想写的内容放不下就会报错白板尺寸设得很大但房间就这么大同样放不下。版本上有个关键变化PostgreSQL 12 之前在 Unix-like 系统上默认使用 System V 共享内存分配行为受kernel.shmmax和kernel.shmall限制12 之后默认改用匿名共享内存通过mmap(MAP_SHARED|MAP_ANONYMOUS)实现不再强依赖 SysV 参数。但很多生产环境仍然开启了huge_pages而 huge pages 的分配又把内核参数和内存碎片问题带回来了。这就是为什么“换新版本后突然报共享内存错误”的情况并不少见。2.2 冲突的真正根源与排查命令这次实例的配置是shared_buffers 8GBhuge_pages没显式配置默认是try。表面上看配置不算激进但故障前的内存状态很糟糕物理内存 32Gfree 显示可用内存只剩不到 2Gswap 几乎被占满。为什么内存会这么紧张查看之后发现这台机器上除了 PostgreSQL还跑了一个 Java 应用堆内存设置非常大cgroup 限制又不严格Java 把内存吃掉了大半。PostgreSQL 启动时要分配共享内存直接撞上了内存天花板。更隐蔽的是系统里残留了几段 SysV 共享内存。用ipcs -m能看到一堆 orphan 共享内存段这些是之前异常退出时没有回收干净的。每个段虽然不算大但累积起来占用了不少shmmax配额。新实例要创建自己的共享内存段时系统认为共享内存总量已经达到上限直接说 No space left on device。排查共享内存类问题我习惯按下面这个顺序看free -h先看物理内存和 swap 是否充足cat /proc/sys/kernel/shmmax和cat /proc/sys/kernel/shmall看 SysV 共享内存的字节上限和页数上限ipcs -lm看系统共享内存限制cat /proc/meminfo | grep -i hugepages看 HugePages 分配情况ipcs -m看是否存在孤儿共享内存段。如果确认有大量孤儿段先别急着ipcrm -m删除。正确做法是确认这些段确实不属于当前运行的任何进程再用ipcs -m输出的 shmid 逐个清理。这一步对不上号就动手可能误删其他应用的共享内存。2.3 参数调整让共享内存不再打架这次调整做了三件事清理孤儿共享内存段、调大内核共享内存上限、顺手处理 huge pages 配置。临时调整内核参数可以使用# shmmax 以字节为单位这里设置成 16GB sysctl -w kernel.shmmax17179869184 # shmall 以页为单位16GB / 4096 4194304 sysctl -w kernel.shmall4194304注意换算关系shmmax单位是字节shmall单位是页。Linux 默认页大小通常是 4096 字节可以用getconf PAGE_SIZE确认。这两个参数要持久化的话写入/etc/sysctl.conf或/etc/sysctl.d/99-postgresql.conf然后sysctl -p生效。为什么我要把shmmax设成 16GBshared_buffers是 8GB但 PostgreSQL 的共享内存不等于只有shared_buffers。它还包括 WAL buffers、锁表、提交日志等这些加起来大约在 shared_buffers 的 10%~25% 之间。8GB 的 shared_buffers整体共享内存需求大概会到 9~10GB。把 shmmax 留到 16GB 是留出余量避免以后加一点参数就再次撞线。传统经验是 shared_buffers 不要超过物理内存的 25%32G 的机器设 8GB 已经到推荐上限这次故障的根子不在于参数过大而在于内存被挤占后没有余量。huge pages 方面我建议保持huge_pages try而不是on。try会让 PostgreSQL 先尝使用大页失败时自动退回普通页对可用性影响最小。如果系统确实需要开启大页提升性能必须在/etc/sysctl.conf里配置vm.nr_hugepages并且确保大页数量足够。否则启动时分配大页失败又会出现共享内存报错而且这条报错路径比 SysV 限制更难排查。注意修改内核参数之前先备份/etc/sysctl.conf这是铁律。改错了可以用备份秒级恢复不备份的话只能靠记忆逆操作压力完全不同。3. 第二个元凶postmaster.pid 文件丢失或残留3.1 postmaster.pid 到底是个什么文件PostgreSQL 的锁文件不叫.lock而是直接使用数据目录下的postmaster.pid。这个文件同时承担两个职责锁文件和身份信息文件。它里面记录了一组很重要的元数据正常情况是这样的3789 /var/lib/postgresql/15/main 1712712345 5432 /tmp * 8 456789第一行是 postmaster 进程的 PID第二行是数据目录路径第三行是启动时间戳后面还有监听端口、socket 目录、共享内存 key 等。PostgreSQL 启动时如果发现这个文件存在会先读取第一行的 PID然后检查这个进程是否真的存在。如果进程存在就认为实例已在运行拒绝重复启动如果进程不存在会报 lock file already exists需要人工确认后清理。这个文件的特殊性在于它不是普通日志而是数据库实例的“身份证”。文件在而进程不在称为残留 PID 文件文件不在而进程还在那问题更麻烦可能出现两个 postmaster 同时认为自己是主人的情况。我们这次遇到的是前者。3.2 文件还在但进程没了确认“假死”的完整步骤看到lock file postmaster.pid already exists后不能直接去 rm 文件必须先回答一个问题文件里记录的进程到底是死是活这一步错了后果可能是数据目录被两个实例同时写入直接破坏数据。确认进程是否还活着的步骤其实非常简单# 查看锁文件里的 PID cat /var/lib/postgresql/15/main/postmaster.pid | head -1 # 检查这个 PID 是否对应真实进程 ps -p 3789 -o pid,cmd # 再全局搜一遍 postgres 进程 ps -ef | grep postgres # 检查端口是否还在监听 ss -lntp | grep 5432如果ps里没有任何 postgres 相关进程端口也没有监听才能判断这是一个残留文件。有一种情况需要特别谨慎主从复制环境里备库可能在运行而你面对的是主库的数据目录。此时 ps 里能看到备库的 postmaster 进程但 PID 和锁文件对不上需要先搞清楚当前是谁在使用这个数据目录。如果备库正通过复制协议连着主库主库数据目录被另一个实例误启动复制链路会立刻出问题。做清理前最好用pg_ctl status -D $PGDATA再确认一次实例状态。3.3 删除残留 PID 文件前必须确认的四个条件我习惯在删除或移动postmaster.pid之前逐项确认以下四个条件ps确认没有指向该数据目录的 postmaster 进程在运行端口没有被其他进程占用ss -lntp输出干净这不是主从切换过程中某端还在等待恢复的场景数据目录所在磁盘没有写满空间足够支撑后续 WAL 写入。任何一条不满足都别动这个文件。满足条件后处理方式也不是rm而是mvmv /var/lib/postgresql/15/main/postmaster.pid /var/lib/postgresql/15/main/postmaster.pid.stale先把文件挪走而不是删掉好处是万一判断错了还能恢复原状。等确认新实例能正常启动、运行稳定后再回头清理这个.stale文件也不迟。这里还有一个很常见的误区看到日志里出现database system was interrupted就以为数据库损坏了急着往恢复方向折腾。实际上这正是 PostgreSQL 崩溃恢复的正常信号。上次实例异常退出后WAL 里可能还有未落盘的事务启动时它会自动进入 recovery 流程把 WAL 回放完才会开放连接。这个阶段什么都不用做等它恢复完成就好。手动干预反而可能打断恢复过程造成连锁问题。4. 从崩溃到恢复完整修复流程复现4.1 一步步把服务拉起来说完了两个根因下面是这次故障实际的处置顺序。背景信息再明确一下避免读者对不上号Ubuntu 22.04PostgreSQL 15 源码包安装数据目录/var/lib/postgresql/15/mainshared_buffers 为 8GB物理内存 32G 但被 Java 应用吃掉大半。第一步备份现场。日志和内核参数先留一份底mkdir -p /tmp/pg_fix_$(date %Y%m%d%H%M) cp /var/log/postgresql/postgresql-15-main.log /tmp/pg_fix_$(date %Y%m%d%H%M)/ cat /proc/sys/kernel/shmmax /tmp/pg_fix_$(date %Y%m%d%H%M)/shmmax.bak cat /proc/sys/kernel/shmall /tmp/pg_fix_$(date %Y%m%d%H%M)/shmall.bak第二步确认进程和端口状态。这一步在前面已经讲过核心就是ps、ss、pg_ctl status三个命令交叉确认。第三步清理孤儿共享内存段。ipcs -m输出里不属于任何当前进程的段用ipcrm -m shmid清理。这一步执行前要特别小心最好先cat /proc/pid/maps确认没有进程在用这个段。第四步调整内核共享内存参数。临时生效用sysctl -w持久化写入/etc/sysctl.conf再sysctl -p。sysctl -w kernel.shmmax17179869184 sysctl -w kernel.shmall4194304 echo kernel.shmmax17179869184 /etc/sysctl.conf echo kernel.shmall4194304 /etc/sysctl.conf sysctl -p第五步移动残留 PID 文件。前面已经确认过没有进程存活、没有端口占用这里执行mv即可。第六步启动实例。我习惯用pg_ctl而不是直接调systemctl start因为pg_ctl可以把启动日志输出到显式指定的文件方便实时追踪启动过程su - postgres -c /usr/lib/postgresql/15/bin/pg_ctl -D /var/lib/postgresql/15/main -l /tmp/pg_start.log start启动后立刻查看日志tail -f /tmp/pg_start.log正常情况下会看到类似这样的输出2025-XX-XX 14:23:11.123 CST [3789] LOG: database system was interrupted 2025-XX-XX 14:23:11.124 CST [3789] LOG: checkpoint record is at ... 2025-XX-XX 14:23:11.125 CST [3789] LOG: redo done at ... 2025-XX-XX 14:23:11.126 CST [3789] LOG: database system is ready to accept connections看到database system is ready to accept connections才意味着启动真正完成。之前所有阶段出现任何异常都要再回头检查而不是急着告诉业务“好了”。第七步让 systemd 确认服务状态避免手动启动的进程和 systemd 预期不一致systemctl status postgresql15-main确认 active (running) 后业务方再进行连接测试。4.2 启动后的校验工作确认数据没丢、业务没断实例能起来只是第一步后面还要做数据层面的校验确保崩溃恢复没有留下隐患。先看启动日志里的 recovery 过程确认database system was interrupted之后出现了redo done、checkpoint complete这类关键字说明 WAL 回放已经完整走完了。再用pg_controldata检查控制文件状态su - postgres -c /usr/lib/postgresql/15/bin/pg_controldata /var/lib/postgresql/15/main重点看Database cluster state这一项。正常应该是in production。如果显示in recovery或shutdown说明状态不对需要继续排查。登录数据库做基础校验psql -U postgres -c select 1; psql -U postgres -c show shared_buffers; psql -U postgres -c select count(*) from pg_stat_activity;连接数恢复正常后提醒应用侧把连接池重置一下。这个问题很容易被忽略数据库重启后客户端连接池里可能还留着旧连接应用不知道服务已恢复一直从池子里拿失效连接。最稳妥的做法是让业务方重启应用或刷新连接池让所有连接重新建立。如果启用了 data checksum可以在数据库停止状态下跑一次pg_verify_checksums做全库校验。需要注意的是这个工具要求实例必须是离线状态在线跑会导致误报所以要在窗口期操作。4.3 防止同类故障再次发生的巡检建议故障恢复之后我通常会把“为什么会发生”和“怎样避免再发生”各写一段。这次的核心教训很简单共享内存冲突和 PID 文件丢失本质都是异常退出后没有清理干净导致的。日常巡检建议重点盯这几项内存使用率长期超过 85% 时要提前扩容或治理内存占用而不是等 PostgreSQL 启动时撞墙kernel.shmmax、kernel.shmall、HugePages 数量记录到资产管理表里变更硬件或内存后同步调整定期检查/dev/shm的剩余空间容器场景下尤其重要很多共享内存报错其实是/dev/shm太小数据目录的磁盘空间低于 20% 时触发告警避免 WAL 写不进去导致启动失败为postmaster.pid文件做一个简单的监控脚本若发现文件存在但进程不存在发出提醒。写一个最简单的健康检查脚本放在 cron 里成本很低#!/bin/bash if [ -f /var/lib/postgresql/15/main/postmaster.pid ]; then pid$(head -1 /var/lib/postgresql/15/main/postmaster.pid) if ! kill -0 $pid 2/dev/null; then echo stale postmaster.pid detected, pid$pid fi fi脚本本身不修复任何东西但能在故障刚发生时第一时间暴露问题省去现场猜谜的时间。5. 排障经验速查表与几句真心话5.1 常见启动失败场景速查表PostgreSQL 启动失败的报错千奇百怪但归纳下来高频场景就那几个。我把这次排障中遇到的以及历史上经常见到的几种情况整理成一张速查表方便下次直接对照报错关键字可能原因优先处理方式lock file postmaster.pid already exists实例仍在运行或残留 PID 文件先确认进程再决定是否 mv 残留文件could not create shared memory segmentshmmax/shmall 限制、内存不足、大页配置异常查内存状态调内核参数清理孤儿段could not bind TCP port 5432端口被占用ss -lntp找到占用进程处理占用data directory /xxx has invalid permissions数据目录属主或权限不对改为 postgres 用户所有目录权限 0700could not open file pg_wal/...WAL 文件缺失或磁盘损坏进入紧急恢复流程优先备份现有数据database system is in recovery mode崩溃恢复还没有完成等待回放结束不要手动干预表格里第三行和第四行看着简单实际生产环境里也经常出现。端口被占用最常见的是同机部署了两个 PostgreSQL 实例配置了相同的监听端口。目录权限问题则多见于误用了 root 操作数据目录或者从备份恢复时没有保留属主信息。5.2 给新人的几条实战纪律做数据库排障这些年我发现绝大多数启动失败最后都能查到明确原因反而是一些“操作习惯”问题会造成二次伤害。分享几条我自己的纪律每条都是踩坑踩出来的第一排障第一原则是“先取证、后处理”。所有操作之前先把日志、进程状态、内存参数、磁盘状态记录下来。别小看这一步它能在你改错参数后帮你快速回滚也能在故障升级时给团队提供完整的现场资料。第二不要轻易kill -9postmaster。要停实例先pg_ctl stop -m fast实在不行再用-m immediate最后才考虑极端手段。kill -9会跳过清理流程往往就是 PID 文件残留和共享内存段无法回收的直接原因。这次故障的一个重要诱因就是之前有人对 postmaster 执行过强制 kill。第三数据目录里的文件不清楚用途就不要随便动。postmaster.pid这种看起来像临时文件的家伙实际上起着锁和身份标识的作用。生产环境里任何对数据目录的写操作都要先搞明白后果。第四日志看不懂就去查官方文档不要靠猜。有些报错信息里的 HINT 其实已经写得很直白比如 “Is another postmaster (PID xxx) running in data directory...”照着 HINT 排查通常不会跑偏。第五所有变更都要有回滚方案。修改内核参数、移动锁文件、清理共享内存段任何一步都可能影响服务状态。动手之前想一想“如果这步错了怎么办”答案清晰了再执行。5.3 一点个人体会这次故障本身不算复杂就是两个常见的启动问题叠在了一起但处理起来的迷惑性比单故障大得多。如果只看“共享内存冲突”很可能删了孤儿段、调了参数后被残留 PID 文件挡住如果只看“PID 文件丢失”删完文件再启动共享内存不足又会再次报错。排障的顺序和完整性比单点修复技巧更重要。我个人在 PostgreSQL 实例维护上的最大体会是启动失败这种故障90% 都能从日志里找到直接原因剩下 10% 是日志被覆盖了。所以遇到这类问题第一件事永远是保护好日志宁可多花五分钟做快照和备份也不要急着把服务拉起来。另一个实用建议是把/etc/sysctl.conf和postgresql.conf纳入版本管理哪怕只是一个简单的 git 仓库。这样每次配置变更都有记录出问题后可以直接 diff 出差异不需要靠模糊记忆去猜之前改了什么。如果你现在也正被“PostgreSQL 启动失败代码 2”这类问题困扰建议先把日志完整拉出来再对照本文提到的共享内存和 PID 文件两个排查方向走一遍。大部分启动失败最后都能在这两个方向里找到影子。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询