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/plans500, 而/api/signals/latest200 → 这两个接口都用 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 ← down6. 看 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 布局三个坑(副本起第二个实例时必踩):
- 数据目录里没有
pg_hba.conf—— 它在/etc/postgresql/16/main/,必须补hba_file=/ident_file= external_pid_file不能置空,要给独立路径- 绝不能让
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 条最要紧)
- ★ 把监控探针从首页换成真接口
现在 Uptime-Kuma 探的是纯静态
/,数据库挂了照样 200 —— 这就是挂了 11 小时没人发现的原因。 改为:https://fund.lyforcdf.cn/api/public/announcements - ★ 补 PG 定期备份(本例事故前完全没有备份,零损失纯属运气)。
建议
pg_dump+ cron,至少保留 7 天。 - 排查硬件/过热:本例根因是机器过热多次硬断电,不解决会复发。
清理:验证无误后 /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 日志