PostgreSQL 集群异常恢复

问题现象:3节点的Patroni + PostgreSQL集群 postgresql-0 节点异常,一直处于starting状态,Lag in MB 显示 unknown,postgresql-0使用的pv、etcd集群排查都没有问题,集群状态及日志报错如下图所示,输出排查步骤及恢复方案

PostgreSQL 集群异常恢复

PostgreSQL 集群异常恢复PostgreSQL 集群异常恢复


qwen3.8-max

基于您提供的截图和描述,这是一个典型的 Patroni + PostgreSQL 集群中备库(Replica)启动失败/卡住 的问题。

1. 现象分析

  • 状态异常postgresql-0 节点在 patronictl list 中显示为 starting,且 Lag in MBunknown。这意味着 Patroni 无法成功连接到该节点的 PostgreSQL 进程来查询复制延迟。

  • 日志关键信息(第二张图):

    • Still starting up as a standby.:Patroni 认为 PG 正在作为备库启动。

    • Error communicating with PostgreSQL... /var/run/postgresql:5432 - rejecting connections这是核心错误。PostgreSQL 进程虽然可能启动了,但它处于“拒绝连接”的状态。通常发生在 PG 正在进行崩溃恢复(Crash Recovery)、正在重放 WAL 日志尚未达到一致性状态,或者启动过程中卡住了。

    • Failed to determine PostgreSQL state from the connection:因为连不上数据库,Patroni 无法判断其真实状态,只能回退到缓存的角色信息。

  • 拓扑结构postgresql-2 是 Leader (TL 266),postgresql-1 是健康的 Replica (TL 135, Lag 752MB),postgresql-0 是故障节点。

  • 手动连接报错FATAL: the database system is starting up。这证实了 PostgreSQL 进程确实活着,但它还没有完成启动/恢复过程,因此拒绝任何业务连接。这也是为什么 Patroni 报 rejecting connectionsLag in MB: unknown 的根本原因。

  • 进程状态 (ps -ef)

    • PID 61 是主进程 postgres -D ...

    • 关键进程 PID 64postgresql: startup recovering 00000008E000000000000077

  • 结论:您的 postgresql-0 节点当前正处于 WAL 日志回放(Crash Recovery / Standby Recovery)阶段。它正在努力应用 WAL 日志以达到一致性状态。在 startup 进程完成之前,数据库无法接受连接,Patroni 也就只能一直显示 starting


2. 为什么会卡在这里?

结合您第一张图的信息:

  1. 集群整体延迟大:健康的备库 postgresql-1 也有 752 MB 的延迟。这说明主库写入量很大,或者网络/磁盘 IO 存在瓶颈。

  2. Timeline (TL) 差异巨大:Leader (postgresql-2) 的 TL 是 266,而 postgresql-1 的 TL 是 135。这说明集群历史上发生过非常多次的故障切换(Failover)。

  3. 当前恢复位置postgresql-0 正在恢复的 WAL 段是 00000008E000000000000077(注意前缀 00000008 代表 Timeline 8)。

核心矛盾postgresql-0 本地的数据还停留在非常古老的 Timeline 8,而当前的 Leader 已经在 Timeline 266 上了。
PostgreSQL 备库在启动时,必须先从本地 WAL 或归档中把历史日志回放完,直到追上当前的 Timeline。由于跨度太大(从 8 到 266),它可能需要回放海量的 WAL 日志,或者正在尝试寻找跨越这些 Timeline 的历史记录,导致启动过程极其漫长,看起来像是“卡住”了。


3. 排错步骤

请按顺序在 postgresql-0 (172.24.0.107) 节点上执行以下操作:

第一步:检查 PostgreSQL 原生日志(最重要)

Patroni 的日志只告诉我们“连不上”,PG 自己的日志才会告诉我们“为什么连不上”。

# 找到 PG 日志路径,通常在 /var/log/postgresql/ 或数据目录下的 pg_log/
# 如果是容器化部署,使用 kubectl logs 或 docker logs
tail -f /var/log/postgresql/postgresql-*.log 
# 或者
tail -f /home/postgres/data/pg_log/postgresql-*.log

重点关注日志中的以下关键词:

  • redo starts at ... / redo done:看是否在进行恢复。如果一直卡在 redo starts 且没有进度,说明 WAL 回放有问题。

  • could not connect to the primary server:看是否能连通 Leader (172.24.0.116)。

  • FATALPANIC:任何致命错误。

  • waiting for WAL to become available:等待 WAL 日志。

第二步:检查进程状态

确认 PG 进程是否存在,以及处于什么状态。

ps -ef | grep postgres
  • 如果没有 postgres 进程:说明 PG 根本没起来,或者是被 Patroni 反复重启。

  • 如果有进程,但状态是 D (不可中断睡眠) 或 R (运行中) 且 CPU 占用高:可能正在大量回放 WAL。

  • 检查是否有残留的 postmaster.pid

