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 又可能泄密或打满盘 |
正确的“冻结记录”应逐项写:
controller refund-consumer / Patroni / backup retention
observed_state running, config hash abc..., owner team-x
exact_scope run_id=backfill-20260730 only
reason stop new external side effects
expected_effect queue remains retained; other consumers continue
resume_owner business owner
resume_condition reconciliation manifest approved优先围住制造新歧义的 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 进程更不是维护模式。
暂停要有过期条件
每个临时控制都要有:
start UTC
owner
exact target
automatic expiry or review time
health signal while paused
resume command and verifier没有恢复条件的临时限流会变成永久容量损失;遗忘的 Patroni pause 会让后续故障不再 自动恢复;遗忘的 archive/retention 变更会在几小时后制造第二次事故。事件结束条件中 必须包含“所有临时控制已复位或进入有 owner 的计划变更”。
31.2.2 记录时间、拓扑、版本、告警与最近变更
建立 incident envelope
第一份现场包不需要“把服务器全抄走”,但必须能回答:
incident id / collector / UTC / clock source
environment / cluster / service endpoint
PostgreSQL version / system identifier / timeline / LSN
primary and replica observations / Patroni and DCS state
HAProxy / pool / VIP route
backup stanza / latest backup / archive boundary
recent deploy / DDL / config / secret / storage / network change
active user impact and business marker
every artifact's source, collection command, hash and access classsystem_identifier 区分不同 PostgreSQL 集群,timeline 识别 failover 后的历史分支,LSN
描述同一 timeline 上的位置;三者不能互相替代。连接到“一个叫 production 的端点”
并不能证明取到了预期数据副本。
运行中的 PostgreSQL 可以在短超时只读事务中取 control identity:
\set ON_ERROR_STOP on
SET statement_timeout = '5s';
SET lock_timeout = '500ms';
SET default_transaction_read_only = on;
BEGIN READ ONLY;
SELECT
clock_timestamp() AS observed_at,
current_setting('cluster_name') AS cluster_name,
current_setting('server_version') AS server_version,
pg_is_in_recovery() AS in_recovery,
s.system_identifier,
c.timeline_id,
c.checkpoint_lsn,
c.redo_lsn,
c.checkpoint_time
FROM pg_control_system() AS s
CROSS JOIN pg_control_checkpoint() AS c;
COMMIT;离线数据目录或只读取证副本可以使用
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)。
命令行可以用:
pig context -o json获取主机、PostgreSQL、Patroni、pgBackRest 与扩展上下文 (pig context)。把它当采集起点而非唯一 真相:还要用 SQL role、Patroni REST、DCS 和客户端端点交叉验证。配置和 secret 文件 通常只保存版本号、权限和 SHA-256,不把内容直接放进普通事件频道。
证据清单本身也要可审计
artifact patroni-members.json
observed_at 2026-07-30T02:05:03.441Z
collector oncall-a
source three declared REST endpoints
scope state / role / version / timeline only
sha256 ...
redaction connection_url and tags removed
access incident-restrictedNIST SP 800-61r3 要求保留 incident data 的完整性和 provenance,同时保护响应记录的 机密性。散列只能证明“以后看到的 bytes 没变”,不能证明采集命令正确、时钟准确或源 本身可信;这些信息要由 provenance 补齐。
31.2.3 先克隆、隔离或只读,再做破坏性尝试
保存原件,实验只在工作副本
可能改写数据页、WAL、catalog 或时间线的动作,先问:
原始状态是否已有独立副本?
这个副本在什么一致性边界创建?
数据目录、tablespace 和 WAL 是否属于同一快照?
工作副本是否与生产网络和路由隔离?
若动作失败,原始副本和恢复链是否仍可用?推荐分成三份:
| 副本 | 目的 | 允许动作 |
|---|---|---|
| 原始证据 | 保留最早可得状态 | 不直接启动或修复;受控访问 |
| 工作副本 | 执行 amcheck、WAL 分析、恢复和修复试验 | 可销毁、可重新生成 |
| 恢复候选 | 通过验证后准备回灌或接管 | 只执行 runbook 声明动作 |
“把目录复制了一份”并不自动得到有效 physical backup。运行中对 PGDATA 做普通文件复制 可能跨越不同页和 WAL 时刻;带 tablespace 的集群还可能跨多个文件系统。使用 pgBackRest、PostgreSQL backup API 或由存储平台保证一致性的原子快照,并记录其 crash-consistent / application-consistent 语义。
副本不等于历史
物理 replica 会重放主库已经提交的错误 DELETE,不能当成误操作前的时间胶囊;
共享故障域中的存储损坏也可能影响多个副本。每份候选都要独立标注:
system identifier
timeline and fork point
last replayed / checkpoint LSN
capture time and clock
checksum state
backup and archive ancestry
business manifest只有这些边界与恢复目标匹配,副本才有资格成为数据来源。
“只读”有多个层次
- 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 文件。
破坏性尝试必须留下退出线
下列动作默认只在工作副本:
zero_damaged_pages
pg_resetwal
手工 page/file copy
强制 catalog 修改
无来源约束的 REINDEX / VACUUM FULL
覆盖式 PITR
失败后继续使用同一个 pg_rewind target即便工作副本“修好了”,也要再回答:丢了哪些 tuple、违反了哪些业务不变量、能否从 WAL/备份/其他副本补齐、修复步骤能否重复、生产切换后如何对账。能启动只是一条证据, 不是完整性结论。
上一节:事件分级与响应目标 · 返回本章目录 · 下一节:从症状路由而不是猜根因 · 查看全书目录 · 查看索引中心