独立开发中·📚18 篇日记·📝最近更新:2026年07月12日
🔥

凌晨3点的数据库崩溃:78分钟全平台宕机

WAL日志塞满磁盘导致PostgreSQL崩溃,七个平台全部不可用。

水哥
2026-07-099 分钟阅读

那一刻的窒息感

凌晨三点十七分,手机开始疯狂震动。

Sentry 的告警通知像鞭炮一样涌进来。短短两分钟内,七个平台的报错量爆了。错误日志里反复出现同一句话:

Error: connect ECONNREFUSED 127.0.0.1:5432

数据库挂了。

我光着脚跑进书房,屏幕上 PostgreSQL 的进程状态是 crashed。所有平台全部不可用——登录、支付、内容发布、消息推送,全部停摆。

第一反应:重启

先重启 PostgreSQL 服务:

sudo systemctl restart postgresql
# Failed! Error: disk full

磁盘满了。df -h 一看,/var/lib/postgresql 挂载的磁盘 100% 满了。

追根溯源:WAL 日志爆炸

PostgreSQL 的 WAL(Write-Ahead Log)日志目录占了 180GB。原因是三天前我开启了 log_statement = 'all' 来做性能分析,但忘了关掉,也忘了设置日志轮转。

-- 我三天前执行的命令
ALTER SYSTEM SET log_statement = 'all';
SELECT pg_reload_conf();

-- 但我忘了这些!
-- 没有设置日志轮转
-- 没有设置日志大小限制
-- 没有设置自动清理

三天内,150 万条 SQL 日志塞满了磁盘。PostgreSQL 写入到一半发现磁盘满了,直接崩溃退出。数据库的文件系统可能已经损坏了。

恢复过程(一次次绝望)

尝试 1:清理日志

rm -rf /var/lib/postgresql/14/main/pg_log/*
# 释放了 180GB

重启 PostgreSQL——还是起不来。磁盘满了导致最后的 WAL 文件残缺,PostgreSQL 拒绝启动。

尝试 2:pg_resetwal

pg_resetwal -f /var/lib/postgresql/14/main
# Transaction log reset

这相当于给数据库做心脏起搏器——能用,但可能丢失最后几条事务。好在是凌晨三点,基本没用户操作。

重启数据库,起来了!

但是…… SELECT count(*) FROM users 执行了 8 秒。索引全坏了。WAL 不完整导致很多索引状态脏了。

尝试 3:重建索引

REINDEX DATABASE water_db;
# 等了 12 分钟

索引重建完成。查询恢复正常。最后检查一遍数据完整性:

-- 检查有没有脏数据
SELECT count(*) FROM orders WHERE created_at IS NULL;
-- 0 条,谢天谢地

SELECT pg_last_wal_receive_lsn() - pg_last_wal_replay_lsn() AS lag;
-- 0,主从同步正常

凌晨四点半,全部平台恢复。七个平台一共宕机了 78 分钟。

事后的补救措施

第二天睡了三小时,下午开始重建灾备体系:

1. 日志自动轮转

# postgresql.conf
log_rotation_age = 1d       # 每天轮转
log_rotation_size = 100MB   # 超过100MB就轮转
log_truncate_on_rotation = on

2. 磁盘监控 + 告警

# crontab:每5分钟检查磁盘使用率
*/5 * * * * [ $(df /var/lib/postgresql | tail -1 | awk '{print $5}' | tr -d '%') -gt 85 ] && /usr/local/bin/alert-disk-full

3. 慢日志只记录关键语句

-- 生产环境只用这个级别
ALTER SYSTEM SET log_min_duration_statement = 1000; -- 只记录 1 秒以上的慢查询
ALTER SYSTEM SET log_statement = 'none';            -- 关掉全量查询日志

4. 自动化备份验证

之前我每周手动备份,但从不验证备份有效性。现在加了自动恢复测试:

#!/bin/bash
# 每周日凌晨自动恢复备份到测试库,验证数据完整性
pg_restore --dbname=water_db_test --schema-only latest.dump
if [ $? -ne 0 ]; then
  curl -X POST "https://api.xxx.com/alert" -d '{"msg":"Database backup validation FAILED"}'
fi

5. 运维手册

把这次事故的排查步骤和恢复命令写成了文档,保存在 Git 仓库里。下次再出事——虽然我希望不会有下次——至少不用在凌晨三点从零开始排查。

刻在骨子里的教训

数据库运维铁律

□ 开启任何全局日志前,先确认轮转策略
□ 磁盘使用率不能超过 80%
□ 备份恢复必须定期自动化验证
□ 凌晨三点是所有事故的高发期
□ 每个运维操作,做到一半要反问自己:如果现在拔电源,会怎样?

心态上的变化

这件事之前,我对运维的态度是"能用就行"。觉得"又不是大厂,没必要搞得那么复杂"。

但用户不会因为你是个人开发者就原谅你宕机。 一个用户在凌晨三点打开你的平台发现打不开,他不会想"哦这个开发者只有一个人",他只会想"这个平台真烂"然后关掉。

可靠性不是看你是不是大厂,是看你对用户的态度。

写在最后

这次事故的成本:

  • 直接损失:78 分钟全平台宕机
  • 数据损失:三条订单记录由于 WAL 残缺无法恢复(幸好凌晨三点下单的本来就很少)
  • 心理损失:至今每次凌晨收到手机震动都会心跳加速

但换来的是一套可靠的运维体系,和一颗敬畏生产环境的心。

做独立开发,你是一个人扛所有。代码你要写,产品你要定,运维你也要兜底。但正是这些"半夜爬起来救火"的时刻,让你从一个写代码的人,变成了一个真正负责的人。

相关文章