ls -l /home/postgres/data/postmaster.pid  # 路径根据实际情况调整
  • 注意:不要随意删除此文件,除非你确定 PG 进程已经完全不存在。

第三步:手动尝试连接(绕过 Patroni)

尝试直接用 pg_isreadypsql 连接本地 socket,看具体报错。

# 检查端口是否监听
netstat -tlnp | grep 5432

# 尝试本地连接
su - postgres
psql -h /var/run/postgresql -p 5432 -U postgres -c "select 1;"
  • 如果报错 the database system is starting up:证实了日志中的猜测,PG 还在恢复中,需要等待

  • 如果报错 no pg_hba.conf entry:配置问题。

  • 如果连接直接被拒绝且无进程:PG 启动失败。

第四步:检查网络连通性

确保 postgresql-0 能访问 Leader (postgresql-2, 172.24.0.116) 的 5432 端口。

telnet 172.24.0.116 5432
# 或
nc -zv 172.24.0.116 5432

如果不通,检查防火墙、安全组或 K8s NetworkPolicy。


4. 解决方案

根据上述排查结果,选择对应的方案:

方案 A:如果只是 WAL 回放慢(最常见)

如果 PG 日志显示正在 redo 且没有报错,只是速度慢(特别是看到 pg-1 也有 700多MB 延迟,说明集群整体写入压力大或网络带宽受限):

  1. 耐心等待:不要强制重启。让 PG 完成恢复。

  2. 监控进度:观察日志中 LSN 的变化。

  3. 优化:如果长期如此,考虑增加 max_wal_senders,调整 wal_receiver_status_interval,或检查磁盘 IO 性能。

方案 B:如果 PG 进程卡死或数据损坏(推荐尝试)

如果日志显示 PANIC,或者长时间(超过 30 分钟)无任何进展,且 psql 始终报 starting up,则需要重建该副本。由于是 Patroni 集群,重建非常安全。


处理方案

针对这种情况,有两种处理策略。强烈建议直接采用方案 B,因为等待一个跨越 200 多个 Timeline 的备库自行追平几乎是不现实的,且极易出错。

方案 A:继续等待(仅适用于刚重启不久)

如果您刚刚重启该节点不到 10-20 分钟,可以观察一下 PostgreSQL 的原生日志,看 LSN 是否在跳动。

# 查看 PG 日志,确认是否在持续回放
tail -f /home/postgres/pgdata/pgroot/data/log/*.log 
# (路径根据您的实际 pg_log 位置调整)

如果日志里不断有 redo at ... 且数字在变大,说明它在干活。但考虑到 TL 差距,不建议死等

方案 B:重建该副本(推荐,最快最安全)

既然 PV 和 etcd 都没问题,利用 Patroni 的自动克隆功能,让它直接从当前的 Leader (postgresql-2) 重新全量同步一份最新的数据,是解决此问题的标准做法。

操作步骤:

1.停止 Patroni 服务
为了防止 Patroni 在我们清理数据时反复尝试拉起 PG,先停掉它。

# 如果是 systemd 管理
systemctl stop patroni

# 如果是容器环境 (如 K8s/Docker),请通过编排工具停止该 Pod/Container

2.确认并清理数据目录
从截图中可以看到,您的数据目录是:/home/postgres/pgdata/pgroot/data
警告:请务必核对路径,删除错误目录会导致灾难性后果!

# 再次确认路径
ls -ld /home/postgres/pgdata/pgroot/data

# 清空该目录下的所有内容 (保留目录本身)
rm -rf /home/postgres/pgdata/pgroot/data/*

# 确认已清空
ls -A /home/postgres/pgdata/pgroot/data

3.重新启动 Patroni

systemctl start patroni
# 或启动对应的容器/Pod

4.观察重建进度
Patroni 启动后,会发现数据目录为空,自动触发 pg_basebackup 从 Leader (172.24.0.116) 克隆数据。

  • 查看 Patroni 日志

tail -f /var/log/patroni/patroni.log
# 或 journalctl -u patroni -f

您应该会看到类似 replicating from leaderrunning pg_basebackup 的日志。

  • 查看集群状态

patronictl list

状态变化预期:

    1. starting (正在克隆)

    2. running (克隆完成,开始流复制)

    3. Lag in MB 会从 unknown 变成一个具体的数字,并逐渐减小到 0 或很小的值。

    4. TL (Timeline) 会变成 266,与 Leader 一致。

总结

您遇到的不是故障,而是备库数据太旧,正在艰难地进行跨 Timeline 恢复。由于落后太多(TL 8 vs TL 266),自行恢复效率极低。清空数据目录让 Patroni 重新克隆是解决此问题的最佳实践。


anzhihe 安志合个人博客,版权所有 丨 如未注明,均为原创 丨 转载请注明转自:https://chegva.com/6806.html | ☆★★每天进步一点点,加油!★★☆ | 

您可能还感兴趣的文章!

发表评论

电子邮件地址不会被公开。 必填项已用*标注