事件分级、现场保护与应急决策——枕戈待旦
31 事件分级、现场保护与应急决策——枕戈待旦
前十二章已经建立部署、HA、备份、安全、可观测、容量、调优、维护、迁移与升级能力。 系统真正出事时,这些能力不会自动拼成一次正确响应:告警只呈现某个观察面的症状, 自动化可能继续改变拓扑,人在压力下又容易把第一个解释当成根因。
本章是第 32~35 章的共同控制平面。它不提前穷举所有故障,而是先回答四个问题:
事故早期最稀缺的通常不是命令,而是可信状态与可逆选择。好的响应不以“最快猜到 根因”为目标,而以更快降低用户影响、数据风险和不确定性,同时保留恢复路径为目标。
学习完成标准
完成本章后,读者应能:
- 从用户影响、数据风险、范围、变化速度和可恢复性分级事件;
- 把“恢复数据、恢复拓扑、释放压力、保护完整性”写成明确响应目标;
- 区分严重度与技术判型,不让首发告警直接授权重启、提升或删除;
- 有选择地控制 Patroni、调度器、路由与保留任务,而不是笼统“暂停一切”;
- 采集带 UTC、拓扑、版本、system identifier、timeline、LSN 与来源散列的最小证据;
- 按 PITR、HA、过载和完整性四条路线进入第 32~35 章;
- 用“事实—假设—动作—预期—停止线—回退—结果”记录每次决策;
- 在单人和团队两种模式下完成前十五分钟响应与可读交接;
- 识别必须引入业务、存储、安全、法务或厂商的升级条件;
- 组织一次不触碰生产的盲抽桌面演练,并区分工具通过与人员胜任。
一张图看懂事故控制面
若新证据推翻当前路线,应回到 triage;若动作越过了写入、timeline 或不可逆边界,应更新 回退定义。严重度可以升降,技术路线也可以改变,但每次改变都必须留下事实和时间。
本章目录
31.1 事件分级与响应目标
31.2 第一原则:保护现场与可恢复性
31.3 从症状路由而不是猜根因
- 31.3.1 误删误改与恢复目标 → ch32《PITR 与误操作恢复》
- 31.3.2 主节点、复制或 DCS 异常 → ch33《故障切换与集群重建》
- 31.3.3 连接、延迟、CPU、内存、I/O 或磁盘表象 → ch34《过载保护与资源故障判型》
- 31.3.4 checksum、索引、排序或逻辑不一致 → ch35《数据抢救与工程取证》
31.4 决策、沟通与变更纪律
31.5 单人值守与团队响应
31.6 实战:盲抽症状的桌面演练
写作与验收提示
本章提供八个盲抽场景,每条后续技术路线各两个。正式参考 run 在 Pigsty FULL/L3
监控环境中对 pg-test 做 L0-read-only 采集,再离线完成一份 solo 与一份 team
响应:
验证器拒绝了 31 个声明反例和 18 个现场证据变异,并绑定 12 个实验源文件。公开摘要见
incident-run.json,完整边界见
lab-contract.md。
这次通过只证明只读采集、盲包、响应合同和校验器可执行;参考响应由程序生成,不代表
任何真人通过了能力考核,更没有实施 failover、恢复备份或处理真实事故。
production_ch31_gate 保持 pending。
上一章:推陈出新:版本升级与回滚策略 · 返回下卷导读 · 下一章:PITR 与误操作恢复——妙手回春 · 查看全书目录 · 查看索引中心
31.1 事件分级与响应目标
监控系统每天会产生很多 event,真正需要进入 incident response 的只是其中一部分。 本书把“事件”定义为:用户、数据、恢复能力或关键控制面出现现实风险,需要超出日常 工单节奏的协调、记录和处置。它可以由告警触发,也可以由用户投诉、审计差异、存储 报错或一次危险变更触发。
这个定义故意不要求先知道根因。先建立响应节奏,才有机会在状态继续变化之前保存证据 和恢复选择。NIST SP 800-61r3 也把检测、响应、恢复放进持续的风险管理活动,而不是把 响应理解成一次孤立的“修服务器”任务 (NIST SP 800-61r3)。
31.1.1 用户影响、数据风险、范围与持续时间
用五个轴描述事件
“数据库有问题”不是可以分级的事实。事件声明至少要填五个轴:
| 轴 | 要回答的问题 | 可验证的例子 |
|---|---|---|
| 用户影响 | 谁现在不能完成什么? | checkout 写失败 74%,只读目录正常 |
| 数据风险 | 已提交状态会丢、错、重复或泄漏吗? | 12,400 行被错误更新,外部退款 318 笔 |
| blast radius | 哪些租户、库、表、节点、地域和依赖受影响? | pg-test 写服务;只涉及订单库 |
| time dynamics | 稳定、扩散、振荡还是已停止? | WAL 每分钟增长 3 GiB;错误 job 仍运行 |
| recoverability | 现有副本、WAL、备份和证据还能支持什么? | archive 覆盖事发前 41 小时,尚未试恢复 |
每个结论都应带三个限定:
不要把 unknown 填成 zero。没有发现数据损坏,可能只是还没有做 manifest 或 checksum 验证;监控图没有错误,也可能是 exporter、规则或标签链路已经失效。第 25 章建立的 “signal missing 也是状态”在事故中尤其重要。
严重度是一套组织合同
下面是一种可用的起点,不是 PostgreSQL 的内置标准:
| 等级 | 典型触发 | 协调节奏 |
|---|---|---|
| SEV1 | 核心服务大面积不可用;数据完整性或唯一 writer 不确定 | 立即指挥、持续记录、5~15 分钟更新 |
| SEV2 | 重要功能显著受损;范围受限但可能扩大 | 值班负责人接管、15~30 分钟更新 |
| SEV3 | 局部降级且有可靠绕行,数据风险低 | 工作时段协调并持续观察 |
| SEV4 | 无当前用户影响的缺陷或近失事件 | 正常问题管理 |
真正的阈值必须写入组织策略:多少用户、多少收入、哪类数据、哪个合规义务。数据库 SLO 可以帮助衡量可用性,却不能单独覆盖错误数据、隐私泄露或恢复窗口丢失。
分级不是一次性动作。影响扩大、发现第二个 writer 或确认 archive 断档时要升级;入口 恢复而数据仍不可信时,不能因为 HTTP 200 回来了就降级。
31.1.2 恢复数据、恢复拓扑、释放流量压力、抢救完整性
先说要恢复什么
restart PostgreSQL、promote replica、drop slot 都是动作,不是目标。若没有目标,
团队无法判断动作成功后是否真的改善了局面。第六篇把主要响应目标分成四类:
| 目标 | 典型问题 | 第一优先保护 | 后续章节 |
|---|---|---|---|
| 恢复数据 | 已提交数据被误删、误改或要回到历史边界 | writer、WAL/archive、恢复目标 | ch32 |
| 恢复拓扑 | primary、timeline、DCS 或服务路由不确定 | 唯一 writer、fencing、提交边界 | ch33 |
| 释放压力 | 连接、锁、CPU、内存、I/O、磁盘成为约束 | 管理通道、关键流量、剩余容量 | ch34 |
| 抢救完整性 | page、checksum、WAL、index 或业务等价存疑 | 原始介质、独立副本、证据来源 | ch35 |
一次事件可能同时有多个问题。例如 inactive slot 填满 pg_wal,既威胁可用性,也威胁
恢复能力。此时主要目标可以先定为“释放压力,但禁止直接删除 WAL”;当空间稳定后再
转入 consumer 重建与完整性验证。
响应目标必须可验收
把“尽快恢复”改写成状态断言:
这些状态可以分步达到。为了保护数据,允许先进入 degraded read-only;为了避免满盘, 可以先 shed 非关键写,而不是立即恢复所有流量。关键是把“临时稳定状态”和“最终恢复 状态”分别命名,避免临时措施永久化。
最小化不可逆性
在信息不足时,优先级通常是:
这不是“永远不动作”。磁盘还剩两分钟时必须行动,但动作仍应有精确目标、owner、停止线
和后续代价。例如扩容比删除 pg_wal 更可控;暂停一个错误 job 比停止整个集群更容易
验证;隔离恢复比在主库上直接覆盖更能保留选择。
31.1.3 严重度决定节奏,不替代技术判型
相同症状,可以是四条完全不同的路
“应用连不上数据库”至少可能表示:
它们都可能是 SEV1,但安全动作相反。第二种情况下随机提升副本会把接入故障变成双主; 第三种情况下重启只会暂时清空连接,重试风暴会再次打满;第四种情况下在原介质上反复 启动可能覆盖证据。
因此,严重度只控制:
- 谁必须加入、谁有决策权;
- 更新频率和业务沟通范围;
- 可以接受多大的临时降级;
- 多快需要引入外部专家。
它不告诉你根因,也不降低高风险动作的证据门槛。
把第一个解释当作假设
首发告警应写成:
一次测试的价值不只在“证实”,也在排除。若直连现任 primary 成功,不能直接宣告数据库 健康;它只降低了“postmaster 已停止”的可能性,还要检查服务 backend、writer 唯一性 和提交确认。
两条状态线并行更新
事故记录中应分别维护:
入口恢复可能让影响下降,但若数据等价尚未验证,技术事件仍未关闭。相反,根因尚未知时 也可以通过限流、围栏或只读模式降低影响。把两条线分开,团队才不会为了“找到根因” 延误止血,也不会为了“服务绿了”过早结束调查。
返回本章目录 · 下一节:第一原则:保护现场与可恢复性 · 查看全书目录 · 查看索引中心
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/备份/其他副本补齐、修复步骤能否重复、生产切换后如何对账。能启动只是一条证据, 不是完整性结论。
上一节:事件分级与响应目标 · 返回本章目录 · 下一节:从症状路由而不是猜根因 · 查看全书目录 · 查看索引中心
31.3 从症状路由而不是猜根因
事故初期通常只能看到症状。路由的目的不是在十五分钟内宣布 root cause,而是把事件 交给一组不会立即破坏恢复条件的下一步协议。当前证据足以回答主要响应目标,就可以 先进入对应章节;新证据矛盾时再返回本节重新判型。
先填一行:
31.3.1 误删误改与恢复目标 → ch32《PITR 与误操作恢复》
什么时候进入数据恢复路线
下列证据把问题指向 ch32:
- 已提交的 DML、DDL、job 或业务事件使数据变成错误状态;
- primary 和 physical replicas 都忠实包含同一错误;
- 需要从某个历史时间、XID、LSN 或 restore point 重建旧状态;
- 当前正确写入仍在发生,不能简单让全库倒退;
- 备份与 WAL archive 是否覆盖目标边界尚需证明。
最常见的误判是“副本没延迟,所以可以提升”。物理复制正是把 WAL 中的错误
DELETE/UPDATE 重放到副本;零延迟只说明错误传播完成。
先固定三个边界
告警时间通常晚于 damage start;应用日志时间还可能受时区和时钟漂移影响。优先用事务 审计、DDL log、job run id、业务 marker 和 WAL/LSN 交叉定位。PostgreSQL 连续归档可以 按 time、XID、LSN 或 named restore point 恢复,并会创建新的 timeline (PITR)。
PITR 恢复的是一个 PostgreSQL 集群状态,不是原地“撤销一条 SQL”。生产通常需要:
进入 ch32 前先保护输入
- 围住精确的错误 job、角色或业务写路径;
- 记录 source system identifier、timeline、当前 LSN 与目标时区;
- 确认 base backup、archive min/max、失败记录和 retention owner;
- 禁止清理 WAL、删除备份或覆盖现役主库;
- 枚举 target-only 合法提交与外部副作用;
- 指定业务数据 owner,数据库团队不能代替业务裁决旧值。
若归档覆盖未知,不要先启动一次随意恢复来“试试看”;先把现有 repo 和 WAL 保留下来, 再在独立目标验证。具体恢复目标、timeline 和回灌协议见 第 32 章。
31.3.2 主节点、复制或 DCS 异常 → ch33《故障切换与集群重建》
从四个观察面证明拓扑
“primary down”至少要拆成:
| 观察面 | 关键事实 |
|---|---|
| 客户端与服务 | DNS/VIP/HAProxy/PgBouncer 实际送到谁,旧连接还在哪 |
| PostgreSQL | pg_is_in_recovery()、system identifier、timeline、LSN、可否提交 |
| Patroni 与 DCS | member、leader lock、租约、pause/failsafe、候选状态 |
| 网络与主机 | 节点是否存活、分区方向、存储 lease/fencing 是否成立 |
四层结论不一致时,优先把它当作 writer identity 风险,而不是选择“看起来最新”的节点 提升。特别是旧 primary 与 DCS 多数派隔离时,它可能仍接受直连或遗留 VIP 流量。
PostgreSQL 核心只提供 standby、promotion 与复制机制,不负责完整的故障检测和通知;
官方 failover 文档强调旧 primary 必须有机制知道自己不再是 primary,通常称为
STONITH/fencing,以避免两个节点都认为自己可写
(PostgreSQL Failover)。
在 Pigsty 中应由 Patroni/DCS 与声明的服务机制协调,而不是绕过控制面随意
pg_ctl promote。
failover 前的最低证明
同步复制提高已确认事务的耐久承诺,但仍要根据当时 sync_state、配置和客户端收到的
commit acknowledgment 判断;“lag 面板为 0”不是 fencing 证明。
若观察到两个 timeline 都有独占提交,事件已经同时包含数据 reconciliation。先保存两边
证据,不能用 pg_rewind 把旧分支直接覆盖。pg_rewind 官方也警告失败后的 target
数据目录可能不可恢复,应改用新备份
(pg_rewind)。
完整的候选选择、切换、fencing、旧主重建与服务验收见 第 33 章。
31.3.3 连接、延迟、CPU、内存、I/O 或磁盘表象 → ch34《过载保护与资源故障判型》
可用性故障不一定是 HA 故障
下列症状优先进入资源判型:
- connection timeout,但 primary/route 身份一致;
- pool wait、
max_connections、worker 或 thread pool 达上限; - TPS 下降伴随 lock、client、I/O、WAL flush 等 wait;
- CPU run queue、内存回收/OOM、I/O latency 或文件系统空间异常;
pg_wal、temp、日志、backup 或 telemetry retention 增长;- 单一查询、租户、job 或重试风暴占据大部分资源。
PostgreSQL 官方建议把累计统计与 ps、top、iostat、vmstat 等 OS 观察结合,
因为数据库视图不能完整描述 kernel page cache、设备和调度器
(Monitoring Database Activity)。
按资源链逐层问
单个指标不能宣布根因。CPU 43% 时连接槽仍可耗尽;磁盘吞吐未满时 fsync tail latency
仍可拖慢 commit;ClientRead 可能表示 backend 等客户端,而不是数据库内部繁忙。
state 与 wait_event 也是独立字段,官方提醒两者之间可能出现短暂不一致。
先保住管理面和剩余容量
优先动作通常是:
- 在入口停止异常 deploy、批任务或非关键重试;
- 保留管理连接,设置诊断查询超时;
- 分批 cancel 精确 query,必要时再 terminate 已复核 session;
- 限制新工作而不是把
max_connections一路调大; - 扩容或释放已声明的应急预留,不手删数据库文件;
- 对 slot、archive、replica 和 backup 分别确认 owner 后再改变保留。
Pigsty 故障指南把数据、pg_wal、日志、本地备份、监控数据和对象存储列为不同的空间
来源
(Pigsty Troubleshooting)。先用
df 确认文件系统,再按目录类别和增长率定位;du 很重时也要限制范围和优先级。
绝不能直接删除 pg_wal 中看似旧的文件。
过载中的负载保护、连接排空、磁盘急救、内存与 I/O 判型见 第 34 章。
31.3.4 checksum、索引、排序或逻辑不一致 → ch35《数据抢救与工程取证》
先区分“不一致在哪一层”
| 证据 | 主要回答 | 不能单独证明 |
|---|---|---|
| data checksum | 读取到的物理 page 是否匹配页内 checksum | 业务值正确、所有页都已读 |
amcheck |
B-tree/heap 结构的特定不变量 | 存储设备健康、业务副本等价 |
| collation version + dependency | 排序 provider 版本与依赖对象范围 | 索引已经重建、冲突值已裁决 |
| seq/index result comparison | 某查询路径是否出现差异 | 全库无其他损坏 |
| business manifest | 行数、摘要、外键、金额等业务不变量 | page/WAL 物理完好 |
| backup restore | 某份备份能恢复并读取声明对象 | 当前 primary 正确、其他时间窗可恢复 |
任何一项失败,都先记录 database、relation、fork、block、timeline、LSN、查询路径和 首次时间。任何一项通过,也只缩小它所覆盖的故障面。
修复会覆盖原因
发现 index scan 漏行时立刻 REINDEX,可能恢复服务,却同时丢掉:
- 修复前 seq/index 集合;
- 原 relfilenode 与受影响 block;
- collation 版本和升级动作顺序;
- 存储错误、checksum 与其他副本对照;
- 业务上冲突键的裁决机会。
更安全的顺序是:
amcheck 提供 B-tree 检查和 verify_heapam,但不同函数的锁、代价与误报/漏报边界不同,
应按版本阅读
amcheck 文档并先在克隆测量。
pg_checksums --check 要求集群 cleanly shut down,不能在现役 primary 上临时跑
(pg_checksums)。
若一个 page 的候选来自 replica 或备份,仍不能直接复制进 live data file;WAL、tuple visibility、FSM/VM、索引和业务约束可能不在同一边界。具体的隔离、抢救、证据链和 重建协议见第 35 章。
上一节:第一原则:保护现场与可恢复性 · 返回本章目录 · 下一节:决策、沟通与变更纪律 · 查看全书目录 · 查看索引中心
31.4 决策、沟通与变更纪律
故障不会因为开了 incident channel 就停止变化。没有统一决策格式时,聊天记录很快会 混合事实、猜测、建议、已经执行的命令和转述结果;十分钟后,团队甚至无法回答“谁在 哪台机器上改了什么”。响应纪律的作用不是增加仪式,而是让并行思考最后汇聚成一个 可审计的系统状态转移。
31.4.1 事实、假设、动作、预期与停止条件
五类记录不能混写
| 类型 | 含义 | 示例 |
|---|---|---|
| fact | 有来源、时间和范围的直接观察 | 02:03Z 写端点 503;直连 primary 只读探针成功 |
| hypothesis | 对事实的可证伪解释 | HAProxy health credential 可能失效 |
| decision | 在不确定性下选择目标或约束 | 在证明 writer 唯一前禁止 promote |
| action | 获授权并实际执行的状态改变或测试 | 回退 exact health-check secret version |
| result | 动作后重新观察到的事实 | 三个 backend 中仅 DCS leader healthy |
“Patroni 有问题,已处理”不属于任何合格类型。它没有时间、来源、动作和结果,也让接班 人无法判断现在是否安全。
把每个动作写成有界实验
一次可执行动作至少包含:
例如:
预期结果必须是可以再观察的状态,不是“应该修好”。停止条件写在命令之前,因为执行后
人容易被 sunk cost 推着继续。没有可行 rollback 时,要明确写
irreversible after <boundary>,而不是填一个虚假的“恢复备份”。
时间线记录的是认识变化
早期假设错误并不可耻,偷偷改写历史才危险。保留被推翻的假设和证据,可以解释为什么 没有执行另一条路径,也能在复盘中发现告警或 runbook 的诱导性。
所有时间以 UTC 为主,并记录 collector clock。若应用、主机和数据库时钟有偏差,先 保存各自时间再估算 offset,不要直接修改原始日志时间。
31.4.2 单一指挥、记录员、执行者与业务接口
单一指挥不等于单一思考
团队模式至少有四个逻辑角色:
| 角色 | 负责 | 不负责 |
|---|---|---|
| incident commander | 目标、优先级、风险门、角色与更新节奏 | 亲自解释所有技术细节 |
| operator | 预检、读回目标、执行一个获批动作、报告原始结果 | 私自扩大范围或并行试命令 |
| scribe | UTC 时间线、证据 ID、决定、结果和待办 | 把聊天摘要伪装成事实 |
| business liaison | 用户影响、业务优先级、外部副作用和状态更新 | 替数据库团队选择技术命令 |
还可以增加 HA、storage、security、application 等 subject-matter expert。NIST SP 800-61r3 强调现代响应依赖领导、事件处理者、技术人员、法律、公共沟通和资产 owner 等多方参与;这些角色要由一个协调实体汇合,而不是各自行动。
IC 不必是职位最高或 PostgreSQL 最熟的人,而应能维护共享状态、拒绝未经验证的高风险 动作、召集正确 owner。技术负责人可以给出候选方案,最终由 IC 根据业务目标和授权 选择状态转移。
一次只允许一个命令 owner
可以并行:
- 一组读取 Patroni/DCS;
- 一组确认应用影响和最近变更;
- 一组检查 backup/archive;
- scribe 持续整理时间线。
不能并行:
- 两个人同时修改同一 cluster;
- 一边 failover,一边重启旧 primary;
- 一边 drop slot,一边尝试恢复 consumer;
- 一边回切路由,一边解除写围栏。
每个有副作用的系统在一个时刻只有一个 operator token。执行前读回:
执行者贴回 exit status、时间与证据引用,不只说“done”。讨论频道、命令频道和业务状态 频道最好分开,避免建议被误当命令、原始日志被转发给无权限人员。
对外更新只说已知边界
一条合格更新包含:
不要为了显得确定而宣布未经验证的 root cause 或恢复时间;也不要把技术日志直接扔给 业务方。固定下一次更新时间可以减少无序追问,让 operator 保持注意力。
31.4.3 高风险动作的复核、审批与回退
风险分级针对动作,不针对职位
| 级别 | 例子 | 最低控制 |
|---|---|---|
| R0 观察 | 短超时聚合 SQL、只读 REST、配置 hash | 范围、来源、timeout |
| R1 可逆 containment | 限制一个 job、摘掉一类非关键流量 | exact target、owner、验证、恢复条件 |
| R2 受控状态变更 | terminate 精确会话、有限且可回退的路由调整、drop exact rebuildable slot、隔离 failover 演练 | IC 批准、独立技术复核、回退/补偿 |
| R3 生产敏感/潜在不可逆 | 生产 failover 或 authority cutover、覆盖恢复、rewind、pg_resetwal、手工页修复 |
保存原件、明确授权、双人 command review、停止线 |
熟练 DBA 执行 R3 仍然是 R3。SEV1 可以缩短等待,但不能让 system identifier、 timeline、目标路径和 writer fencing 变得不重要。
批准之前解析最终目标
审批材料不能只有模板变量:
最终执行包应包含:
- 当前证据与目标状态;
- exact host/service/database/object/PID/path;
- 命令渲染结果,而不是未解析变量、glob 或 command substitution;
- 前置条件和权限;
- 预计持续时间、负载与观察查询;
- stop condition;
- rollback、compensation 或不可逆边界;
- operator、reviewer 与 IC 的时间戳。
审批证明组织授权,不证明命令技术上正确。reviewer 必须独立检查目标、方向和恢复前提, 不能只回一个“LGTM”。
回退、补偿和重建不是同义词
| 类型 | 含义 | 例子 |
|---|---|---|
| rollback | 恢复原状态且没有新独占事实 | 目标写入前切回 copy-mode 旧集群 |
| compensation | 原动作无法抹去,新增反向业务动作 | 已发退款后执行会计冲正 |
| rebuild | 丢弃一个非权威副本,从权威来源重建 | failover 后重建旧 primary 为 replica |
DROP replication slot 无法 rollback;只能让 consumer 从新快照重建。目标数据库已接受
独占写后,切回旧库需要反向对账,不再是路由 rollback。pg_rewind 失败后不能假设重跑
会恢复 target;官方建议此时取新备份。
高风险动作完成后必须运行独立的 verifier。执行命令的人报告成功还不够:服务 owner 验证端点,数据 owner 验证业务 manifest,HA owner 验证 writer/timeline,backup owner 验证归档与恢复链。最后再决定扩大流量或进入观察窗口。
上一节:从症状路由而不是猜根因 · 返回本章目录 · 下一节:单人值守与团队响应 · 查看全书目录 · 查看索引中心
31.5 单人值守与团队响应
同一响应协议既要能被凌晨单人值守执行,也要能在几十人加入时保持一致。差别不在技术 正确性:单人把角色按时间串行切换,团队把只读调查并行化;两种模式都要有一个状态、 一个时间线、一个命令 owner 和明确的升级条件。
31.5.1 单人时先稳定、记录,再逐级升级
前十五分钟按顺序切换角色
| 分钟 | 逻辑角色 | 产物 |
|---|---|---|
| 0~2 | IC | incident ID、UTC 起点、当前用户影响、下一次更新时间 |
| 2~5 | scribe | 首发事实、告警来源、最近变更、当前自动化和 writer 身份 |
| 5~9 | operator | L0 只读现场包;连接/HA/资源/完整性最小证据 |
| 9~12 | technical lead | 候选路线、被排除路线、第一安全动作与 stop line |
| 12~15 | liaison | 呼叫所需 owner、发布 known/unknown/impact/next update |
一个人可以戴四顶帽子,但要按顺序。先把“准备执行什么”写进时间线,再切到 shell; 执行后贴原始 exit status 和 verifier 结果,再回到记录。多开几个终端不会让一个人获得 真正的独立复核,只会增加连错环境和忘记动作的概率。
单人第一目标是保持可操作
- 保留一个管理连接或 out-of-band 路径;
- 给诊断 SQL、SSH 和 HTTP probe 设置短 timeout;
- 先停止精确的错误来源,不做全局“重启看看”;
- 把当前 cluster、node、role、system id、timeline 放在命令前;
- 不在普通聊天粘贴连接串、原始 SQL、业务行或私钥;
- 每五分钟停一次,问“现有动作是否仍匹配响应目标”。
若手已经在高压下连续操作,最有价值的动作往往是叫醒第二个人。独立读回一个 target 可以阻止方向反了的 failover、误删 slot 或在错误 data directory 上恢复。
预先写死升级触发器
单人不能因为“还没完全搞清楚”而无限延迟呼叫。下列任一项应立即升级:
真正存在“几分钟后满盘”的倒计时,也只授权最小保护动作,例如按声明 marker 释放
emergency reserve 或在入口 shed load;它不自动授权删除 pg_wal。按组织 break-glass
政策执行后,必须立刻补齐 owner、证据和独立复核。
防范单人认知陷阱
| 陷阱 | 自检问题 |
|---|---|
| anchoring | 除首发告警外,还有哪两个解释? |
| action bias | 不做这条命令,未来五分钟会丢掉什么? |
| confirmation bias | 哪条最便宜的证据会推翻我的路线? |
| sunk cost | stop condition 是否已经触发? |
| fatigue | 我能否准确复述 target、方向和不可逆边界? |
如果最后一个问题答不清,应停止有副作用的操作并交接,而不是靠咖啡继续。
31.5.2 团队时避免多人同时改同一系统
两分钟内建立响应拓扑
人员不足时可以合并角色,人员过多时也不要复制角色。新加入者先读当前摘要和 stop line, 再领取一个问题;不要从头重复所有探针或提出第五次“要不要重启”。
并行问题,不并行状态改变
一个好的分工可能是:
这些 track 默认 R0。任何人提出 R1~R3 动作,都先写成 decision packet;IC 指定唯一 operator 和 verifier。若已有动作执行中,新动作必须说明是等待、互斥还是可以安全并行。
“一个人重启 pool、另一个人切流、第三个人 terminate session”无法从结果中辨别哪个 动作有效,还可能在连接排空过程中制造未知事务。
使用 read-back 和 closed loop
IC:
operator 复述 target 和条件,执行后报告:
verifier 从独立观察面确认。只有 IC 宣布状态转移后,时间线才从 authorized 变成
completed/verified。这样可以避免“某人说 done”被误读为业务已恢复。
交接要传递未决风险
轮班摘要不应只列已做事项:
接班 operator 对关键身份做一次 read-back。长事件要安排休息和轮换;疲劳是会改变系统 风险的运行条件,不是个人意志问题。
31.5.3 何时必须请求业务、存储、安全或厂商协助
技术 owner 不能替代语义 owner
| 需要谁 | 强制触发条件 | 需要对方决定什么 |
|---|---|---|
| 业务/数据 owner | 错误值、target-only 写、退款/通知等副作用 | 权威状态、补偿规则、可接受降级 |
| 应用 owner | 重试、连接池、幂等键、deploy/job 相关 | 围栏范围、回放安全、客户端验收 |
| 存储/基础设施 | media error、snapshot、空间、fsync/latency 异常 | 一致快照、fencing、扩容与设备替换 |
| 网络/DCS owner | leader lock、分区、VIP/route 不一致 | 多数派、网络围栏和路径恢复 |
| 安全/法务/隐私 | 凭据泄漏、恶意变更、受监管数据、证据可能用于调查 | 保全、通知、访问范围和 chain of custody |
| 扩展/厂商 | 非核心插件、内核崩溃、未文档格式或支持合同相关 | 已知缺陷、受支持恢复路径、补丁 |
数据库团队可以证明“这 77 行在恢复点之后被合法修改”,却不能决定哪一笔退款该撤销; 存储团队可以生成 crash-consistent snapshot,却不能宣称业务事务完整;厂商可以建议 修复步骤,最终生产授权仍属于本组织。
外部求助包要小而完整
发送前先写一个明确问题:
随包提供:
- incident ID、版本、OS/架构、扩展与精确错误;
- system identifier、timeline/LSN 和简化拓扑;
- 已执行动作、结果和 stop line;
- 最小可复现样本或隔离工作副本;
- 相关日志的精确时间窗、散列与脱敏说明;
- 业务影响和所需答复时限。
不要默认发送整个 postgresql.conf、pg_hba.conf、Patroni YAML、完整日志、core dump
或业务表;它们可能含凭据、连接串、SQL、参数值和个人数据。先按合同、法律和最小必要
原则确认接收渠道与访问权限。
提前定义“必须停手”
以下情况应停止本地修复尝试,转为保存、隔离和专家协作:
- 无法确认目标 data directory 的 system identifier 或 timeline;
- 两个 writer 都有独占提交;
pg_resetwal、手工 page copy、catalog hack 等成为唯一候选;pg_rewind已失败,target 状态未知;- 存储错误仍增长,工作副本来源不可靠;
- 扩展数据格式或加密密钥不受当前团队理解;
- 任何动作可能影响法定通知或证据可采性。
及时升级不是“把事故甩出去”。IC 仍要跟踪请求 ID、对方假设、建议适用版本、执行 授权和验证结果,并把外部建议纳入同一决策日志。
上一节:决策、沟通与变更纪律 · 返回本章目录 · 下一节:实战:盲抽症状的桌面演练 · 查看全书目录 · 查看索引中心
31.6 实战:盲抽症状的桌面演练
本节不对数据库制造故障。它先从 Pigsty FULL/L3 沙箱采集一份最小只读上下文,再从
八个场景中盲抽两个,把本章协议压缩进前十五分钟。这里有两个容易混淆的 “level”:
参考 run 的响应由程序生成,用来证明合同和 validator 自洽;真正的人员训练必须由主持人 只发盲包、按请求提供证据卡并独立评分。
31.6.1 随机症状、误导性首发告警与缺失信息
先读合同
完整边界见
lab-contract.md,环境与验收条件见
requirements.json,响应协议见
incident-contract.json,拓扑与路由见
topology.mmd,参与者填写
response-template.json。
先做纯静态检查:
完整参考 run 需要连接已经确认的四节点开发沙箱,但在线阶段只有只读 SQL、Patroni REST
和 pgBackRest info:
也可以分步执行:
capture 显式使用 BEGIN READ ONLY、短 statement/lock timeout,只保存聚合会话、复制、
slot、archive、control identity 和缩减后的 HA/backup 上下文。它不读取业务行、不保存
query text 或原始日志。exercise 完全离线,不 pause Patroni、不 failover、不重启、不
terminate connection、不改路由、不恢复 backup。
八个场景让首发症状“不够用”
| 场景 | 首发误导 | 正确主路线 |
|---|---|---|
| 订单数下降、复制延迟为零 | 把同步副本误当历史副本 | PITR |
| 数据库健康、退款突然增长 | 把技术健康误当语义正确 | PITR |
| 写端点 503、primary up | 把接入故障误当 primary 故障 | HA |
| DCS timeout、节点自称 primary | 相信单节点自报角色 | HA |
| connection timeout、CPU 43% | 只看 CPU,忽略连接槽和 pool | OVERLOAD |
| 满盘且 replica lag | 把 lag 当根因,想手删 WAL | OVERLOAD |
| checksum mismatch、lag 为零 | 把同步状态当完整性证明 | INTEGRITY |
| seq/index 结果不同 | 把 REFRESH VERSION 当重建 |
INTEGRITY |
每个 blind packet 只包含:
- initial signal 与已报告用户影响;
- 可以请求的证据卡 ID、问题、层次和时间成本;
- 十五分钟 deadline 与作答规则。
它不会包含 hidden truth、严重度答案、route、required card、safe/dangerous action 或
证据 observation。facilitator-pack.json 才保存这些内容。场景源文件本身是公开教材,
所以这种隔离是教学流程而非密码学保密;正式考核应由主持人控制文件访问或使用私有
派生场景。
“没有证据”也要进入记录
参与者请求一张暂时不可得的证据卡时,主持人应回答:
而不是替换成“正常”。参与者要决定是换独立观察面、升级权限 owner、缩小动作,还是 因为关键事实缺失而保持 stop line。事故能力的一部分就是在证据不完整时拒绝不安全的 确定性。
31.6.2 分别按单人和团队模式完成前十五分钟
主持方式
- 主持人运行参考工具并单独保管 evidence directory;
- 只把一个 blind packet 与空白 response template 发给参与者;
- 参与者先声明五轴影响和当前 severity;
- 参与者按 ID 请求证据卡,主持人从 facilitator pack 逐张返回 observation;
- 每个有副作用的建议都要求 risk、owner、expected、stop、rollback;
- 第 15 分钟停止,参与者发布状态更新并完成 handoff;
- 最后才揭示 hidden truth、required cards、safe/dangerous actions。
参考 run 固定 seed 抽到:
随机 seed 可改变题目,但 runner 会保证两题属于不同路线。不要重复抽到熟题就假装完成 盲测;应记录 seed、场景使用历史和参与者是否见过答案。
solo 与 team 使用同一张答卷
solo 模式四个 role 都由 solo-oncall 承担,但按时间串行切换;team 模式要求 IC、
operator、scribe、business liaison 是四个不同 actor。两者都必须做到:
团队可以让多个 R0 track 并行,但仍只有一个 operator token;单人不能用“我自己复核过” 冒充独立 R2/R3 review。
一份可复用评分表
| 项目 | 分值 | 失分例 |
|---|---|---|
| 五轴影响与 severity | 15 | 把 unknown 写成无影响 |
| 现场和恢复能力保护 | 20 | 未围住 writer;建议删除 WAL |
| 跨层证据选择 | 20 | 只看一张 dashboard |
| 技术 route 与目标 | 15 | severity 直接决定 failover |
| 决策日志与 stop line | 15 | 动作无预期、回退和结果 |
| 角色、升级与沟通 | 10 | 多人同时改;不报 unknown |
| 机密与生产边界 | 5 | 粘贴凭据;宣称已获生产授权 |
发生以下任一项应直接判定需要重练,不用总分掩盖:
- 在证明 writer 唯一前 promote;
- 删除现役
pg_wal; - 在原始证据上执行覆盖式修复;
- 未经业务 owner 直接覆盖事后合法写入;
- 把 facilitator 未提供的信息自行当成事实。
程序生成的 responses.json 只是一份 schema-complete reference,不是答题者成绩。真人
评分要保存自己的 response、主持人发卡时间和讨论录音/记录权限,并由未参与执行的人
复核。
31.6.3 在 Pigsty L3 输出时间线、决策日志、证据包与路由选择
正式参考现场
正式 run 在 pg-test 捕获:
现场的 pg_stat_archiver.failed_count 为 21,但 last failure 在
2026-07-29T18:57:58Z,last archived success 在
2026-07-30T01:36:16Z。这正说明累计失败数不能直接解释成“当前归档故障”。本章不因
看到 21 就 reset 统计或修改 archive;它保留 reset 与时间,交给真实趋势和 backup
证据解释。
“repo 中有 6 份 backup”也不等于恢复通过。本次没有执行 restore,公开结论只写
pgbackrest_status=0 和 backup count;第 21、32 章的恢复证据才回答可恢复性。
私有证据与公开摘要分层
证据目录包含:
目录权限为 0700、文件为 0600。review 拒绝 credential URI、SCRAM verifier、
private key、clear password、raw SQL/log field,并检查 preflight、盲包、答案、
exercise 与 public summary 属于同一 run。
公开参考摘要见
incident-run.json。它只保留环境轮廓、路线、安全
边界与验收计数,不公开 facilitator 答案和 live 细节。
反例验证防止“格式漂亮的伪响应”
正式 validator 要求:
它会拒绝:
- severity 被允许直接选择 route;
- blind packet 泄露 hidden truth 或 expected route;
- required evidence 未齐就决策;
- solo/team role 语义不成立;
- action 属于 dangerous set 或晚于 minute 15;
- decision log 缺 expected/stop/rollback;
- live capture 不是 READ ONLY、cluster/timeline/topology 不匹配;
- backup system identifier 不同;
- source/upstream/evidence hash 被替换;
- 任何 production gate 被打开。
对同一份私有证据可以重复:
但修改实验源文件、盲包或 response 后,不能继续复用旧 exercise hash;应开启新 run, 保留旧包作为历史。
本章交付物不是根因报告
合格的第 31 章 handoff 应包括:
参考 run 最终结论是:
下一章从第一条路线开始:面对已经提交的误删误改,怎样选准恢复目标、在隔离实例完成 PITR,并把历史正确状态与事后合法写入重新合并。
上一节:单人值守与团队响应 · 返回本章目录 · 下一章:PITR 与误操作恢复——妙手回春 · 查看全书目录 · 查看索引中心