PostgreSQL 崩溃应急处理流程(11 机 · PG16)

PostgreSQL 崩溃应急处理流程(11 机 · PG16)

适用症状:站点部分接口 500,而首页/其他接口正常(典型「一半好一半坏」) 实战来源:2026-10-14 11 机(10.66.66.11)PG16 因硬断电导致 WAL 检查点损坏,停摆约 11 小时 核心原则:先判断、后取证、先副本、再动生产。全程不删任何数据。


一、问题现象(长这样就是它)

现象 说明
浏览器报 500 Internal Server Error,页面是 Flask/Werkzeug 默认样式 是 Python 后端抛的,不是 nginx
同一站点有的接口 200,有的 500 关键特征:挂的是「碰数据库的那部分」
首页正常打开 静态资源由 nginx 托管,与数据库无关
容器 docker ps 显示 healthy(假象) 容器活着 ≠ 依赖活着
挂了很久没人发现 监控探的是静态首页,永远 200

一句话结论:容器都活着,但它依赖的数据库没了。


二、5 分钟定位(自上而下,逐层排除)

0. 先确定请求经过哪几台机器

nslookup fund.lyforcdf.cn          # 解析到公网入口(本例 119.29.215.19)

1. 分清「前端故障」还是「后端故障」

curl -s -o /dev/null -w "首页=%{http_code}\n" https://fund.lyforcdf.cn/
curl -s -o /dev/null -w "接口=%{http_code}\n" https://fund.lyforcdf.cn/api/public/announcements

首页 200 + 接口 500 → 跳到第 4 步,别再查 nginx。

2. 从反向代理日志拿到具体 500 路径

ssh root@119.29.215.19 "grep ' 500 ' /var/log/nginx/fund.lyforcdf.cn.access.log | tail -20"

一眼看到 /api/public/announcements、/api/account/membership/plans 500, 而 /api/signals/latest 200 → 这两个接口都用 PG,那个不用的走缓存。

3. 确认后端容器状态与端口

ssh root@10.66.66.11 "docker ps --format 'table {{.Names}}\t{{.Status}}'; ss -lntp | grep -E '5001|5173|5432'"

容器 healthy 但 5432 不在监听 → 直指数据库。

4. 抓应用日志拿根因异常

ssh root@10.66.66.11 "docker logs --tail 80 fund_web-api-1 2>&1 | tail -40"
psycopg2.OperationalError: connection to server at "host.docker.internal" (172.17.0.1),
port 5432 failed: Connection refused

→ 容器连宿主机的 5432 被拒,问题在宿主机 PG,不在容器。

5. 查 PG 集群状态

ssh root@10.66.66.11 "pg_lsclusters; ss -lntp | grep 5432 || echo '!! 5432 未监听'"
Ver Cluster Port Status Owner    Data directory
16  main    5432 down   postgres /var/lib/postgresql/16/main     ← down

6. 看 PG 为什么起不来(决定性一步)

ssh root@10.66.66.11 "tail -40 /var/log/postgresql/postgresql-16-main.log"
systemctl status postgresql@16-main --no-pager | head -20

崩溃日志特征:

PANIC:  could not locate a valid checkpoint record
invalid record length at A0B/F72DB198: expected at least 24, got 0

三、根因判定速查表

PG 日志关键字 根因 处理
could not locate a valid checkpoint record WAL 尾部丢失(断电/硬重启最常见) 走第四步 pg_resetwal
could not open file ... No such file or directory 数据文件缺失 先查磁盘/OOM 痕迹,考虑备份恢复
invalid page in block ... of relation 数据页损坏 单表 VACUUM/REINDEX 或备份恢复
No space left on device 磁盘写满 df -h → 清理后重启

旁证(判断损失大小):

# 数据目录/磁盘/OOM
ssh root@10.66.66.11 "df -h /; dmesg -T | grep -iE 'killed process|out of memory' | tail"
# 控制文件里记的检查点位置
ssh root@10.66.66.11 "/usr/lib/postgresql/16/bin/pg_controldata -D /var/lib/postgresql/16/main | head -30"

本例:21:24:57 检查点完成 → 21:25:09 WAL 最后写入 → 断电。 检查点刚刷完数据页 ⇒ 数据损失≈0。这是能放心 pg_resetwal 的依据。


四、恢复流程(先副本验证,再动生产)

