备库卡在RECOVER状态的根本原因是恢复进程未完成即退出,因recovery.signal存在时PostgreSQL不阻塞主进程等待恢复结束,常见于restore_command失败、WAL不可达、systemd超时或recovery.signal残留等场景。
备库启动卡在 RECOVER 状态,根本原因是实例没等恢复完就退出
postgresql 备库(standby)重启后卡在 recover 状态,不是坏了,而是主进程被信号中断或配置不当导致恢复中途退出。关键点在于:postgresql 启动时若检测到 recovery.signal 文件存在,会以只读方式进入恢复流程;但这个过程默认不阻塞主进程等待完成——一旦恢复线程出错、wal 不可得、或 pg_ctl start 超时返回,主进程可能直接退出,留下一个“活着但没真正启动成功”的实例。
常见错误现象:pg_is_in_recovery() 返回 true,但 ps aux | grep postgres 显示 postmaster 进程已消失;日志里反复出现 could not locate a valid checkpoint record 或 WAL segment ... has already been removed。
- 确保
postgresql.conf中archive_mode = on且restore_command可稳定执行(比如用cp /path/to/archivedir/%f %p或带重试的脚本) -
recovery_target_timeline = 'latest'必须显式设置,否则遇到时间线切换时可能停在旧 timeline 上不动 - 避免把
pg_ctl start塞进 systemd service 时加--wait却没配Type=notify,这会导致超时后强制 kill
自动恢复脚本必须区分“启动”和“等待恢复完成”两个阶段
很多脚本把 pg_ctl start 和 “确认备库可用”混在一起,结果一启动就返回,根本不知道 WAL 是否追上。正确做法是分两步走:先确保 postmaster 起来并监听端口,再轮询 pg_is_in_recovery() 直到返回 false(或达到最大等待时间)。
使用场景:CI/CD 部署、K8s initContainer、Ansible playbook 中的 post-task 检查。
- 用
pg_ctl -D /path/to/data start启动,不加-w(它只等 postmaster 进程就绪,不等恢复完成) - 随后用
pg_is_in_recovery()查询,建议配合timeout 300s bash -c 'until pg_is_in_recovery; do sleep 5; done'类逻辑 - 如果用
psql -c "select pg_is_in_recovery();",注意连接参数要带-U和-d postgres,否则可能连到主库或报database "postgres" does not exist
systemd 服务中 Type=notify 和 ExecStartPost 的配合容易漏掉
PostgreSQL 12+ 支持通过 systemd-notify 告知 systemd “我真准备好了”,但默认不启用。如果只写 Type=simple,systemd 会在 pg_ctl start 返回后立刻认为服务启动成功——哪怕此时 WAL 还差 10 分钟才追平。
性能影响:没有 Type=notify,上游依赖该服务的组件(如 HAProxy、Patroni)可能过早转发流量,导致只读查询失败或延迟飙升。
- 在
.service文件中设Type=notify,并在postgresql.conf加systemd_notify = on - 用
ExecStartPost=/bin/sh -c 'until pg_is_in_recovery; do sleep 3; done'做最终确认,但别放太久(避免阻塞整个系统启动) - 不要在
ExecStart里直接调用带轮询的 shell 脚本——systemd 会把它当主进程,一旦轮询结束就认为服务退出
recovery.signal 文件残留或缺失导致状态误判
这是最隐蔽也最高频的问题:服务器重启后,recovery.signal 文件还在,但归档路径变了、restore_command 权限不对、或 primary_conninfo 指向已下线的主库。PostgreSQL 会尝试恢复,失败后静默退出,日志里只有一行 shutting down due to recovery failure,然后你就看到实例“卡在 RECOVER”。
兼容性注意:PG 12+ 把 recovery.conf 参数全挪进 postgresql.conf,但 recovery.signal 文件仍必须存在才能触发 standby 模式。
- 每次重启前检查
recovery.signal是否存在,以及standby.signal(PG 12+ 推荐)是否冲突——两个都存在时行为未定义 - 用
pg_controldata /path/to/data | grep -E "(Database system identifier|Latest checkpoint location)"快速确认数据目录是否真的来自同一集群 - 如果用流复制,
primary_conninfo中的host=必须能 DNS 解析且端口可达,别用localhost—— 容器或跨网段时大概率连不上
真正麻烦的不是脚本怎么写,而是恢复过程中任何一环断开(网络、磁盘、权限、时间线跳变),都会让备库停在“半恢复”状态,而这个状态本身不报错也不退出,得靠外部主动探测才能发现。










