31.2 第一原则:保护现场与可恢复性
事故现场不是静止的。应用在重试,Patroni 在判断角色,WAL 在产生和回收,日志在轮转, backup retention 在清理历史,运维人员的查询本身也会留下连接和日志。所谓“保护现场” 不是把所有东西停住,而是知道谁还会改变什么,有选择地围住最危险的状态转移,并 为恢复保留必要的 WAL、备份、拓扑和业务边界。
31.2.1 暂停自动化、危险变更与证据覆盖
先列控制器,再决定是否暂停
Pigsty 集群中常见的主动控制器包括:
| 控制器 | 可能继续做什么 | 盲目停止的代价 |
|---|---|---|
| Patroni + DCS | 角色管理、重启、failover、配置协调 | 丧失自动保护;pause 也不是 fencing |
| HAProxy / VIP / pool | 根据健康检查改变流量去向 | 现有连接与新连接可能走不同节点 |
| 应用重试与 job | 继续 DML、DDL、回填、消费消息 | 错误或负载继续扩大 |
| WAL archiver / backup | 保存恢复链、执行保留策略 | 停 archive 可能填满 pg_wal 并缩短恢复窗口 |
| autovacuum / maintenance | 清理 tuple、冻结 XID、重建对象 | 全局停用会引入膨胀和 wraparound 风险 |
| 日志轮转与 telemetry retention | 覆盖旧日志、聚合或丢弃高基数字段 | 取证窗口变短;全部开启 debug 又可能泄密或打满盘 |
正确的“冻结记录”应逐项写:
优先围住制造新歧义的 writer、重试和错误 job。不要为了“干净现场”关闭 WAL 归档、删除 旧备份或把所有监控改成 debug;这些动作可能同时破坏恢复能力和磁盘余量。
Patroni pause 不是“时间停止”
Patroni 的 pause mode 适合大版本升级、损坏恢复等需要暂时脱离自动管理的特殊操作,但 其官方语义很具体:
- member key 和 primary 的 leader lock 仍会更新;
- Patroni 仍可能对运行中的成员做只读查询;
- 人工 restart、failover/switchover 和 reinitialize 仍可执行;
- 发现 parallel primaries 时只告警,并不会自动 demote 无 leader lock 的 primary;
- PostgreSQL 停止后不会被自动拉起。
所以 patronictl pause 既不是写围栏,也不是禁止所有人工动作。使用前必须先记录成员、
leader、timeline、路由和现有 pause 状态,明确谁负责 resume,并验证旧 writer 已在
网络、存储或服务层被独立围住。单纯停止 Patroni 进程更不是维护模式。
暂停要有过期条件
每个临时控制都要有:
没有恢复条件的临时限流会变成永久容量损失;遗忘的 Patroni pause 会让后续故障不再 自动恢复;遗忘的 archive/retention 变更会在几小时后制造第二次事故。事件结束条件中 必须包含“所有临时控制已复位或进入有 owner 的计划变更”。
31.2.2 记录时间、拓扑、版本、告警与最近变更
建立 incident envelope
第一份现场包不需要“把服务器全抄走”,但必须能回答:
system_identifier 区分不同 PostgreSQL 集群,timeline 识别 failover 后的历史分支,LSN
描述同一 timeline 上的位置;三者不能互相替代。连接到“一个叫 production 的端点”
并不能证明取到了预期数据副本。
运行中的 PostgreSQL 可以在短超时只读事务中取 control identity:
离线数据目录或只读取证副本可以使用
pg_controldata,但报告中
必须写清楚读取的是哪个路径、当时是否运行、工具版本和文件来源,不能把两个节点的
输出拼成一条时间线。
动态状态、累计统计和日志不是同一种证据
PostgreSQL
pg_stat_activity
描述当前 backend;pg_stat_replication 描述当前 walsender;pg_stat_database、
pg_stat_archiver 等是累计计数。官方文档指出累计统计不会瞬时更新,并且默认在同一
事务内缓存;clean shutdown 可以保存统计,而 crash、base backup 启动和 PITR 会重置
累计计数
(Cumulative Statistics System)。
因此:
failed_count = 21不等于当前 archive 正在失败,要比较 reset、last failure、 last success 和当前 WAL;deadlocks = 0必须同时记录stats_reset;- 一张事务内的静态快照适合关联字段,连续变化率则要用多个有时间的样本;
- 采集活动时默认按 state、wait 和 age 聚合,原始 SQL、bind value、client address 只在必要且获授权时进入受控证据。
PostgreSQL 日志还有轮转与覆盖策略;log_truncate_on_rotation 在特定文件命名下可以
周期性覆盖旧内容
(PostgreSQL Logging)。
应先复制事故时间窗内的精确文件并计算散列,而不是先改 logging 配置或执行一次会刷屏
的诊断。
在 Pigsty 中跨层对齐
Pigsty 的 FULL/L3 监控把 PostgreSQL、PgBouncer、Patroni、HAProxy、主机与日志用
cls、ins、ip 等标签关联。事故时可以从 PGSQL Alert → Cluster → Service /
Patroni / Replication / Persist → Instance / Session / Query 逐层缩小,而不是只看
一张总览
(Pigsty Dashboard)。
命令行可以用:
获取主机、PostgreSQL、Patroni、pgBackRest 与扩展上下文 (pig context)。把它当采集起点而非唯一 真相:还要用 SQL role、Patroni REST、DCS 和客户端端点交叉验证。配置和 secret 文件 通常只保存版本号、权限和 SHA-256,不把内容直接放进普通事件频道。
证据清单本身也要可审计
NIST SP 800-61r3 要求保留 incident data 的完整性和 provenance,同时保护响应记录的 机密性。散列只能证明“以后看到的 bytes 没变”,不能证明采集命令正确、时钟准确或源 本身可信;这些信息要由 provenance 补齐。
31.2.3 先克隆、隔离或只读,再做破坏性尝试
保存原件,实验只在工作副本
可能改写数据页、WAL、catalog 或时间线的动作,先问:
推荐分成三份:
| 副本 | 目的 | 允许动作 |
|---|---|---|
| 原始证据 | 保留最早可得状态 | 不直接启动或修复;受控访问 |
| 工作副本 | 执行 amcheck、WAL 分析、恢复和修复试验 |
可销毁、可重新生成 |
| 恢复候选 | 通过验证后准备回灌或接管 | 只执行 runbook 声明动作 |
“把目录复制了一份”并不自动得到有效 physical backup。运行中对 PGDATA 做普通文件复制 可能跨越不同页和 WAL 时刻;带 tablespace 的集群还可能跨多个文件系统。使用 pgBackRest、PostgreSQL backup API 或由存储平台保证一致性的原子快照,并记录其 crash-consistent / application-consistent 语义。
副本不等于历史
物理 replica 会重放主库已经提交的错误 DELETE,不能当成误操作前的时间胶囊;
共享故障域中的存储损坏也可能影响多个副本。每份候选都要独立标注:
只有这些边界与恢复目标匹配,副本才有资格成为数据来源。
“只读”有多个层次
- SQL
BEGIN READ ONLY阻止普通数据库写事务,但不冻结其他会话、WAL replay 或后台 状态变化; - 只读业务路由只约束经过该端点的应用,不约束 owner、scheduler 或直连;
- 文件系统只读保护 bytes,却可能使 PostgreSQL 无法完成 crash recovery,不能在 原始取证挂载上强行启动;
- snapshot clone 通常是独立可写工作副本,但 CoW 底层和保留策略仍要记录。
因此应保存不可变原件,再从它派生可写工作副本,而不是把“只读”当作万能开关。
pg_waldump主要用于 debug 和教学,
官方还提醒在 server 运行时可能给出错误结果。对事故 WAL 的分析应固定工具版本、
timeline 和 segment 范围,优先在复制出的 WAL 上完成;不要为了让工具读取而随意改名
或移动现役 pg_wal 文件。
破坏性尝试必须留下退出线
下列动作默认只在工作副本:
即便工作副本“修好了”,也要再回答:丢了哪些 tuple、违反了哪些业务不变量、能否从 WAL/备份/其他副本补齐、修复步骤能否重复、生产切换后如何对账。能启动只是一条证据, 不是完整性结论。
上一节:事件分级与响应目标 · 返回本章目录 · 下一节:从症状路由而不是猜根因 · 查看全书目录 · 查看索引中心