STEP 1 · 全量冷备(必做,不许跳)

ssh root@10.66.66.11 "mkdir -p /root/_pg_rescue_$(date +%Y%m%d) && \
  cp -a /var/lib/postgresql/16/main /root/_pg_rescue_$(date +%Y%m%d)/main && \
  cp -a /etc/postgresql/16/main   /root/_pg_rescue_$(date +%Y%m%d)/conf && \
  du -sh /root/_pg_rescue_$(date +%Y%m%d)"

7.4G 大约几分钟。先备份再动手,出任何意外都能退回。

STEP 2 · 在副本上演练

cp -a /root/_pg_rescue_YYYYMMDD/main /var/lib/postgresql/gt_recover
chown -R postgres:postgres /var/lib/postgresql/gt_recover
su postgres -c "/usr/lib/postgresql/16/bin/pg_resetwal -f -D /var/lib/postgresql/gt_recover"

⚠️ Debian 布局三个坑(副本起第二个实例时必踩):

  1. 数据目录里没有 pg_hba.conf —— 它在 /etc/postgresql/16/main/,必须补 hba_file= / ident_file=
  2. external_pid_file 不能置空,要给独立路径
  3. 绝不能让 data_directory 指回生产目录(用 -o "-c data_directory=..." 或改副本内配置)

启动副本并全表堆扫描验证(关索引,专门暴露坏页):

SET enable_indexscan = off; SET enable_bitmapscan = off; SET enable_indexonlyscan = off;
-- 对每张表 SELECT count(*)  ——  能跑完即无坏页

STEP 3 · 副本全绿后,才对生产执行

systemctl stop postgresql@16-main 2>/dev/null

su postgres -c "/usr/lib/postgresql/16/bin/pg_resetwal -f -D /var/lib/postgresql/16/main"
#   → Write-ahead log reset

pg_ctlcluster 16 main start
pg_lsclusters          #   → 16 main 5432 online

docker restart fund_web-api-1 fund_web-sync-1

pg_resetwal -f 会重置 WAL 并丢弃最后未落盘的事务,这就是必须先冷备、先在副本验证的原因。


五、验收清单(逐项过,不绿不算完)

# 1. 集群在线
ssh root@10.66.66.11 "pg_lsclusters"

# 2. 关键表行数与副本/预期一致
ssh root@10.66.66.11 "su postgres -c 'psql -d app2_db -c \"select count(*) from app_users\"'"

# 3. 容器内能连上
ssh root@10.66.66.11 "docker exec fund_web-api-1 python -c \"import psycopg2;print('PG OK')\""

# 4. 三个原 500 接口
for u in /api/public/announcements /api/account/membership/plans /api/signals/latest; do
  printf '%-42s -> %s\n' "$u" "$(curl -s -o /dev/null -w '%{http_code}' -m 20 https://fund.lyforcdf.cn$u)"
done

# 5. 反向代理已无新增 500
ssh root@119.29.215.19 "tail -300 /var/log/nginx/fund.lyforcdf.cn.access.log | grep -c ' 500 '"

验收标准:集群在线 + 行数一致 + 接口全 200 + 日志最后一次 500 停在修复完成之前。


六、善后与预防(三条,第 1 条最要紧)

  1. ★ 把监控探针从首页换成真接口 现在 Uptime-Kuma 探的是纯静态 /,数据库挂了照样 200 —— 这就是挂了 11 小时没人发现的原因。 改为:https://fund.lyforcdf.cn/api/public/announcements
  2. ★ 补 PG 定期备份(本例事故前完全没有备份,零损失纯属运气)。 建议 pg_dump + cron,至少保留 7 天。
  3. 排查硬件/过热:本例根因是机器过热多次硬断电,不解决会复发。

清理:验证无误后 /var/lib/postgresql/gt_recover 可删回收 7.4G; 生产冷备 /root/_pg_rescue_20261014/main 建议保留至少一周。


附:命令速查

pg_lsclusters                                  # 集群状态(第一眼看这个)
pg_ctlcluster 16 main start|stop|restart       # 启停
pg_controldata -D <datadir>                    # 控制文件(检查点位置)
pg_resetwal -n -D <datadir>                    # 干跑,只看不做
pg_resetwal -f -D <datadir>                    # 真正重置 WAL(危险,先备份)
tail -f /var/log/postgresql/postgresql-16-main.log   # PG 日志