望闻问切:监控体系与可观测诊断
25 望闻问切:监控体系与可观测诊断
“有监控”很容易,“知道发生了什么”很难。
一个看起来成熟的平台可能同时拥有:
事故发生时却仍然只能说:
这些句子描述了现象,没有回答五个决定性问题:
- 用户旅程是否真的失败、变慢、读旧或产生错误结果?
- 这是首发症状、伴随现象,还是已经被证据支持的机制?
- 观察数据是零、尚未刷新、被重置、被采样,还是根本缺失?
- 哪个 owner 应采取什么首个安全动作?
- 什么证据能够证明恢复,而不是仅仅“面板变绿”?
第 24 章先定义了服务、SLI、SLO、控制目标、缺失语义和告警治理。本章做下一步:
这条链的重点不是“多收集一些数据”,而是让每个结论都能说明:
本章目标
完成本章后,你应当能够:
- 从用户、入口、数据库和主机四层组织观察问题;
- 区分症状信号、原因信号、控制信号与监控系统自身信号;
- 说明指标、日志、事件和追踪各自适合回答什么;
- 为 label cardinality、采样、保留和查询成本建立预算;
- 把“没有数据”区分为无流量、采集故障、查询错误、延迟与真实零值;
- 正确读取
pg_stat_activity、pg_locks与 wait event; - 使用
pg_stat_database、pg_stat_io、pg_stat_wal、pg_stat_checkpointer和pg_stat_archiver; - 解释累计计数、瞬时状态、估算值和进度视图的不同时间语义;
- 区分 WAL distance、时间 lag 与 commit-correlated freshness;
- 观察 autovacuum、冻结年龄、dead tuple、对象增长和维护进度;
- 正确解释
pg_stat_statements的聚合键、reset、deallocation 与权限; - 解释为什么 normalized query text 仍不能随意进入证据;
- 为慢语句、锁等待、临时文件和错误日志设置有界政策;
- 评估
auto_explain的ANALYZE、timing、采样和参数泄露成本; - 把第 24 章 SLO 合同变成 multiwindow、multi-burn-rate 规则;
- 区分 page、ticket、diagnostic 与 proposed test route;
- 为 alert 的
for、分组、抑制、恢复和缺失语义写测试; - 防止 fast burn、slow burn 与预算工单形成重复风暴;
- 防止 metamonitoring 抑制独立用户症状或正确性告警;
- 理解 Pigsty v4 的 VictoriaMetrics、VictoriaLogs、VictoriaTraces、 VMAlert、Alertmanager、Grafana、pg_exporter 与 Vector;
- 用
cls、ins、ip、database 与 queryid 在不同粒度间下钻; - 从仪表盘回到 PostgreSQL SQL、日志和主机事实复核;
- 自动保存有界、私密、可复验的诊断包;
- 区分首发症状、相关现象、候选机制与根因;
- 限制诊断查询的 timeout、并发、结果量和权限;
- 用隔离时间序列验证 pending、firing、recovery 与 missing;
- 用空 receiver 离线验证路由,不触碰真实 pager;
- 输出覆盖矩阵,诚实标出尚未实现的应用 SLI;
- 对当前沙箱给出“机制通过、生产待决”的可审计结论。
本章不做什么
本章不是一个“复制几十条 PromQL 就上线”的规则包,也不会:
- 把当前沙箱阈值包装成所有生产环境的通用答案;
- 修改在线 VMAlert、Alertmanager、Grafana 或 PostgreSQL;
- 向在线 Alertmanager 提交合成告警;
- 接入 webhook、邮件、短信、Slack 或真实 pager;
- 运行
EXPLAIN ANALYZE、压测、统计 reset 或数据库故障注入; - 导出 query text、bind value、日志正文、client address 或凭据;
- 用
pg_up=1证明订单服务可用; - 用
pg_lag=0证明 read-your-writes; - 用
failed_count>0直接证明归档仍在失败; - 用“当前无告警”证明通知链可达;
- 用一次仪表盘相关性宣布 root cause;
- 声称
pg36_shop已经具有真实应用埋点。
实验使用 pg36_shop 这一 synthetic teaching service。应用层五种信号仍是刻意
保留的缺口:
缺口不是失败的写作,而是本章必须保留的事实。如果没有应用事件,平台不能从 数据库组件指标“推算”出一个看似完整的用户 SLO。
从监控到可观测诊断
监控回答已知问题
监控通常从一个已知条件出发:
它适合稳定、可计算、可自动执行的问题:
- SLO 是否快速燃烧;
- exporter 是否持续不可达;
- 规则是否持续报错;
- 恢复证据是否超过政策期限;
- 容量预测是否进入评审窗口。
可观测诊断解释未知状态
诊断从“不知道为什么”出发,需要沿不同证据层缩小假设:
可观测性不是一个产品名,也不是“拥有 logs + metrics + traces”自动获得的属性。 它要求系统输出足够的、语义明确的证据,让操作者能区分多个竞争解释。
诊断不等于根因
本章采用四层语言:
| 层次 | 可以说什么 | 例子 |
|---|---|---|
| 首发症状 | 最早被可靠观察到的服务偏离 | availability fast burn 先触发 |
| 伴随现象 | 与症状同窗出现 | pool queue、lock wait 同时升高 |
| 候选机制 | 现象与某机制一致 | 长事务可能阻止 vacuum 推进 |
| 根因证据 | 机制被复现/独立证实,反例被排除,修复验证闭环 | 释放特定锁后等待消失且合成路径恢复 |
“两条线一起升高”最多是相关性。要升级为根因,至少需要:
四层问题,而不是四套孤岛面板
本章把信号按问题分为四层:
| 层 | 主要问题 | 首选事实 |
|---|---|---|
| 用户 | 旅程是否成功、及时、正确、足够新鲜 | eligible events、合成探针、domain reconciliation |
| 入口 | 请求在哪排队、被路由、重试或拒绝 | 应用 edge、HAProxy、PgBouncer |
| 数据库 | PG 状态能否解释症状 | activity、locks、I/O、WAL、maintenance、queryid |
| 主机 | 资源或基础设施是否构成约束 | CPU、memory、disk、network、clock |
从下往上推断很危险:
从上往下诊断更稳健:
正确性与恢复就绪又是特殊控制:
四种信号,各有边界
| 信号 | 擅长 | 不擅长 | 必须声明 |
|---|---|---|---|
| metric | 趋势、比率、聚合、规则 | 高维上下文、单请求故事 | type、unit、label、reset、missing |
| log | 离散事件、错误上下文、状态迁移 | 完整总体比率、无界扫描 | schema、采样、脱敏、保留 |
| event | 发布、切换、配置和所有权时间线 | 单独证明因果 | actor、target、result、clock |
| trace | 单次跨组件路径 | 未采样总体、长期预算 | sample、baggage、PII、correlation |
四者不是竞争关系:
同样,也不应强迫每个问题都使用四种信号。一个能够由累计计数精确回答的问题, 不需要先扫描全部日志;一个需要参数上下文的问题,也不应把 error text 做成 metric label。
时间语义比数值更重要
观察数据至少有五种时间性质:
| 类型 | 示例 | 典型陷阱 |
|---|---|---|
| 当前状态 | pg_stat_activity.state |
一次采样漏掉短暂事件 |
| 累计计数 | pg_stat_database.xact_commit |
把总数当速率、忽略 reset |
| 滚动窗口 | rate(counter[5m]) |
窗口过短、采样不足 |
| 估算值 | n_dead_tup |
当成精确 bloat |
| 当前进度 | pg_stat_progress_vacuum |
没有行不等于从未运行 |
还要处理采集链延迟:
如果数据源有意延迟 30 秒,而规则在“当前时刻”查询,最新样本可能尚未可见。 VMAlert 支持 evaluation delay;正确值取决于实际采集和存储延迟,不能机械照抄。
本章的规则分层
实验生成 18 条记录规则和 13 条告警规则。它们分成三类:
第 24 章已经接受的七条
| 告警 | 路由 | 目的 |
|---|---|---|
PG36ShopAvailabilityFastBurn |
page | 1h + 5m,14.4x |
PG36ShopAvailabilitySlowBurn |
page | 6h + 30m,6x |
PG36ShopAvailabilityBudgetTicket |
ticket | 3d + 6h,1x |
PG36ShopFreshnessFastBurn |
page | commit-correlated freshness |
PG36ShopCorrectnessMismatch |
page | 不允许平均稀释的正确性 |
PG36ShopCapacityHorizon |
ticket | 经评审预测才进入工作队列 |
PG36MonitoringPathBroken |
page | 观察或通知路径不可证明工作 |
仍待治理接受的六条
它们有完整规则和测试,但统一标记:
“代码写好了”不等于“组织接受了 page/ticket 政策”。这个显式不一致检查很重要: 第 24 章定义了 latency SLO,却没有接受 latency alert candidate。本章没有暗中 补齐生产政策,而是把它暴露为待决项。
诊断记录
复制距离、长事务、freeze age、dead tuples、exporter 状态、VMAlert rule error 和 notification failure 首先是诊断或控制输入。它们不会因为“容易写阈值”就 自动变成 page。
当前沙箱的真实快照
正式实验公共摘要:
observability-run.json。
采集时间为 2026-07-29T22:48:21Z。快照只代表该时刻:
| 项目 | 观察值 |
|---|---|
| Pigsty | v4.5.0 |
| PostgreSQL | 18.6 |
| pg_exporter | v1.4.0 |
| VictoriaMetrics | v1.148.0 |
| VictoriaLogs | v1.52.0 |
| VictoriaTraces | v0.9.4 |
| Alertmanager | 0.33.1 |
| VictoriaMetrics series | 44,842 |
| live VMAlert groups | 17 |
| live alert rules | 50 |
| live recording rules | 698 |
| live rule errors | 0 |
| current VMAlert alerts | 0 |
pg_up / pg_exporter_up instances |
4 / 4 |
pg36_shop_* application SLI series |
0 |
目标身份通过三层交叉确认:
0 bytes 是当时的发送—回放距离,不是 read-your-writes 证明。
pg_stat_statements 快照:
归档快照有一个关键反例:
如果规则只是:
它会在系统已经恢复后永久报警,直到统计被重置。候选规则因此同时检查:
再回到 pgBackRest 与恢复证据复核。累计 counter、当前故障和恢复就绪是三个 不同结论。
实验为什么分成在线与隔离两部分
在线只读基线
在线部分只做:
- HTTP health/API/metrics 读取;
- VictoriaMetrics 即时查询;
- 通过元节点进入真实
pg-test-1; - 带
statement_timeout=5s、lock_timeout=500ms的只读 SQL; - 聚合 activity、locks、I/O、WAL、checkpointer、archiver、
replication 和
pg_stat_statements; - 记录版本、reset、freshness 与缺口。
不会 reset、reload、写表、运行计划、制造负载或读取 query text。
隔离规则与路由
规则文件上传到沙箱元节点的:
随后:
vmalert -dryRun检查规则语法;vmalert-tool启动 loopback-only VictoriaMetrics;- 注入合成时间序列;
- 验证 normal、pending、firing、recovery 和 missing;
amtool check-config检查 Alertmanager 配置;- 用八组标签离线解析到空 receiver;
- 用五个用例验证抑制边界;
- 清理临时目录并确认目录不存在。
它不会接触在线 VMAlert 或在线 Alertmanager。
本章目录
25.1 从问题选择可观测信号
25.2 PostgreSQL 核心运行信号
25.3 SQL 可观测基线
- 25.3.1
pg_stat_statements的统计口径与重置 - 25.3.2 慢语句、锁等待、临时文件与错误日志
- 25.3.3
auto_explain的采样、嵌套语句与开销 - 25.3.4 日志不得泄漏密码、令牌和敏感参数
25.4 把观察契约变成告警
25.5 Pigsty 可观测体系
25.6 从告警到诊断包
25.7 实战:实现并演练观察契约
官方资料
本章技术语义优先回到原始文档:
- PostgreSQL 18 Monitoring Database Activity
- PostgreSQL 18 Cumulative Statistics
- PostgreSQL 18
pg_stat_statements - PostgreSQL 18
auto_explain - PostgreSQL 18 Error Reporting and Logging
- PostgreSQL 18 Routine Vacuuming
- Pigsty PostgreSQL Monitoring
- Pigsty PostgreSQL Dashboards
- Pigsty pg_exporter
- VictoriaMetrics VMAlert
- VictoriaMetrics vmalert-tool
- Prometheus Alerting Rules
- Prometheus Unit Testing Rules
- Alertmanager Configuration
下一章 第 26 章 容量规划与压测基线 会在这些观察 语义之上建立需求模型、容量水位和可比较基线;第 28 章 VACUUM、冻结与膨胀治理 再深入维护信号。第 31 章把本章的诊断包作为故障排查入口,处理慢查询、锁、 连接、复制与磁盘等具体事件。
上一章:纲举目张:SLO、SOP 与组织治理 · 返回下卷导读 · 下一章:胸有成竹:容量规划与压测基线 · 查看全书目录 · 查看索引中心
25.1 从问题选择可观测信号
先选指标,再问它能说明什么,是监控系统膨胀的主要原因。
更可靠的顺序是:
例如,“副本 lag 多大”不是一个完整问题。它可能对应三种完全不同的决定:
| 决定 | 真正问题 | 需要的信号 |
|---|---|---|
| 是否把 read-after-write 流量路由到副本 | 已知 commit 是否在该读取路径可见 | commit token probe |
| 是否有 WAL 保留风险 | primary 与 replica 的 LSN 距离是否持续扩大 | replication position + WAL retention |
| 是否解释查询结果陈旧 | 用户查询走了哪条路径、返回哪个版本 | routing event + application semantics |
一个 pg_lag 无法同时替代这三个答案。
本节建立一份问题驱动的信号合同。可执行版本见
signal-contract.json。
25.1.1 用户体验、服务入口、数据库与主机四层
第一层:用户是否获得了正确服务
用户层不是浏览器 RUM 的同义词,而是最接近服务承诺的测量点。对
pg36_shop:
这些定义直接决定分子和分母:
$$ \text{availability bad ratio}
\frac{\text{failed or unreconciled eligible attempts}} {\text{all eligible attempts}} $$
如果只收 HTTP 2xx/5xx,会漏掉:
- 返回 200,但事务后来失败;
- 客户端超时,但写入已经提交;
- 重试创建了两个订单;
- 响应成功,但订单属于错误租户;
- primary 写入成功,随后从副本读取不到;
- 应用在 admission 前正确拒绝无效请求。
所以用户层信号常常要组合两个测量点:
eligible event 必须先定义
没有 eligible 分母,错误率会随着流量分类任意变化。例如:
“用户断开”是否计入也不能临场决定:
正确性不是可用性的一部分
如果 10,000,000 个订单中有一行串租:
因此正确性使用 control signal:
它需要:
invariant是有界枚举;- reconciliation 有输入边界;
- 结果有 hash 或不可变运行记录;
- stale/missing 本身是控制失败;
- 修复后由独立核对确认,而不是把 gauge 手工设为零。
第二层:请求在哪个入口发生了什么
入口层回答的是路径问题:
每个入口有不同的拒绝、排队和重试语义:
| 入口 | 关键问题 | 典型证据 |
|---|---|---|
| application edge | 请求是否 admission、是否重试、最终 outcome | request counter、duration histogram、release id |
| HAProxy | 选择了哪个 backend、健康检查如何判断 | backend/session/queue、routing event |
| PgBouncer | 等待 server connection 还是正在执行 | client/server/pool state、wait duration |
| PostgreSQL | backend 在执行、等待还是 idle in transaction | activity、wait event、locks |
“连接数高”可能代表:
如果没有入口身份与状态,单个总数无法区分。
用 operation class,避免用 endpoint 爆炸
应用指标需要能分段,但不能把完整 URL 做标签:
operation class 是有界业务语义;原始 path 可能包含 tenant、order 和 token, 既造成 cardinality 爆炸,也造成数据泄露。
第三层:PostgreSQL 能否解释用户症状
数据库层分成当前状态与累计事实:
它们回答:
- 请求是否在 PostgreSQL 内;
- backend 在 CPU 上运行还是等待;
- 等待是 lock、I/O、client、WAL、buffer pin 还是其他类别;
- transaction 已持续多久;
- 哪个 queryid 消耗时间、I/O、临时块或 WAL;
- checkpoint 写入和同步成本如何变化;
- WAL 生成、发送、归档和回放是否推进;
- vacuum/freeze 是否被阻止;
- dead tuple、对象大小和统计新鲜度如何变化。
它们不直接回答:
- 用户请求是不是 eligible;
- 应用是否返回了正确结果;
- 哪个 tenant 受到影响;
- 用户是否走了 replica read path;
- 恢复点是否被业务接受。
数据库信号是解释层,不是服务层的替代品。
current 与 cumulative 必须分开
下面两个事实可以同时成立:
前者是“现在没有”,后者是“窗口内曾经发生”。不能因为 current view 已经恢复, 就否定累计异常;也不能因为累计 counter 非零,就宣称当前仍在发生。
第四层:主机是否构成资源约束
主机层观察:
- CPU utilization、run queue、steal;
- memory pressure、swap、OOM;
- filesystem capacity、inode、mount state;
- block-device latency、queue、throughput;
- network loss、retransmit、bandwidth;
- clock synchronization;
- process、cgroup、systemd 和 kernel event。
主机指标要与 PostgreSQL 语义交叉:
主机指标很适合 falsify 假设。例如,若 PostgreSQL 认为 I/O 时间增加,而设备 层没有对应变化,可能是 OS cache、采集时间窗、虚拟化层或统计口径不同,而不是 直接得出“磁盘坏了”。
四层不是固定下钻顺序
通常从用户症状向下,但也有例外:
正确原则不是“永远只看用户”,而是:
原因和容量信号默认进入 diagnostic 或 ticket。
建立问题卡
每个新信号先填一张问题卡:
若填不出 decision、missing 和 action,这个信号还不适合变成告警。
反例:从副本时间戳推用户新鲜度
常见快捷方式:
在一个空闲副本上,最近没有 WAL,它可能显示“很久以前”;但副本其实完全 追平。持续写入时,它又只能说明最后一次 replay 的时间,不知道用户关心的 commit 是否已经可见。
新鲜度需要:
WAL distance 和 replay timestamp 仍有诊断价值,但不能替代 commit correlation。
25.1.2 指标、日志、事件和追踪各回答什么
metric:把总体变成可计算时间序列
metric 最适合:
- event ratio;
- latency distribution;
- rate、increase 和 trend;
- 容量 horizon;
- 规则自动评估;
- 多实例聚合。
四种常见类型:
| 类型 | 语义 | PostgreSQL/Pigsty 示例 | 主要陷阱 |
|---|---|---|---|
| counter | 只增,进程/reset 后重置 | transaction、deadlock、WAL byte | 直接比较总值 |
| gauge | 可升可降的当前/最近值 | connections、lag、object size | 对瞬时噪声 page |
| histogram | bucket counter + count/sum | request duration | bucket 不一致、聚合错误 |
| summary | 客户端计算 quantile | 某些应用延迟 | quantile 难以跨实例聚合 |
counter 要转成窗口
不要:
除非语义明确要求“生命周期内从未失败”。大多数运行告警关心的是新失败与当前 推进,而不是历史存在。
gauge 需要稳定条件
evidence age 是 gauge,但它不是“数据库坏了”。它是恢复控制未满足,应进入 change gate 或 ticket,除非业务政策另有紧急定义。
histogram 要从 bucket 算事件比率
延迟 SLO 目标是 99% 在 250 ms 内:
$$ \text{latency bad ratio}
1 - \frac{\text{rate}(\text{bucket}_{le=0.25})} {\text{rate}(\text{count})} $$
它与 p99 不完全等价。event-based SLO 直接计算“多少 eligible events 超标”; quantile 是分布位置,适合探索,但不一定能直接算错误预算。
metric contract 的最小字段
没有 type,就不知道能否 rate();没有 reset,就不知道突然下降是改善还是
重启;没有 missing,就可能把 absence 变成健康。
log:保存离散上下文
日志适合回答:
- 哪个错误类别发生;
- 状态何时迁移;
- lock wait 在
deadlock_timeout后是否被记录; - 哪个 temporary file 被创建;
- Patroni 何时改变角色;
- pgBackRest 哪一步失败;
- 配置 reload 或连接认证发生什么。
日志不适合作为默认总体统计:
因为:
- 日志行不等于 eligible event;
- multiline 和格式变化影响计数;
- sampling 会丢事件;
- rotation/retention 改变分母;
- 查询成本随数据量增长;
- 原始 SQL、参数和用户信息可能泄露。
structured log 仍然需要 schema
推荐将字段分为:
restricted payload 不应自动复制到 alert annotation、ticket 或公开 evidence。 “JSON 格式”只解决解析,不解决敏感性。
event:把变化放进时间线
发布、切换、配置和容量变化最好是结构化 event:
它能帮助回答:
event 本身仍不证明因果。一个发布接近事故,只是强候选,需要机制和修复验证。
event 的 clock 与 identity
事件至少记录:
- stable event id;
- actor role,而不是随意字符串;
- exact target;
- UTC 时间和时间源;
- request、approval、execution、result;
- rollback/roll-forward reference;
- source integrity。
若主机时钟相差两分钟,所谓“先发生”会被颠倒。诊断包要同时保存:
trace:跟随单次路径
trace 能展示:
它适合:
- 一次请求在哪里耗时;
- 重试和 fan-out 如何展开;
- 哪个 dependency 返回错误;
- sampled slow path 与正常 path 有何不同。
它不适合单独算完整 SLO:
- 采样意味着不是全部事件;
- tail sampling 会改变总体;
- trace backend 丢失不能解释为“无慢请求”;
- baggage 可能携带 tenant、token 或 PII;
- 数据库 span 未必包含真实执行等待。
不要把 SQL 文本塞进 span
推荐:
谨慎或禁止:
queryid 也不是绝对安全身份:它是 hash,可能冲突;相同文本在不同
search_path 下语义也可能不同。它的价值是聚合和关联,不是授权或数据分类。
四种信号如何组合
一个 latency fast burn 的调查:
这组证据支持“入口池排队”而不是“数据库执行变慢”。如果回滚池配置后:
- queue 恢复;
- trace pool wait 恢复;
- user latency windows 恢复;
- 没有 correctness mismatch;
因果证据才明显增强。
选择最便宜的充分信号
同一问题可能有多种来源:
如果只需要总体 rate,优先 counter;若要解释一类错误,再查询有界日志;若要 追单次跨服务路径,再使用 trace。不要因为存储便宜就永久收集一切。
25.1.3 标签基数、采样、保留与缺失数据
cardinality 是维度乘积
一个 metric 的 series 数近似为:
假设:
则:
如果再加:
系统不仅会爆炸,还会把业务标识复制到监控存储。
bounded label allowlist
本章允许:
禁止:
release 虽然有界,也要限制同时保留多少版本;滚动发布若不断产生唯一 commit
SHA,而旧 series 长期不消失,仍会增长。
Pigsty 的 pg_query_* 指标有一个需特别说明的例外:label 名为 query,值是
数值型 queryid,而不是 SQL 文本。它的上限受 pg_stat_statements.max 约束;
仍要结合 database,并禁止把 raw query text 填进同名 label。
label 与 annotation 的边界
label 用于:
- series identity;
- query aggregation;
- alert grouping/routing;
- silence matcher。
annotation 用于人读的说明,不参与 series identity。但 annotation 也不能放 秘密。一个安全模式:
不要:
先测 cardinality,再加维度
增加 label 前回答:
- 值域是否有硬上限?
- 谁控制它?
- 每个值保留多久?
- query 与 dashboard 是否真实使用?
- alert 是否需要它来路由?
- 是否能在日志/trace 中按需查,而不进入 metric?
- 该字段是否是 PII、secret 或业务主键?
若只是“以后可能有用”,默认不加。
sampling 必须改变措辞
日志或 trace 被采样后,允许说:
不允许说:
采样合同至少包括:
- head、tail 或 probabilistic;
- sample rate;
- error 是否强制保留;
- rate 随流量是否变化;
- dropped count;
- decision point;
- 是否能重建总体;
- 变更历史。
PostgreSQL log_min_duration_sample 与 log_statement_sample_rate 也是采样政策。
如果前者关闭,后者值为 1 并不代表所有语句都会被采样记录。
retention 决定能否回答窗口问题
若 SLO 是 rolling 28d,而原始 SLI 只保留 7d:
可以使用长期 recording rule,但必须记录:
- 原始数据保留多久;
- 聚合数据保留多久;
- rule expression 的版本;
- label 是否在聚合时丢失;
- reset/缺口如何进入结果;
- 回填是否发生。
诊断与治理保留期也不同:
| 数据 | 典型目的 | 关注点 |
|---|---|---|
| 高频 raw metric | 近期诊断 | 容量大、粒度高 |
| recording rule | 长期趋势/SLO | 语义版本 |
| log body | 事件上下文 | 敏感、访问、删除 |
| trace | sampled path | 成本与 PII |
| incident evidence | 决策与复盘 | 完整性、最小化 |
“永久保存以备万一”通常同时违反成本和隐私原则。
缺失数据至少有七种解释
还有 counter reset、staleness marker 和查询窗口不足。
absent() 不是万能答案
判断 SLI missing 需要一个独立期望:
如果服务夜间没有流量,request counter 没新样本不一定故障。可以用:
- 独立 synthetic probe;
- admission counter;
- deployment/inventory declaration;
- scrape target freshness;
- expected schedule。
关键是不能用被监控对象自身同时证明“应该有数据”和“数据存在”。
zero、empty、NaN、stale 与 error
| 结果 | 含义 |
|---|---|
0 |
series 存在,当前值或计算结果为零 |
| empty vector | selector 没匹配 series |
NaN |
运算未定义,例如 0/0 |
| stale | 时序被标记过期 |
| query error | 规则没有获得结果 |
把 empty vector 用 or vector(0) 填零可能很危险:
如果 bad ratio 消失是 exporter 故障,这会把“未知”变成“100% 健康”。只有在 零值语义、identity join 与独立 freshness 都被证明时,才能安全补零。
低流量与分母
本章记录规则没有用一个任意常数把分母抬高:
低流量时要显式决策:
- 使用更长窗口;
- 同时要求 minimum event count;
- 使用 synthetic probe;
- 保持 unknown;
- 用 ticket 而不是 page;
- 聚合到更稳定的 operation class。
若写:
每秒不到一个请求时,会改变真实 event ratio。clamp_min 可以防数值问题,
不能免费替代低流量政策。
counter reset
对 counter 使用 rate()/increase() 可以处理正常 reset,但仍要观察:
- reset 是否过于频繁;
- target identity 是否变化;
- scrape window 是否跨越长缺口;
- process restart 是否是事故的一部分;
- 历史 recording rule 是否有断点。
PostgreSQL 统计也有 reset:
数值突然变小,必须先问 reset,而不是直接说“负载下降”。
监控系统也要被监控
至少观察:
- exporter/scrape freshness;
- VictoriaMetrics query 与 ingestion;
- VMAlert group/rule error;
- missed evaluation;
- Alertmanager notification failure;
- 独立 canary 的 receipt;
- external blackbox。
本章现场快照显示:
这只能证明计数器当前没有记录失败。没有一条最近被 receiver 确认收到的 canary, 不能宣称真实通知链可达。
current lab cardinality snapshot
同一快照中:
这些是容量基线,不是目标上限。应当记录随时间的:
一个新 exporter 可能“工作正常”,但在一周内把 series 增长十倍。
信号引入评审
上线前用下面的表:
| 问题 | 必须通过 |
|---|---|
| question | 能写成一个可证伪问题 |
| decision | 观察结果会改变具体决定 |
| source | producer 和采集路径明确 |
| type | counter/gauge/histogram/event/log/trace 明确 |
| labels | 有界、必要、无 secret/PII |
| time | scrape、window、delay、reset 明确 |
| missing | 不会默认为健康 |
| cost | series、bytes、query、retention 有预算 |
| access | 最小权限与脱敏明确 |
| action | owner、首个安全动作、停止线明确 |
| test | 正常、异常、缺失、恢复均可重放 |
| retirement | rename/deprecation 有迁移方案 |
本节验收
你应当能够对任意一个候选指标回答:
如果只能回答“Grafana 上有这条线”,信号合同还没有建立。
25.2 PostgreSQL 核心运行信号
PostgreSQL 自带两类观察接口:
查询视图很简单,正确解释并不简单。以下事实可以同时成立:
本节目标不是记住所有列,而是掌握一套读法:
完整视图以当前版本官方文档为准: PostgreSQL 18 Monitoring Stats。
25.2.1 会话、事务、等待与锁
先确认统计功能是否开启
关键设置:
它们不是同一个开关:
| 设置 | 作用 | 关闭后的含义 |
|---|---|---|
track_activities |
当前命令与开始时间 | activity 信息受限 |
track_counts |
数据库/表等累计活动 | autovacuum 也依赖它 |
track_functions |
函数调用统计 | 不代表函数没有运行 |
track_io_timing |
数据文件 I/O timing | 时间未测量,不是零成本 |
track_wal_io_timing |
WAL I/O timing | WAL 时间未测量 |
stats_fetch_consistency |
一个事务内统计读取一致性 | 影响缓存/快照行为 |
统计有开销,timing 尤其依赖平台时钟成本;但关闭后必须把“未测量”保留下来。 绝不能把:
解释为 WAL 写入没有花时间。
本章沙箱:
pg_stat_activity 是当前 backend 视图
先使用不导出 query text 的聚合:
为什么带 backend_type?PostgreSQL 18 里不只有 client backend:
如果把后台进程与 client backend 混在一起,某个长期运行的 background worker 可能被误判为“用户 SQL 运行几小时”。
state 与 wait_event 独立
常见错误:
实际:
所以等待查询写成:
这条查询没有读取 query 列。需要 SQL 上下文时,应在受限交互会话中按
queryid、application、database 和 owner 缩小范围,避免把全文复制进工单。
wait event 是“正在等什么”,不是“根因”
wait_event_type 先把等待分大类:
同一种等待可能有多种机制:
因此:
才形成诊断。
PostgreSQL 18 的异步 I/O 引入 io worker 等 backend type 和相应等待。升级后
不要假设旧版 wait event 列表仍完整;dashboard 和规则要按当前版本校验。
当前统计在一个事务里可能保持不变
累计统计不是每次访问都无条件读取最新值。PostgreSQL 会把统计写入共享内存, 各进程最迟按一定节奏 flush;访问者又可能在当前事务内缓存读取结果。
这段会造成困惑:
在 stats_fetch_consistency=cache 下,同一事务后续读取可能继续看到缓存值。
诊断时优先:
pg_stat_clear_snapshot() 清的是当前 session 的统计 snapshot,不是重置全局
统计;不要与 pg_stat_reset* 混淆。
统计更新也有时间边界
累计统计通常在 transaction 完成后才反映:
因此调查进行中的大事务:
- activity 看当前 transaction age;
- locks 看当前持有/等待;
- progress 看支持的维护动作;
- WAL/IO counter 看累计变化;
- 不等待累计表统计“先证明它存在”。
crash、恢复与复制会改变统计历史
PostgreSQL 正常关闭会保存累计统计;非正常关闭、从 base backup 恢复或 PITR 可能导致统计 reset。跨 failover 比较时:
必须同时保存:
- member/timeline;
- stats reset;
- postmaster start;
- role transition;
- source instance;
- sampling window。
long query 与 long transaction 不同
场景:
| 状态 | query age | xact age | 风险 |
|---|---|---|---|
| active long query | 长 | 约等于或短于 xact | 执行/等待资源 |
| idle in transaction | 当前 query 已结束 | 长 | lock、xmin、vacuum |
| active in old transaction | 当前 query 短 | 很长 | snapshot/业务批次 |
| idle | 上条 query 的开始时间不代表在执行 | 无 transaction | 通常只是连接 |
因此不要用 query_start 对所有 state 排序然后自动 cancel。
idle in transaction 为什么危险
它可能:
- 保留 row/table lock;
- 持有旧 snapshot;
- 阻碍 dead tuple 回收;
- 拉长
backend_xmin; - 占用 connection/pool slot;
- 让后续应用错误更难定位。
诊断字段:
取消或终止是变更动作,不属于本章 L0 采集。先确认:
- owner/application;
- transaction 是否仍有不可重试副作用;
- pool mode;
- unknown commit outcome;
- cancel 与 terminate 的差异;
- rollback/重连影响;
- 用户症状是否关联。
pg_locks 是锁申请,不是完整业务解释
安全聚合:
找 blocker 可使用 pg_blocking_pids():
这个函数给出 blocker PID,但仍要判断:
锁图而不是最长列表
事故中更有用的是:
并保存:
- edge 采样时刻;
- blocker state;
backend_xid/xmin;- queryid,而不是默认 query text;
- application/release;
- lock type/mode;
- user impact。
一条锁边可能瞬间消失。诊断包应保存有界快照,而不是事后只看当前视图。
deadlock 与普通阻塞
普通 lock wait 可以持续;deadlock 是一个等待环,PostgreSQL 会检测并中止其中 一个 transaction。
观察:
log_lock_waits=on 只在等待超过 deadlock_timeout 后记录,短等待不会出现。
日志“没有 lock wait”不能证明没有短暂锁竞争。
权限边界
普通用户只能看到其他 session 的有限信息。pg_read_all_stats 能读取全库统计和
其他 session 的更多信息,但这仍然是高敏感可观测权限:
- query text 可能含业务值;
- application name 可能带身份;
- client address 暴露拓扑;
- activity 能推断业务行为。
建议:
不要因为它不是 superuser 就把它当低风险权限。
查询本身也会进入观察结果
读取 pg_stat_activity 时,你自己的查询也是 active;访问许多系统视图也会拿
AccessShareLock。本章正式快照出现的 relation locks 就包括采集查询本身。
因此:
- 标识 collector application;
- 从结果中区分自身;
- 限制 statement timeout;
- 不在 tight loop 高频轮询;
- 避免一次展开所有 query text;
- 将 observer effect 写进证据。
25.2.2 缓冲、I/O、WAL、检查点与复制
数据路径不是“内存或磁盘”二选一
一个 PostgreSQL page 读取可能经过:
所以:
pg_stat_io 明确不区分物理磁盘和 OS page cache。必须结合 node/block-device
证据。
pg_stat_database 给出数据库级累计轮廓
常用字段:
它适合:
- database-level rate;
- hit/read 变化;
- temp spill 趋势;
- rollback/deadlock 变化;
- reset-aware baseline。
不适合:
- 归因到具体 query;
- 直接推物理 IOPS;
- 用
blks_hit / (blks_hit + blks_read)单独判断内存是否足够; - 比较不同 reset 区间的裸总数。
cache hit ratio 不是性能分数
$$ \text{hit ratio}
\frac{\Delta hits} {\Delta hits + \Delta reads} $$
即使正确用窗口增量,也受 workload 影响:
- 大表顺序扫描天然产生 reads;
- 小表热点容易高命中;
- OS cache 命中仍记为 PostgreSQL read;
- 低 hit 可能是合理批处理;
- 高 hit 不代表 CPU、lock 或 plan 健康。
把它作为 workload 特征,不要设一个跨服务的“低于 99% 就 page”。
pg_stat_io 按谁、什么对象、什么上下文拆分
PostgreSQL 18 可按:
观察:
先聚合非零工作:
窗口诊断需要两次快照求 delta 或 exporter counter rate。裸累计值只说明 reset 以来总量。
timing 要与 byte/count 一起读
平均值会掩盖 tail。需要时结合:
- block-device latency histogram;
- request latency histogram;
- trace sampled tail;
- queryid-level I/O;
- workload mix。
buffer、backend 与 checkpoint 写入
dirty buffer 可能由 backend、background writer、checkpointer 等路径写出。
pg_stat_io 帮助按 backend type 区分;pg_stat_checkpointer 给 checkpoint
累计结果。
PostgreSQL 18:
重要字段包括:
解释:
不要只看一次 write_time 总值。计算每窗口的 checkpoint count、buffer、write
和 sync delta,再与用户 latency、WAL rate 和 host I/O 对齐。
checkpoint 不是越少越好
过频可能增加写入压力和 full-page image;过稀可能:
- 增加 crash recovery 时间;
- 需要更多 WAL;
- 在 checkpoint 集中更多工作;
- 改变恢复和容量特征。
任何调参要结合:
本章只观察,不修改 checkpoint_timeout、max_wal_size 或 completion target。
pg_stat_wal 是 WAL 生成轮廓
用途:
- WAL byte rate;
- full-page image 比例变化;
- WAL buffer full;
- 与 write workload、checkpoint、replication、archive 对齐。
不能直接说明:
- 哪个 query 产生 WAL;
- WAL 是否已归档;
- replica 是否已回放;
- recovery 能否成功。
query 归因需要 pg_stat_statements.wal_bytes 等;归档与恢复需要另外的证据。
full-page image 的上下文
checkpoint 后 page 首次修改可能记录 full-page image,以支持恢复。wal_fpi
变化与:
- checkpoint 频率;
- 工作集;
full_page_writes;- page 修改模式;
- compression;
- backup/recovery policy
相关。不要看到 FPI 高就关闭安全机制。
pg_stat_archiver 同时有 counter 和最后事件
三种不同问题:
沙箱快照:
最近成功晚于失败,说明历史 counter 非零不能证明当前仍失败。
候选告警:
这仍只是 recovery risk candidate。下一步要复核:
- 当前 WAL 是否继续生成;
- archive command/pgBackRest 状态;
- repository;
- latest archive;
- backup/WAL coverage;
- 最近 restore drill。
“归档恢复”不等于“恢复就绪”。
replication 有位置、时间、状态三套语义
primary:
replica:
公开/长期证据不一定应保存 sender/client address;可以保留 member identity、 state、sync_state 和位置差。
位置 gap
位置差按 WAL byte 计,不是秒:
因为 replay throughput、workload、conflict、I/O 和 future WAL 都会变化。它可 用于容量与趋势,不应直接承诺 RTO。
时间 lag 可能是 NULL
write_lag、flush_lag、replay_lag 是最近同步交互产生的测量,并不保证
持续给出“当前落后秒数”。空闲系统可能为 NULL;这不等于零,也不等于故障。
使用:
- LSN distance;
- connection/state;
- last message;
- known commit probe;
- workload/WAL generation
共同解释。
本章正式 SQL 快照中,两条 streaming async replica 的 LSN gap 都为 0,而
lag interval 为 NULL。这正好说明:
sync_state 与 commit durability
sync_state=async 表示它不是当前同步确认的一部分。即使 gap 为零:
- 后续 commit 仍可能尚未复制;
- primary 立即丢失会有 write gap 风险;
- 应用 commit acknowledgment 语义取决于
synchronous_commit和配置; - user freshness 取决于读取路径。
把 pg_stat_replication 与第 20 章 HA 合同一起读,不要从瞬时 gap 倒推出
durability guarantee。
replication slot 与 WAL retained risk
除了 replica gap,还要观察:
slot identity 可能属于 extension/consumer;不要自动删除 inactive slot。风险:
- WAL retention 填满磁盘;
- logical consumer 落后;
- slot 失效;
- consumer 被误认作废弃。
容量规则默认 ticket,动作需 owner 和 consumer 证据。
复制冲突
hot standby 查询可能与 replay 冲突。观察:
pg_stat_database_conflicts;- replica activity;
- query cancellation log;
- feedback/delay 设置;
- retained xmin/WAL;
- user read path。
降低冲突的设置可能增加 bloat 或 freshness lag,不能只优化一张图。
25.2.3 vacuum、冻结、膨胀与对象增长
vacuum 有四个主要目的
常规 vacuum 不是“清空表”,而是:
- 回收 dead row version,使空间可在表内复用;
- 更新 visibility map,支持 index-only scan 等;
- 防止 transaction ID wraparound;
- 维护统计/冻结等运行状态。
ANALYZE 更新 planner statistics;它与 vacuum 可以一起运行,但目的不同。
autovacuum 依赖统计
track_counts=on 不只是“多收指标”。autovacuum 使用累计活动决定何时处理表。
关闭它会影响维护机制。
每表触发近似由:
决定,并受 insert threshold、per-table storage parameter、cost limit、worker 数、 全局设置等影响。
不能只看“autovacuum process 存在”。要看:
- table change pressure;
- last vacuum/autovacuum;
- dead tuples;
- analyze freshness;
- blockers;
- progress;
- freeze age;
- runtime/duration。
表级统计是 estimate + counter
n_live_tup、n_dead_tup 是估算;count 是自 reset 累计。它们适合找候选,不
适合直接计算“精确 bloat 百分比”。
dead tuple 不等于 bloat
vacuum 后 n_dead_tup 可能下降,但文件通常不缩小,因为普通 vacuum 将空间
留给表内复用。需要归还操作系统的操作通常更重,涉及 rewrite/lock/extra disk;
不能因为文件没缩就说 vacuum 失败。
长事务如何阻止回收
MVCC 需要保留旧版本给仍可能看见它的 snapshot。长 transaction、prepared transaction、replication slot/feedback 等可能拉住 xmin:
观察:
还要查:
- prepared transactions;
- replication slots;
- replica feedback;
- vacuum progress;
- table-level age。
“找到最老 PID 就 terminate”不是安全策略。
freeze age 是剩余空间,不是普通 latency
transaction ID 是有限循环空间。旧 tuple 必须被 freeze,避免 wraparound 后 可见性灾难。
数据库级:
表级:
不要把阈值写成与配置无关的魔法数字。应比较:
本章候选 PG36FreezeAgeHorizon 使用固定数字只是 isolated lab 的测试输入,
明确标为 proposed;生产规则应由当前配置和容量政策生成。
anti-wraparound autovacuum 的特殊性
为防 wraparound 启动的 autovacuum 通常不会像普通 autovacuum 那样轻易被冲突 动作自动打断。不要把它当“可以随时 kill 的后台噪声”。
如果已经进入紧急区:
- 停止增加风险的长事务;
- 找出不能推进的表与 blocker;
- 评估 I/O/空间/锁;
- 按 runbook 控制维护;
- 不并行执行未经评估的 rewrite;
- 保留 evidence。
最优策略是在容量 horizon 阶段用 ticket 解决,而不是等 emergency page。
progress view 是当前进度,不是历史
vacuum:
PostgreSQL 还为:
ANALYZE;CREATE INDEX/REINDEX;CLUSTER/VACUUM FULL;COPY;- base backup
提供相应 progress view,具体列按版本文档。
没有行只表示当前没有该动作被报告,不表示:
- 从未运行;
- 上次成功;
- 下一次会成功;
- 没有被瞬间启动后失败。
历史需要日志、事件和累计 count。
progress 百分比可能不单调
不同 phase 使用不同总量;并行、索引清理和 dead item cycle 也会改变解释。 不要把:
当成整个 vacuum 的精确完成百分比。应同时显示 phase 和相关量。
analyze freshness
planner statistics 变旧会导致估算偏差。候选信号:
n_mod_since_analyze 是估算/累计线索,不是每行精确 change counter。对热点、
分区和高度偏斜列,需要 workload-aware 策略。
对象增长要拆分
增长可能来自:
- 正常业务数据;
- index 数量;
- TOAST;
- dead/reusable space;
- fillfactor;
- 分区保留;
- 临时/中间对象;
- rewrite;
- 失控 batch。
不要只按总大小 page。需要:
容量是预测和计划问题,默认 ticket。
partition 会改变聚合方式
父表和各分区的:
- size;
- table stats;
- autovacuum;
- analyze;
- index;
- freeze age
需要分别观察,再按业务分区策略聚合。只看父表可能近乎空;只列每个分区又会 产生巨大 cardinality。
推荐:
不要把每个临时分区名永久做成高基数 alert label。
bloat estimate 的边界
extension 或 SQL 估算 bloat 常依赖:
- row width;
- null bitmap;
- alignment;
- fillfactor;
- statistics;
- page sample;
- index type。
它是排序候选,不是字节级财务账。要做重操作前:
- 复核对象大小与增长趋势;
- 判断空间能否复用;
- 找出生成机制和 blocker;
- 评估 rewrite/lock/replication/WAL/backup 影响;
- 准备额外磁盘和回退/前滚;
- 在维护窗口验证。
本章不会自动 VACUUM FULL 或 REINDEX。
把三类维护信号分开
| 类别 | 问题 | 典型动作 |
|---|---|---|
| 运行正确性 | wraparound 是否接近 | 高优先级维护/停止风险来源 |
| 性能卫生 | dead tuple、stats 是否影响 workload | vacuum/analyze 调整与 blocker 修复 |
| 容量 | 对象/索引/WAL 是否耗尽空间 | retention、扩容、结构优化、rewrite 计划 |
它们的 severity、owner 和时间尺度不同。一个“表大”告警不能同时代表三者。
沙箱快照如何读
正式采集在 pg-test-1 观察到:
这不是“vacuum 永远健康”的证明:
- synthetic workload 很小;
- 只有一次瞬时快照;
- 没有长期增长率;
- 没有生产 transaction rate;
- 没有生产配置与 margin;
- estimate 可能变化。
可以得出的结论只有:
PostgreSQL 信号最小关联表
| 用户症状 | PG 入口 | 原生证据 | 外部复核 |
|---|---|---|---|
| latency | active/wait | activity、locks、queryid | pool、host、trace |
| errors | rollback/deadlock | database counter、logs | app outcome |
| stale read | replica path | replication position/state | commit token probe |
| write latency | WAL/checkpoint | wal、checkpointer、I/O | device、app histogram |
| query spill | temp bytes | database、statement、logs | plan/work_mem policy |
| vacuum delay | old xmin | activity、table stats、progress | workload/change event |
| freeze risk | XID age | database/class age | rate/horizon/capacity |
| recovery risk | archive | archiver + WAL | pgBackRest + restore drill |
本节验收
你应当能解释:
active为什么仍可能在等待;- 同一 transaction 为什么可能读到缓存的累计统计;
track_wal_io_timing=off为什么不能把时间解释为零;pg_stat_io为什么不等于物理磁盘 I/O;failed_count>0为什么不等于当前归档失败;- time lag
NULL与 WAL gap0为什么可以同时出现; - WAL gap
0为什么不能证明用户新鲜度; n_dead_tup为什么不等于精确 bloat;- 普通 vacuum 为什么通常不缩小文件;
- progress view 没有行为什么不证明历史成功;
- query、lock、vacuum 和 freeze 信号分别对应哪种动作;
- 任何取消、终止、reset、vacuum 或配置变更为什么不属于 L0 观察。
上一节:从问题选择可观测信号 · 返回本章目录 · 下一节:SQL 可观测基线 · 查看全书目录 · 查看索引中心
25.3 SQL 可观测基线
数据库“忙”只是结果,SQL workload 才是来源之一。
要回答:
需要组合三个接口:
三者都有盲区,也都有成本。最危险的做法是为了“可观测”无界记录所有 SQL 和 参数,结果既拖慢系统,又制造一份高密度敏感数据库。
25.3.1 pg_stat_statements 的统计口径与重置
它不是默认自动完整可用
pg_stat_statements 需要:
- 出现在
shared_preload_libraries; - PostgreSQL 重启后模块被加载;
- 每个需要查询视图的 database 安装 extension;
- query identifier 可用;
- 查询角色有足够权限;
- 容量、track policy 和 reset 被声明。
检查:
extension 可能不在 public:
Pigsty 沙箱中的结果:
所以查询使用:
不要硬编码成 public.pg_stat_statements。
聚合键不是 query text
一行主要按以下身份聚合:
这意味着:
- 同一 queryid 在不同 database 分开;
- 不同执行角色分开;
- top-level 与 nested statement 分开;
- query text 是一个代表性 normalized text,不是主键;
- 同一可见文本仍可能因语义环境不同获得不同 queryid;
- hash collision 在理论和实践上都可能发生。
因此关联时保留:
只保存 queryid 而丢掉 database/user/toplevel,会把不同 workload 合并。
normalization 不是脱敏承诺
常量通常会被替换:
但不能据此认为 query 列可以公开:
- object 名可能包含客户或项目身份;
- comment 可能含敏感上下文;
- dynamic SQL 结构可能暴露值;
- utility statement 的行为不同;
- query text 与日志/错误组合可能重新识别业务;
- 权限边界本身说明它被视为敏感。
本章 evidence 明确:
queryid 是关联键,不是密码学身份
queryid 是内部 hash:
- 算法可能随 major version 改变;
- collision 可能发生;
- object identity 与
search_path会影响语义; - 相同显示文本可能代表不同解析对象;
- failover/upgrade 前后要重新建立 baseline。
不要用 queryid:
- 做访问控制;
- 证明 SQL 完全相同;
- 做永久跨版本业务 ID;
- 取代 plan fingerprint;
- 取代 application operation class。
它适合:
- workload 聚合;
- before/after 比较;
- dashboard 下钻;
- 与受限日志/plan 关联;
- top-N 诊断。
从总时间拆成频率与单次成本
$$ \text{total execution time}
\text{calls} \times \text{mean execution time} $$
top total time 可能来自:
基本查询不要一开始读 text:
注意:
- min/max 易受单次异常影响;
- mean 掩盖分布;
- row count 的语义随 statement 类型变化;
- block 是 PostgreSQL block,不是任意存储 byte;
- I/O timing 依赖开关;
- WAL byte 不等于 commit durability;
- top-N 会漏掉排名外 workload。
用 delta,而不是跨 reset 比裸值
两个采样点:
只有 identity 与统计窗口连续时才计算:
$$ \text{window mean}
\frac{\Delta total_exec_time} {\Delta calls} $$
如果:
- row 消失;
stats_since改变;- postmaster restart;
- extension reset;
- entry deallocated 后重新创建;
- failover 到另一成员;
就不能直接减。
pg_stat_statements_info 是解释入口
核心:
dealloc 增长表示 statement entry 因容量压力被丢弃。此时 top workload 可能
被 churn 影响;应评估:
pg_stat_statements.max;- query shape 数量;
- dynamic SQL;
- reset;
- 内存成本;
- 是否需要更稳定的 application query pattern。
沙箱正式快照:
这是一次窗口基线,不是永久容量结论。
每行还有自己的时间边界
PostgreSQL 18 的 statement 行包含:
如果只 reset 某条或只 reset min/max,不同 entry 的窗口可能不同。一个 dashboard 不能只在标题写全局“过去 24 小时”,却忽略每行实际开始时间。
reset 是破坏性观察动作
函数可以全局或选择性 reset;当前版本还支持只 reset min/max。它很有用,但 会销毁比较基线。
原则:
本章 L0 采集明确禁止:
如果必须 reset:
- 记录 request/approval;
- exact target;
- old reset time;
- snapshot/hash;
- reason;
- expected observation gap;
- downstream dashboard impact;
- new baseline start。
planning 与 execution 并非一一对应
pg_stat_statements.track_planning=on 可以收集 plan count/time,但:
- 默认通常为 off;
- 对高并发相同 query,plan 统计更新会有明显开销;
- prepared/cached plan 改变 plan/execute 次数关系;
- 执行失败与计划失败的记录条件不同;
- utility 和 nested track policy 影响 population。
沙箱 track_planning=off,所以:
表示未收集,而不是规划不耗时。不要为了填满 dashboard 直接在生产开启;先做 负载测试和决策评审。
track=all 的语义
top 只跟踪 top-level;all 还跟踪 nested statement。沙箱设置 all,再用
toplevel 区分。否则 stored procedure 或 function 内 SQL 可能看不见,或被
误与 top-level 混合。
top-N 查询要保护系统
观察查询也消耗:
- shared memory lock;
- sort;
- format/round;
- dashboard 并发;
- network;
- 结果存储。
生产查询建议:
不要每 5 秒在每个 database:
尤其不要自动导出完整 query text。
建立 workload baseline
基线至少分:
按:
关联。不要仅按 instance;failover 后 workload 会移动。
25.3.2 慢语句、锁等待、临时文件与错误日志
慢日志是离散样本
核心设置:
log_min_duration_statement
记录执行时间达到阈值的语句;0 表示记录全部,-1 关闭。
优点:
- 对超过阈值的 population 较完整;
- 可以看到离散 tail;
- 能与 queryid、错误和时间线关联。
代价:
- I/O 与格式化;
- log volume;
- SQL/参数泄露;
- 高频略慢语句形成洪水;
- 日志系统成为新瓶颈。
log_min_duration_sample
达到 sample threshold 后,再按 log_statement_sample_rate 采样。它适合控制
高流量环境的日志量,但“未出现”不能解释为“未发生”。
如果:
采样路径仍是关闭的。不要只看 rate。
duration 的边界
语句 duration 与用户 end-to-end latency 不同。用户时间可能包括:
PostgreSQL 慢日志只覆盖 server 侧语句范围。
lock wait 日志
log_lock_waits=on 在等待超过 deadlock_timeout 时记录。沙箱:
这意味着:
- 超过约 50 ms 的 lock wait 有机会被记录;
- 更短等待可能很多但没有日志;
- deadlock detection cadence 与日志量相关;
- 不能为了更多日志随意降低 timeout;
- 日志是离散事件,当前 lock view 是即时状态。
调查组合:
temporary file
log_temp_files 在临时文件删除时记录超过阈值的文件。沙箱为:
这类日志说明:
- 某操作产生了 temp file;
- 大小达到记录政策;
- 日志时刻可能是删除时刻,不是创建/峰值时刻。
关联:
pg_stat_database.temp_files/temp_bytes;pg_stat_statements.temp_blks_read/written;- queryid;
- plan;
- sort/hash/window;
- workload concurrency;
work_mempolicy。
不要看到 temp 就全局提高 work_mem。它按 operation/node/worker 使用,高并发
会把一个小改动放大成内存风险。
error log 与用户 outcome
PostgreSQL error 包含 SQLSTATE、severity、detail、context 等;应用可能:
- retry 后成功;
- 返回失败;
- 超时但 commit;
- 屏蔽错误;
- 将一个 DB error 映射成不同业务 outcome。
所以错误日志要与应用 outcome 关联,而不是用 log line 数直接做 availability 分子。
稳定聚合维度:
不适合作 label:
structured CSV/JSON 不会自动安全
沙箱使用:
CSV 方便 Vector 解析,但:
- statement 字段仍可能敏感;
- detail/context 可能含值;
- file permission/collector group 需要评审;
- 集中存储扩大读取面;
- retention 必须独立设置。
从 metric 到 log,而不是无界 log query
正确流程:
本章诊断包规定:
需要正文时,应在受限界面临时查看并按事件政策处理,而不是复制进公共工单。
日志缺失也有语义
没有日志可能是:
- 事件未发生;
- threshold 未达到;
- sampling 丢失;
- collector/Vector 失败;
- rotation/retention;
- parse schema drift;
- 权限或查询错误;
- 日志写入阻塞;
- service 在另一 instance。
必须同时观察 log pipeline。
25.3.3 auto_explain 的采样、嵌套语句与开销
auto_explain 解决什么
pg_stat_statements 告诉你:
EXPLAIN/EXPLAIN ANALYZE 告诉你:
auto_explain 在满足政策时自动把 plan 写入日志,适合捕捉难以手工复现的慢
执行。
它不是零成本“打开即可”。
最重要的开关
| 设置 | 问题 |
|---|---|
log_min_duration |
多慢才记录;默认 -1 不启用 |
sample_rate |
满足阈值的 statement 采样多少 |
log_analyze |
是否实际执行统计进入 plan |
log_timing |
ANALYZE 时是否逐节点计时 |
log_buffers |
是否记录 buffer 使用 |
log_wal |
是否记录 WAL 使用 |
log_nested_statements |
function 内语句是否记录 |
log_parameter_max_length |
参数记录长度 |
log_format |
text/xml/json/yaml |
log_verbose |
是否输出额外细节 |
log_settings |
是否记录影响 planning 的设置 |
log_triggers |
trigger 统计 |
log_analyze 的隐藏成本
官方文档特别警告:启用 log_analyze 后,即使最终语句没有达到
log_min_duration 而不写日志,也可能需要为所有语句做 per-plan-node timing。
如果同时:
开销可能非常高,尤其是大量短 node 的 workload。
log_timing=off 可以保留 rows 等实际统计并减少逐节点时钟调用,但仍需实测。
沙箱的真实设置
本章只读检查发现:
这不是推荐模板。它是一个需要单独 overhead 与泄露评审的真实基线:
sample_rate=1没有限制 eligible statement;log_analyze + log_timing有全局计时成本;- nested 可能显著放大日志量;
- parameter length
-1允许完整参数; - 阈值 1 秒只限制最终写 plan,不一定消除 instrumentation cost。
本章不 reload、不改参数,只把风险记录下来。
BUFFERS 与 WAL 依赖 ANALYZE
自动计划中的实际 buffer/WAL 信息要求 analyze path。不能关闭 analyze 后仍假装 获得运行时资源。
设计取舍:
必须在代表性 workload 上量化。
nested statement
function、trigger、procedure 内可能包含真正慢的 SQL:
log_nested_statements=on 能看见,但会:
- 增加 volume;
- 重复上下文;
- 暴露更多 query/parameter;
- 让一个 top-level 请求产生多份 plan。
同时使用 pg_stat_statements.track=all 与 toplevel 关联。
采样测试应覆盖 tail
开启前在隔离 workload 回答:
不要只跑一条慢查询说“开销不大”。
不要在事故中临时全开
事故中:
可能:
- 进一步降低吞吐;
- 加剧 disk/log pipeline;
- 泄露参数;
- 改变被观察 workload;
- 制造新的故障。
更安全顺序:
- 用现有 SLI、activity、wait、queryid 缩小范围;
- 使用已有日志与 plan;
- 评估只对 session/role/database 的受控方法;
- 明确 timeout、采样、持续时间和 rollback;
- 由变更政策批准;
- 观察 overhead;
- 到期自动撤销并验证。
auto_explain 不是 plan history 系统
计划只在满足政策时进入日志;sampling、rotation、retention、parse 都会造成 缺口。若要做 plan regression:
- 在测试/发布流程保存
EXPLAIN (FORMAT JSON); - 记录 schema/statistics/version/settings;
- 与 queryid/plan fingerprint 关联;
- 不要把生产日志当完整 plan catalog;
- 不要在没有参数与数据分布语义时做机械 diff。
25.3.4 日志不得泄漏密码、令牌和敏感参数
SQL observability 是高敏感数据面
可能出现:
PostgreSQL 文档明确警告,statement logging 可能暴露敏感数据,甚至明文密码。
参数记录的两套限制
一般语义:
auto_explain.log_parameter_max_length 又是独立设置。
沙箱:
这表示错误路径参数被禁用,但普通慢 statement/auto_explain 仍可能完整记录参数。 是否安全取决于 protocol、query 和应用,不能因为“一项为 0”宣布无泄露。
log_statement 的风险
log_statement=all 会记录每条 statement;DDL 中尤其可能出现:
即使参数化 DML 避免 literal,DDL、utility、comment 和 dynamic SQL 仍可能带 秘密。生产不得把“排障方便”当默认充分理由。
文件模式:0600 不是唯一安全答案
PostgreSQL log_file_mode 控制 collector 创建文件的 mode。
Pigsty 使用 Vector 收集日志时,受控 group read 可能是合理实现。安全要求是:
沙箱为 0640。本章没有证明 collector group membership 已完成生产审批,因此
将其作为待评审事实,而不是自动判定安全或不安全。
传输到 VictoriaLogs 扩大了边界
原本只有 database host 上的日志,集中后可能被:
- Grafana data source;
- log query API;
- incident automation;
- backup/export;
- 多个 operator
访问。
必须重新定义:
“源文件权限正确”不能证明集中日志安全。
metric label、alert annotation、ticket 是二次泄露面
最常见事故不是直接开放 log file,而是自动化复制:
本章诊断包只导出:
- error class/count;
- queryid;
- database/role class;
- time window;
- release/change id;
- source link without credentials。
不导出:
- statement;
- bind value;
- log body;
- client address;
- tenant/order/customer。
redaction 要在尽量靠近 source 的位置
优先级:
- 应用不把 secret 放进 SQL/comment;
- 参数化查询;
- PostgreSQL logging policy 限制;
- collector parse/redact;
- storage ACL/retention;
- alert/evidence allowlist。
最后一步 regex 不是万能防线:
- 数据格式会变化;
- 编码/截断会绕过;
- 新字段未被匹配;
- secret 可能已进入上游缓存/备份。
query text 的受控交互查看
真正排障有时需要 SQL text。正确做法不是绝对禁止人看,而是区分:
这既保留诊断能力,也控制扩散。
prepared statement 不自动消除泄露
参数化可以让 statement text 不含 literal,但:
- bind parameter logging 仍可能记录值;
- 错误 detail/context 可能包含;
- 应用 comment/baggage 可能包含;
- object/table/schema 名仍可能敏感;
- auto_explain parameter logging 是独立开关。
所以要检查完整链路,不只看应用 ORM。
慢日志与审计日志不是一回事
慢日志回答 performance sample;审计回答谁在何时对什么对象做了什么。两者:
- 目的不同;
- 保留不同;
- 访问不同;
- 完整性要求不同;
- 敏感性不同。
不要让 log_statement=all 同时冒充性能、审计、合规和安全检测。需要审计时,
使用明确的审计政策、对象范围和审阅流程。
安全基线查询
这只是配置事实,还要复核:
- actual file mode/owner/group;
- directory mode;
- Vector identity/config;
- VictoriaLogs access/retention;
- sample log 是否被正确 parse/redact;
- alert/template 是否复制 body;
- backup 是否包含日志。
本章 L0 只记录配置,不读取日志正文。
SQL 可观测基线清单
本节验收
你应当能够解释:
- 为什么 extension schema 不能假设是
public; - 一行
pg_stat_statements的四个核心聚合维度; - normalization 为什么不是脱敏保证;
- queryid 为什么不能做永久或安全身份;
calls、total、mean、max 各说明什么;dealloc、global reset、stats_since、minmax_stats_since的差异;track_planning=off时 plan time 为零意味着什么;- 慢语句、采样慢语句、lock wait 和 temp file 日志各何时产生;
auto_explain.log_analyze + log_timing为什么可能给所有语句带来成本;- nested statement 为什么同时增加诊断价值与泄露/volume;
0640如何在受控 collector group 下成立;- 为什么自动 evidence 只保留 queryid 与聚合,而交互查看另行授权。
上一节:PostgreSQL 核心运行信号 · 返回本章目录 · 下一节:把观察契约变成告警 · 查看全书目录 · 查看索引中心
25.4 把观察契约变成告警
规则语法正确,只证明 parser 接受了它。
一条生产告警还必须同时通过:
本章生成的文件:
它们是教学候选,不是在线配置。
25.4.1 SLO 燃烧率与用户影响
从 error ratio 到 burn rate
若 SLO target 为:
允许的 bad ratio:
某窗口实际 bad ratio 为 $E$,燃烧率:
例如:
若持续 28 天,会花掉 14.4 倍预算;若只持续 1 小时,约花掉:
这解释了为什么 14.4x/1h 常被用作“快速消耗约 2% 月度预算”的起点。它是 政策起点,不是自然常数。
为什么使用两个窗口
只用长窗口:
只用短窗口:
组合:
长窗确认预算风险,短窗确认问题仍在发生。
本章可用性 fast burn
记录规则预先计算五个窗口:
fast:
标签固定:
fast、slow 与 ticket 是一套政策
| 规则 | 长/短窗 | burn | for |
路由 |
|---|---|---|---|---|
| fast | 1h / 5m | 14.4x | 2m | SEV-1 page |
| slow | 6h / 30m | 6x | 5m | SEV-2 page |
| budget | 3d / 6h | 1x | 15m | ticket |
它们不是三个独立“数据库报警”:
- fast 处理快速用户损害;
- slow 处理持续预算风险;
- ticket 处理应进入 backlog 的可靠性债务。
当 fast firing 时,同一 service/environment/objective 的 slow 和 ticket 可以 被抑制,避免一个事件三次通知;原始 alert state 和证据仍保留。
burn threshold 必须跟 target 绑定
延迟目标是 99%:
可用性目标是 99.9%:
把同一个绝对 error ratio 复制过去会错一个数量级。
规则生成器若使用 policy object,应从:
计算,而不是在 YAML 中散落魔法数。
event ratio,不是 instance ratio
错误写法:
它算的是实例比例,不是用户 event ratio。三节点中一个 down:
- 服务可能完全正常;
- 某些 read path 可能降级;
- failover 可能正在进行;
- 用户失败比例不一定是 1/3。
正确 availability SLI 来自 application outcome:
instrumentation 要预初始化 bounded outcome series,否则“没有 bad outcome series”与“零 bad events”会混淆。
latency 规则暴露了治理缺口
第 24 章定义 SLO-LATENCY,但 accepted alert list 没有 latency candidate。
本章没有悄悄将它变成生产 page,而是:
规则和测试都存在,生产门禁仍拒绝。这体现:
owner 还要决定:
- latency threshold 是否 250 ms;
- eligible event 与 availability 是否相同;
- success 但慢、failure 且慢如何计数;
- 低流量如何处理;
- page severity;
- 首个安全动作;
- 与 availability page 如何去重。
freshness 必须使用 commit-correlated event
规则:
分母是携带已知 commit token 的 probe,bad 是五秒内不可见。
不要替换成:
因为 pg_lag 的语义、空闲行为和用户 path 都不同。
正确性不燃烧
它没有:
一个未解释 mismatch 就进入完整性响应。缺失或 stale reconciliation 是另一个 control failure,不是零 mismatch。
恢复就绪也是 control
候选:
它应:
- block 高风险 release 或建立 ticket;
- 引导查看不可变 restore manifest;
- 安排隔离 restore drill;
- 不把 backup success 当替代。
当前第 24 章已定义 control objective,但告警候选仍待接受,所以走 test sink。
容量预测先要求 reviewed
为什么第二个条件重要?
都可能产生虚假线性 forecast。未经评审的模型不应 page。即使 reviewed,也默认 ticket,因为当前没有用户损害。
用户影响必须写进规则
同一个 threshold 若不能说明 user impact,就不能决定 severity:
这也是为什么 PG36HostCpuHigh 保持 diagnostic。
25.4.2 阈值、持续时间、去抖、抑制与分组
threshold 是政策,不是颜色
阈值来源可以是:
- SLO/error budget;
- control objective;
- 容量 horizon;
- 安全/完整性不变量;
- 组件规格;
- 统计/历史基线;
- 供应商硬限制。
每种来源对应不同动作。不要在 Grafana 中选一个“看起来红”的值,然后反向写 解释。
for 的状态机
典型:
for 不是简单 sleep:
- evaluation interval 决定检查粒度;
- rule error 与 no-data 的行为不同;
- restart/state persistence 要配置;
- data delay 可能让最新点不可见;
- label set 改变会创建新的 alert identity;
- 短暂 false 可重置 pending。
VMAlert 文档说明:no data 会重置 pending;evaluation error 则不会按普通 false 处理,而会保留此前状态。必须监控 rule error,不能只看业务 alert。
for 不能修复错误语义
仍然不知道用户是否受损。
仍然会把历史失败永久当当前失败。
先修 expression,再用 for 处理持续性。
scrape、evaluation 与 delay
假设:
“2m 后 page”不代表现实事件开始后精确两分钟:
可能更久。规则验收应测 end-to-end detection time,而不是只读 YAML。
VMAlert 支持 group eval_delay/query latency offset 等。取值要根据真实 ingestion
延迟,并在 rule test 与 dashboard query 中使用一致时间对齐。
recording 与 alert group 分离
VMAlert 一个 group 内顺序执行 rule,但 recording result 异步写回 remote storage。若同组后续 alert 立即读取前一个 recording result,可能看不到本轮 结果。
不要:
本章分成:
测试用 group_eval_order 先运行 recording groups,再运行 alert groups。
rule result limit
错误 label 可能让一条 rule 产生数十万结果。VMAlert group 支持 result limit; 生产应按预计 cardinality 设置并监控 exceeded error。
限制不是修复:
- 先保证 labels 有界;
- 再估算 series;
- 限制作为故障保护;
- 超限时 health unknown,不能默默丢结果;
- 监控 rule error。
Alertmanager group_by
本章:
没有把 ins 放进 user symptom grouping。原因:
组件信息在诊断包和 dashboard,下游 page 围绕服务事件。
group timing
含义大致是:
- group_wait:新组第一次通知前等待相关 alert 聚合;
- group_interval:组变化后再次发送的最小间隔;
- repeat_interval:持续 alert 重复提醒间隔。
取值要与 severity 匹配。SEV-1 等 30 秒可能可以,也可能太慢;真实策略需 notification SLO 和业务 owner 接受。
dedup identity 来自 labels
改变 label 会创建新 alert:
因此不要把高变化字段放 alert label。它们会:
- 重置
for; - 打破 dedup;
- 增加通知;
- 让 silence 失效。
inhibition 只去重,不删除事实
本章第一条:
它不会抑制:
- 不同 service;
- 不同 environment;
- freshness;
- correctness;
- monitoring path。
第二条:
它不抑制独立 blackbox 看到的 user symptom。
正确性永不被普通抑制
本章对抗测试会拒绝任何 target matcher 包含:
原因:
可以在 incident tooling 中关联同一事件,但不能静默正确性 page。
silence 与 inhibition 不同
inhibition 是配置中的关系规则;silence 是带 matcher 和期限的操作。
silence 必须:
- owner;
- reason;
- start/end;
- exact matcher;
- replacement observation;
- incident/change reference;
- review/expiry。
不要为了维护直接 silence 整个 cluster 的所有 alert。planned maintenance 也 不应从 SLO event 中事后删除。
recovery 与 flapping
恢复条件可以:
- expression 直接 false;
- 使用 hysteresis;
- 使用
keep_firing_for(若平台与政策采用); - 由 Alertmanager repeat/group timing 控制通知;
- 要求独立 verification。
不要只让 threshold 出现巨大滞后,导致真实恢复长时间仍 page。
测试至少覆盖:
25.4.3 每条告警绑定所有者、证据和首个安全动作
page 是打断权
page 会打断一个人的当前工作或睡眠。因此最低合同:
没有这些字段,报警不是“先上线再补文档”的半成品,而是潜在事故放大器。
route class
| route | 何时使用 | 例子 |
|---|---|---|
| page | 需要立即有人介入 | fast burn、correctness、monitoring path |
| ticket | 当前可排期但有 deadline/owner | capacity、slow reliability debt |
| diagnostic | 解释 symptom,不独立打断 | replica distance、CPU、pool queue |
| test | 尚未治理接受或只做演练 | latency candidate、archive candidate |
severity 不应与 component 数值机械绑定。
owner_function,不是个人名字
真实 Alertmanager route 再映射到当前值班表。好处:
- 人员轮换不改 rule;
- 服务责任与平台执行分开;
- 可以审查 role 是否可达;
- 可以测试 fallback escalation。
本章空 receiver 只是验证标签分支,未证明真实 on-call 可达。
首个安全动作要降低不确定性或损害
fast availability:
不是:
这些数据库动作可能扩大未知写结果。
freshness:
正确性:
monitoring path:
首个动作不是完整修复,而是事故中最不容易后悔的下一步。
evidence link 不能带 credential
source/dashboard URL:
不要:
receiver 中的 secret 也不能进入 Git。
annotation 要短而确定
通知首屏包含:
不要塞:
- 全部诊断 SQL;
- 数百行日志;
- 未经验证的 root cause;
- 变化的 error text;
- 密码/token;
- “请检查”这种无动作文本。
runbook 需要停止线
告警 runbook 不是:
它至少包括:
- entry condition;
- identity/target;
- authority;
- safe read-only queries;
- hypotheses;
- first action;
- stop condition;
- escalation;
- mutation approval;
- verification;
- evidence。
一个 page 若 runbook 第一条就是 destructive action,应退回评审。
dashboard 不负责结论
dashboard URL 负责定位:
- SLI windows;
- event count;
- release marker;
- entry/DB/host correlation;
- metamonitoring。
runbook 负责:
- 如何解释;
- 如何复核;
- 什么能做;
- 什么不能做。
Grafana panel 不能表达完整权限和停止线。
alert review expiry
第 24 章 candidate 带 expires_for_review。规则应定期检查:
- SLO target 是否变;
- metric/version 是否变;
- query cost;
- false positive/negative;
- page actionability;
- route ownership;
- runbook 是否可执行;
- last drill;
- label cardinality;
- notification receipt。
“从未触发”可能是服务稳定,也可能是规则永远不匹配。VMAlert 有 never-firing 检测思路,但仍要用合成测试证明。
actionless instance-down 为什么被拒绝
候选:
缺少:
- user impact;
- owner;
- runbook;
- first safe action。
一个 replica 可能计划下线且服务正常。替代:
这不是“不监控实例”,而是不把所有 component state 都升级为打断。
25.4.4 用演练验证告警,而不是等生产事故
规则需要单元测试
测试文件指定:
然后注入:
检查:
为什么不直接在在线 VMAlert 测
向在线 datasource/Alertmanager 发合成数据会:
- 污染 production-like time series;
- 触发真实 route;
- 创建 silence/incident confusion;
- 和现有规则相互作用;
- 难以证明完全清理。
本章使用 vmalert-tool:
这是 L1 ephemeral,不是 live deployment。
测记录规则的算术
输入:
期望:
工具实际查询 recording result,防止:
- selector 写反;
- 分母只算 good;
- label join 丢失;
- window 名与内容不一致;
- expression 语法只“看起来对”。
测 alert labels 和 annotations
测试不仅看 alertname,还看:
这样 candidate 被误改成 page,或 accepted rule 被误改成 proposed,会使测试或 validator 失败。
测 missing
输入:
期望:
不要用零 bad ratio 代替。
还应分别测试:
- expected traffic 为零;
- SLI fresh;
- exporter down;
- rule query error;
- independent probe fails;
- label rename。
测 route,不发送通知
Alertmanager sandbox receiver 只有名字:
没有:
amtool config routes test 根据标签离线解析 receiver。八个用例覆盖:
测 inhibition 的正反例
五个用例:
只测“应该抑制”不够;错误的 broad matcher 往往会吞掉最重要的告警。
测规则引擎本身
在线当前快照:
这是已有 Pigsty 规则,不包括本章 31 条候选规则;本章从未部署它们。
metamonitoring 还应观察:
- evaluation duration vs interval;
- missed iteration;
- datasource error;
- remote write error;
- notifier error;
- state restore;
- no-series/never-firing;
- data delay;
- result limit。
“当前无告警”不是通知测试
current alerts=0、notification failures=0 不能证明:
- route matcher 正确;
- receiver secret 有效;
- 外部服务接收;
- 值班人员可达;
- 升级路径工作。
真实 notification canary 要穿过:
本章没有权限触达真实 receiver,覆盖矩阵保持 real_receipt_canary=false。
变更后的最小测试矩阵
| 改动 | 必测 |
|---|---|
| expression | normal/threshold/below/above/missing |
| window | onset、pending、firing、recovery |
| labels | grouping、dedup、cardinality |
| route | expected sink、default、no real integration |
| inhibition | positive + must-not-inhibit |
| annotation | owner/action/runbook/dashboard |
| recording | exact arithmetic and labels |
| engine | dry run、unit、error metrics |
| deployment | canary、rollback、receipt;本章未执行 |
本节验收
你应当能说明:
- burn rate 如何由 objective target 计算;
- 14.4x 为什么只是 policy starting point;
- 两个窗口分别防什么问题;
- instance ratio 为什么不是 event ratio;
- latency rule 为什么只能走 proposed test;
- correctness 为什么没有 error budget;
for为什么不能修复错误 expression;- recording 与 dependent alert 为什么要分 group;
- group_by 为什么不默认带 instance;
- fast/slow/ticket 应如何抑制;
- metamonitoring 只能抑制哪些派生告警;
- page 最低要绑定哪些动作字段;
- synthetic rule test 与 offline route test 各证明什么;
- 为什么它们仍不能证明真实 pager delivery。
上一节:SQL 可观测基线 · 返回本章目录 · 下一节:Pigsty 可观测体系 · 查看全书目录 · 查看索引中心
25.5 Pigsty 可观测体系
Pigsty 提供的是一套 PostgreSQL 可观测参考实现,而不是另一套数据库语义。
每一层都可能:
- 正常工作;
- 延迟;
- 丢数据;
- 标签漂移;
- 权限不足;
- 版本升级;
- 将一个原生值重新聚合;
- 把缺失误写成零。
所以“Pigsty 面板显示”仍要回到 PostgreSQL 和应用合同复核。
当前官方入口:
25.5.1 采集、存储、规则、面板与通知链
metrics 采集
Pigsty PostgreSQL 监控主要汇集:
实际端口、TLS 和访问控制以 inventory/rendered config 为准,不能把教学默认值 当网络策略。
沙箱 pg-test-1 的只读探测:
探测 endpoint 可证明 process/HTTP 返回,不能证明:
- 所有 SQL collector 成功;
- 所有 database 被发现;
- 权限足够;
- series 新鲜;
- rule 查询正确;
- 用户服务健康。
target registration
Pigsty 文档中的 PostgreSQL target 文件沿用:
命名不意味着当前存储一定是旧版 Prometheus;Pigsty v4 的当前栈使用 VictoriaMetrics。target 记录:
这个 external identity 会与 raw exporter metric-specific labels 合并。
raw exporter 与存储后的 label 不同
直接访问 :9630/metrics:
写入 VictoriaMetrics 后还会带:
因此调试 label 漂移要分别看:
- exporter raw output;
- target/relabel config;
- storage series;
- recording result。
只看一层可能误判 producer。
pg_exporter 是 SQL 到 metric 的编译层
它的配置定义:
同一个 view 可以变成多条 metric。升级 PostgreSQL/pg_exporter/Pigsty 后:
- 列可能增加;
- metric 名可能变化;
- tag 可能变化;
- 旧 dashboard/rule 可能失配;
- 权限 schema 可能变化。
生产需要 versioned config 和 compatibility test。
exporter 内建最小信号
pg_exporter 即使不加载大量自定义 collector,也有基本自描述/连通信号,例如:
它们适合判断:
- exporter process/target;
- database connectivity;
- server version;
- recovery role。
pg_up=1 不证明所有高成本 collector、扩展 view 或 application database 可读。
management endpoint 要保护
pg_exporter 的管理/配置能力不应无条件暴露给业务网络。即使 /metrics 可被
监控系统读取,也要区分:
使用:
- 网络 ACL;
- reverse proxy auth/TLS;
- bind address;
- least-privilege service;
- 管理面关闭或隔离;
- access log/audit。
不要把“exporter 没有业务写权限”等同于“管理 endpoint 无风险”。
metrics storage
Pigsty v4 使用 VictoriaMetrics 保存时序。它负责:
- ingest;
- time-series index;
- MetricsQL/PromQL-compatible query;
- retention;
- recording result;
- rule state相关读写;
- API。
本章快照:
这些不是“监控容量还剩多少”的答案。还要看:
- ingest rate;
- data size;
- retention;
- series churn;
- query latency;
- cache;
- disk;
- backup;
- self errors;
- cardinality trend。
logs storage
Vector 收集:
发送到 VictoriaLogs。沙箱 health/version 证明:
本章没有读取任何日志 body,因此没有证明:
- 所有 source 被采集;
- parser 正确;
- redaction 正确;
- retention 正确;
- query role 正确;
- incident window 有完整日志。
endpoint health 只是第一层。
traces storage
沙箱有:
但没有声称 pg36_shop 发出 application span。三件事要分开:
没有第三项,不能在架构图上把 trace 当成已覆盖。
rule evaluation
VMAlert:
- 读取规则;
- 向 datasource 查询;
- 执行 recording/alert;
- remote-write recording/state;
- 把 alert 发给 Alertmanager;
- 暴露自监控 API/metric。
快照:
这些是 Pigsty 已有规则。第 25 章的:
只经过隔离工具测试,未放进在线 VMAlert。
dashboard
Grafana 将三类 data source 组织为:
当前 Pigsty 文档列出约 26 个 PostgreSQL dashboard,按 overview、cluster、 instance、database 等层级组织。
dashboard 的优势:
- 导航与变量;
- 版本化 panel;
- 多层关联;
- time range;
- release annotation;
- top-N;
- drill-down。
它的边界:
- panel query 可能与告警不同;
- 变量默认值可能聚合错 scope;
- time range/step 改变结果;
- downsampling 隐藏 spike;
- 颜色阈值不等于 SLO;
- Grafana cache/query error;
- 直接 PG datasource 可能与 metric snapshot 不同。
Alertmanager
Alertmanager 负责:
- group;
- dedup;
- route;
- inhibition;
- silence;
- receiver integration;
- repeat。
它不负责决定 expression 是否有业务意义。VMAlert 能成功发送给 Alertmanager, 也不等于外部 receiver 已收到。
快照:
没有 receipt canary,真实 delivery 仍是盲区。
端到端链路的健康层
| 层 | 证据 |
|---|---|
| producer | PostgreSQL view/metric raw output |
| scrape | target up、scrape duration/error |
| ingest | newest sample time、storage health |
| query | exact expression result、error |
| rule | evaluation state/error/duration |
| notifier | VMAlert notifier success/error |
| route | Alertmanager matched receiver |
| integration | external API accepted |
| receipt | human/system acknowledgment |
“面板有数据”通常证明到 query;“Alertmanager 无失败”最多证明部分 notifier/ integration 路径;receipt 需要独立 canary。
Pigsty 三种监控模式
当前文档:
| 模式 | 场景 | 可见能力 |
|---|---|---|
| RDS / Basic | 只有可连接 PGURL | PG metrics,缺 host/pool/LB/log |
| Managed | 现有数据库且可 SSH/sudo | PG + node,其他可选 |
| Full / Standard | Pigsty 创建并管理 | PG/pool/LB/node/log 完整参考栈 |
这直接影响诊断:
dashboard 应根据 capability 隐藏/标记 unavailable,而不是显示绿色零。
平台版本是合同的一部分
本章快照:
旧教程若仍写 Prometheus、Loki 等历史组件,不能直接套用 v4。规则语法具有兼容 目标,但 storage、API、自监控 metric 和运行特性必须按实际版本核对。
25.5.2 以指标语义定位集群、实例、数据库和查询
三个稳定基础身份
Pigsty 使用:
同一个:
可关联 PostgreSQL、PgBouncer、Patroni、node、HAProxy 与 logs。
它们解决的是平台身份,不自动解决业务身份:
需要应用层单独提供。
为什么同时保留 cls、ins、ip
| label | 用途 | 变化风险 |
|---|---|---|
cls |
服务集群聚合 | cluster rename/migration |
ins |
稳定成员角色 | rebuild/replacement |
ip |
node/网络关联 | IP 重用/迁移 |
IP 不是唯一永久身份;instance 也可能重建。诊断包同时保存 topology event 和 captured_at。
instance 与 exporter endpoint
storage series 还有:
这个 instance 是 scrape target endpoint,不一定等于 Pigsty ins。写 query
时明确:
不要误用 Prometheus convention 的 instance 代替 Pigsty member identity。
cluster 级
常见问题:
- 集群是否有 primary;
- 多少 member exporter 可达;
- transaction/WAL 总 workload;
- aggregate capacity;
- replication topology;
- entrypoint health。
查询示意:
第一条 min(pg_up) 只是“是否每个目标 up”,不是 service availability。
instance 级
常见:
本章现场:
可区分:
但角色应与 Patroni、direct SQL 和 topology event 交叉,特别是在切换窗口。
database 级
raw exporter:
storage 加平台身份后,可以:
database label 是 logical database 名;多个 cluster 可能都有 postgres 或
test,不能丢 cls。
query 级
现场 pg_query_calls:
这里 query 的值是 numeric queryid 字符串,不是 SQL text。语义:
查询时:
具体 metric 名与单位由当前 pg_exporter.yml 定义,使用前查看 HELP 和
dashboard query;不要凭记忆假设 _exec_time 是 counter 还是 seconds。
query label 仍有 cardinality 成本
即使不是 raw text:
pg_stat_statements.max=10000;- 多个 database/user/toplevel;
- 多个 instance;
- 旧 series retention;
- query churn;
仍可产生大量 series。current snapshot 的 top label cardinality 中
recording 有 698 个值,也说明 rule identity 自身会形成规模。
需要:
- 只保留需要的 query metric;
- 记录 dealloc;
- 控制 dynamic SQL shape;
- top-N dashboard;
- retention;
- 不要把 queryid 复制到 page grouping。
exact metric names 来自当前 exporter
沙箱 pg_exporter v1.4.0 实际暴露:
这是版本化现场证据,不是永久 API。升级时用:
仅在受控网络检查 # HELP、# TYPE 与 label,不要把 endpoint 公网暴露。
pg_lag 与 pg_repl_replay_diff
现场:
前者可能是 time-like convenience metric,后者是 replay distance;具体实现要 查 exporter SQL。两者都不能代替 commit-correlated SLI。
archiver metric 命名的实现差异
native view:
exporter 观察名:
规则作者要确认:
finish_time是 Unix seconds;- counter type;
- primary/replica exposure;
- reset;
- last success after failure;
- missing on replica。
不能把 native column 名直接猜成 metric 名。
label join 的显式性
组合两个指标:
必须显式声明 join key。默认所有共同 label 会参与匹配;一侧多一个 ins 或
job,结果可能空。
调试步骤:
- 分别查询 A/B;
- 列 label sets;
3.确定语义上应该一对一、多对一还是聚合;
4.先 aggregate;
5.用
on/ignoring; 6.检查重复结果。
不要通过删除 identity 修复 join
错误:
它可能跨 cluster/environment 聚合,虽然“有数了”,语义已丢失。
正确做法先确定 service scope,再用相同 bounded dimensions。
scrape freshness
本章正式采集使用:
并计算 newest sample age,要求不超过 180 秒。up=1 但最后样本很旧,不应视为
健康;storage 里旧值可能仍可查询。
current app SLI absence
查询:
结果 series 为 0。结论:
不是:
这一区分贯穿全章。
25.5.3 面板结论回到 SQL、日志与主机事实复核
dashboard 是索引,不是裁判
一个 panel 应能回答:
看不到 query 的 panel 不适合支撑高风险动作。
先检查 dashboard scope
事故前先读变量:
常见错误:
- 选了
pg-meta而非pg-test; - instance 仍是旧 primary;
- database 选
postgres而用户在test; - time range 不包含 onset;
- browser timezone 与事件 UTC 不同;
- panel 聚合 all instance;
- Grafana repeat panel 隐藏一个 member。
本章 SSH 还遇到一个真实访问路径陷阱:工作站对多个逻辑 IP 的 SSH port
forward 落到同一元节点。只有进入元节点再连接真实沙箱网络,并验证
hostname + Patroni scope + cluster_name,才证明查询了 pg-test-1。
从 availability panel 回到 event
panel:
复核:
- source metric 是否存在;
- eligible/good selector;
- event count;
- low traffic;
- release/route;
- missing;
- reconciliation; 8.独立 probe。
如果 app metric series 根本不存在,panel 不应该显示绿色 0。
从 connection panel 回到 activity
panel 显示连接增加:
再看:
- PgBouncer client/server/wait;
- HAProxy queue/session;
- application pool config event;
- connection churn log;
- transaction age;
- max connection headroom。
不要直接提高 max_connections。
从 lock panel 回到 blocker graph
然后在受限会话按 queryid/application 找 owner。panel 的 lock count 不足以
授权 terminate。
从 I/O panel 回到两个系统
PostgreSQL:
主机:
只有两层同窗,才能区分 PG workload 与 host path。
从 WAL/replication panel 回到原生位置
再检查:
- Patroni role/timeline;
- receiver;
- slot;
- WAL generation rate;
- read routing;
- commit token probe。
不要仅凭 panel 的“lag 0”关闭 freshness incident。
从 archive panel 回到时间顺序
问:
沙箱就是 failed_count=21 但后来成功。panel 若只把 total failed 画红,会永久
误报。
从 slow query panel 回到 reset
先看:
再看 queryid 聚合:
检查:
- reset;
- dealloc;
- member/failover;
- calls vs mean;
- track planning/timing;
- dashboard delta 算法;
- query text 权限。
从 vacuum panel 回到对象与 blocker
再查:
- progress;
- old transaction/xmin;
- slot/feedback;
- relation size;
- freeze age;
- autovacuum config;
- host I/O。
不要因为 bloat panel 红就执行 rewrite。
从 log panel 回到 pipeline
先验证:
然后限定:
本章不导出 body;如果必须查看,在受限界面完成。
面板与规则必须共享 contract
如果 alert:
dashboard 却画:
值班无法复核 alert。至少提供:
- exact long/short expression;
- event count;
- threshold;
- pending/firing start;
- missing/freshness;
- release marker;
- rule evaluation error。
用 source link,不复制 payload
alert 携带:
而不是把 metric dump、SQL text、log body 全塞进通知。诊断包按权限拉取。
三次复核法
高风险动作前至少三类独立证据:
不是机械凑三条,而是让每条能 falsify 竞争解释。
Pigsty dashboard 的正确使用路径
下钻过程中始终保留 time range 和 identity。
平台升级验收
Pigsty/pg_exporter/PG major 升级后:
- inventory/target identity;
- raw exporter HELP/TYPE/labels;
- required metric presence;
- series cardinality;
- recording rule dry run/unit test;
- dashboard no-data/error;
- alert route test;
- native SQL parity;
- log parse/redaction;
- production canary/rollback。
不要以“Grafana 首页能打开”验收监控升级。
本节验收
你应当能解释:
- Pigsty v4 metrics、logs、traces、rules、dashboards、routes 各由谁负责;
- raw exporter label 与 storage external label 为什么不同;
cls、ins、ip与 scrapeinstance的区别;- RDS/Managed/Full 三种模式会缺哪些信号;
pg_query_*的querylabel 为什么是 queryid 而非 text;- exporter endpoint up 为什么不等于所有 collector 正常;
- VictoriaMetrics series count 为什么需要趋势而非单次上限;
- VMAlert 无 error 为什么不等于本章规则已部署;
- Alertmanager 无失败为什么不等于 receiver 已收到;
- trace backend healthy 为什么不等于应用有 span;
- 从 connection、I/O、replication、archive、query、vacuum panel 分别回到什么原生证据;
- 平台升级为什么必须做 metric/rule/dashboard/native parity。
上一节:把观察契约变成告警 · 返回本章目录 · 下一节:从告警到诊断包 · 查看全书目录 · 查看索引中心
25.6 从告警到诊断包
告警只告诉你一个合同条件成立。它不是事故报告,也不是根因分析。
值班最先丢失的往往不是 metric,而是上下文:
诊断包的任务,是在不增加事故的前提下,自动保存一个有界、脱敏、带身份与 时间语义的起点:
它不能承诺:
机器可读合同:
diagnostic-pack-contract.json。
25.6.1 自动保存时间窗、拓扑、变更与关键查询
诊断包从 manifest 开始
任何包先记录:
没有 manifest 的截图无法回答:
- 哪次事件;
- 哪个环境;
- 哪一版 query;
- 谁采集;
- 何时采集;
- 是否做了 mutation;
- 是否后来被改。
target 要通过多源确认
本章正式采集没有相信工作站 SSH alias,而是:
如果其中冲突:
连接成功不是身份验证。
时间窗要覆盖 onset 前后
本章合同默认:
这不是 universal window。选择应考虑:
- 最长 burn window;
- scrape/evaluation/group delay;
- release duration;
- query/log retention;
- incident severity;
- 存储成本。
至少保存:
如果 after window 尚未完成,可以:
- 先保存 initial pack;
- 15 分钟后补一个 immutable supplement;
- 不覆盖 initial;
- manifest 建立 parent/child。
保存 event count,不只保存 ratio
同样 10% bad ratio:
动作和置信度不同。SLO section 保存:
- good/bad/eligible count 或 rate;
- long/short window;
- threshold;
- burn;
- low-traffic flag;
- missing/freshness;
- independent probe;
- objective version。
不要只截图红线。
topology 是事件时刻拓扑
保存:
不自动保存:
- client address;
- password;
- connection URI;
- inventory secret。
IP 是否允许进入私密包取决于政策;本章公开摘要只保留教学网络身份。
变更时间线
收集:
每个 event:
不要直接收集个人聊天全文作为唯一 change log。
PostgreSQL section
本章自动保存:
为什么 activity 只自动聚合:
- 完整 query text 敏感;
- PID 列表可能很大;
- 一次 current sample 很快过时;
- 自动证据的目标是安全起点。
若 incident 需要 blocker graph,runbook 再用受限角色采集二级包。
SQL query controls
采集连接先执行:
并禁止:
READ ONLY 不是绝对安全证明:
- SELECT 仍可很贵;
- function 可能具有副作用,具体取决于实现与权限;
- 系统视图查询可拿锁;
- external extension 可能访问资源。
因此同时使用 allowlisted query、timeout 和 least privilege。
queryid-level top list
自动保存:
限制:
top 50 不是完整 workload。manifest 要写:
平台 section
保存:
- exporter/target freshness;
- VictoriaMetrics health/series;
- VMAlert groups/rules/error/missed iteration;
- Alertmanager failure counter;
- node resource trend;
- 日志 query count,不含 body;
- component versions。
本章公开摘要包括:
它是采集时刻 baseline,不能事后覆盖。
文件布局
建议:
正文和 body 类材料如确需保存,放在更严格的 evidence tier,不与常规诊断包 混合。
分区大小上限
本章每 section:
超限时:
- 不应无限截断而不标记;
- 记录
truncated=true; - 保存 total count;
- 使用 top-N;
- 提供 restricted source link;
- 按需发起二级采集。
初始包与后续包
不要让 T0 自动化拥有 T2 权限。
source hash 与可重放
保存:
- collector version;
- query template hash;
- rule/config hash;
- upstream governance run id;
- output hash;
- capture parameters。
这样以后能回答:
hash 证明 byte identity,不证明内容真实或政策正确;仍需身份和 source trust。
25.6.2 区分首发症状、伴随现象与根因证据
先写 timeline,不先写故事
事实表:
| 时间 | 来源 | 事实 | 置信度 |
|---|---|---|---|
| 10:01:30 | release event | version B 完成 | high |
| 10:03:00 | user SLI | latency short window 超阈值 | high |
| 10:04:00 | PgBouncer metric | wait queue 增长 | medium/high |
| 10:04:20 | trace sample | pool wait 占主要时间 | sampled |
| 10:05:00 | PG activity | active/wait 未显著变化 | point sample |
先不要写:
事实支持的初步说法:
四种状态标签
诊断包中的每条观察带 role,避免相关线自动升级成 root cause。
首发症状不是第一个 dashboard spike
“first observed” 受:
- scrape interval;
- evaluation interval;
- threshold;
- 日志延迟;
- 时钟;
- 采样;
- dashboard refresh
影响。可以说:
不要说“绝对第一个事件”,除非证据支持。
假设要写预测与反证
模板:
这比“看起来像连接池”更可执行。
反例一:CPU 同时升高
可能机制:
- 应用 retry storm 让 CPU 高;
- slow query 让 CPU 高并导致错误;
- batch 与 outage 巧合;
- monitoring query 自身;
- 另一 process。
需要:
CPU 是 correlate,不是自动 cause。
反例二:replica gap 与 stale read
强候选,但仍检查:
- probe 确实走 replica;
- token commit 已确认;
- clock;
- route;
- cache;
- transaction snapshot;
- replica state/timeline。
如果切 primary path 后新 token 立即满足,而 gap 回落后 replica path 恢复, 机制更强。
反例三:归档失败计数
时间线:
结论:
这是 falsification:反驳“非零 counter = 当前故障”。
反例四:锁已经消失
用户 latency 曾受 lock 影响,但初始包晚到:
不能因为 current view 空就否认历史;也不能因为日志有等待就证明当前仍阻塞。 timeline 必须保留各自时间语义。
修复验证要回到症状
如果动作是 cancel blocker:
只是组件验证。还要:
- user burn short/long window;
- event outcome;
- unknown writes reconciliation;
- correctness;
- pool/entry;
- 副作用;
- recurrence。
动作可能同时让 query 消失和用户失败增加。
recovery 不是瞬时绿
fast rule 的两个窗口恢复速度不同:
关闭事件政策可要求:
- 短窗恢复;
- 长窗下降趋势/低于阈值;
- independent probe;
- correctness check;
- no new unknown outcome;
- repair stable for observation period。
不要等所有长窗完全归零,也不要一个样本就关。
counterfactual
root cause review 要问:
不能真实重放生产时,可以:
- staging reproduction;
- synthetic fixture;
- plan/config diff;
- canary;
- independent domain evidence。
明确证据强度。
诊断包不是 postmortem
诊断包:
postmortem:
不要在自动包里预填 root_cause=database。
25.6.3 证据采集本身的负载和权限边界
observer effect
采集会:
- 建立连接;
- 拿
AccessShareLock; - 读取 shared stats;
- 执行 sort/aggregate;
- 查询 storage;
- 扫描日志;
- 占网络与磁盘;
- 出现在
pg_stat_activity/pg_stat_statements; - 影响被观察系统。
目标是有界,不是声称零影响。
风险分级
| 采集 | 风险 | 默认 |
|---|---|---|
| health/version/small metric | L0 | 自动 |
| bounded system-view aggregate | L0 | 自动 |
| top queryid without text | L0 | 自动,有 timeout |
| blocker graph/PID detail | L0/L1 data-sensitive | incident role |
| bounded log body | sensitive | 二级授权 |
| raw SQL/bind export | high data risk | 禁止自动 |
EXPLAIN |
low/moderate | 评估 function/lock |
EXPLAIN ANALYZE |
executes statement | 禁止自动 |
| statistics reset | destructive observation | 禁止 |
| load/fault injection | mutation | 隔离演练 |
EXPLAIN ANALYZE 会执行
对 DML:
真的执行 DELETE,除非在可回滚事务中且没有外部副作用;即便 rollback,也会:
- 运行 trigger/function;
- 拿锁;
- 产生 WAL/side effect;
- 消耗资源;
- 影响 sequence/external service 等。
诊断自动化不得把它当只读。
function 与 extension
SELECT 可以调用:
- volatile function;
- security definer;
- external API;
- advisory lock;
- file/extension;
- sleep。
allowlist system catalog/view query,不接受 incident 参数拼任意 SQL。
query timeout
本章:
timeout 后:
- 记录 section incomplete;
- 保存 error class;
- 不在 tight loop 重试;
- 不自动提高 timeout;
- 交给 operator 决定替代 source。
并发限制
对 100 个 database 同时执行 catalog query,单条很轻也会形成 load spike。
策略:
log query cost
无界:
会拖慢 VictoriaLogs,也可能返回大量敏感正文。
有界:
metric query cost
危险模式:
- regex 匹配所有 metric;
- 高基数 query label 长窗口;
- subquery 小 step;
topk前未聚合;- cross join;
- dashboard 多 panel 同时 refresh;
- incident automation 重复相同查询。
记录:
- expression hash;
- start/end/step;
- series count;
- duration;
- result size;
- timeout/error。
权限
数据库:
监控平台:
不应是一个万能账号。
evidence 文件权限
本章 private bundle:
reviewer 检查所有文件 mode。公共仓库只放 allowlist summary:
- 版本;
- 计数;
- pass/fail;
- declared gaps;
- run id;
- production gate。
evidence secret scan
自动扫描:
- SCRAM verifier;
- private key;
- credential-bearing URI;
- authorization header;
- clear password JSON;
query/raw_sql/sql_text字段。
扫描通过不证明绝对无秘密:
- 未知格式;
- encoded value;
- 业务 PII;
- hash 可重识别;
- file name。
仍要 data classification 与人工 review。
retention
本章 private evidence 教学默认 30 天;生产需独立政策。决定:
- 事件/合规需求;
- 敏感程度;
- 调查周期;
- 删除;
- legal hold;
- backup;
- 访问 audit;
- hash/manifest。
不要因为是“监控证据”永久保存。
完整性与可更新性
原始 initial pack 不覆盖。修正错误时:
公开摘要可以更新展示,但应保留 underlying immutable run reference。
failure-safe behavior
若 capture 失败:
本章 validator 对 application SLI absence 就是:
而不是失败或绿色。
自动化权限不能随事故升级
事故 severity 变高,不代表 collector 自动获得:
- superuser;
- SSH root;
- raw log body;
- query text export;
- failover;
- terminate;
- restore。
需要新 authority 的动作必须停下来进入 SOP/change plan。
诊断入口输出
给第 31 章的输入:
第 31 章再按症状分支:
本节验收
你应当能说明:
- 诊断包为什么先有 manifest;
- target 为什么要多源确认;
- 为什么同时保存 alert、collector 和 database clock;
- ratio 为什么必须带 event count;
- topology 与 change event 为什么要保存事件时刻版本;
- automatic pack 为什么只保存 queryid 聚合;
- T0/T1/T2/T3 四层 evidence 权限如何分开;
- symptom/correlate/hypothesis/cause 的措辞差异;
- 假设为什么必须带预测与反证;
- repair 为什么要回到 user/control signal 验证;
EXPLAIN ANALYZE为什么不能自动运行;- timeout/limit/concurrency 如何让采集 fail-safe;
0700/0600与 public allowlist 各解决什么;- secret scan 为什么仍不能替代数据分类;
- 采集失败为什么必须是 incomplete/unknown 而不是 healthy。
上一节:Pigsty 可观测体系 · 返回本章目录 · 下一节:实战:实现并演练观察契约 · 查看全书目录 · 查看索引中心
25.7 实战:实现并演练观察契约
本节把全章压成一条可重放的证据链:
它刻意把两种动作分开:
实验合同:
25.7.1 为延迟、错误、复制、备份和资源建立规则
文件布局
职责:
| 文件 | 证明什么 |
|---|---|
| requirements | target、risk、上游、hard rejection、gate |
| signal contract | source/type/label/reset/missing/cost |
| recording rules | SLI window 与诊断聚合 |
| alert rules | accepted/proposed policy |
| rule tests | synthetic arithmetic、pending、firing、recovery |
| AM config | empty sink route 和 inhibition |
| route tests | 八个 route、五个 inhibition 正反例 |
| diagnostic pack | 自动证据字段、limit、权限 |
| coverage | 已有信号、预期缺口、fallback |
| negative cases | validator 不是只会通过 |
| capture | live L0 baseline |
| exercise | remote /tmp isolated test |
| review | file mode、secret、claim、count |
| public summary | allowlist 结果,不含 private evidence |
上游 binding
第 24 章四份输入按 hash 绑定:
并要求:
如果上游改了 target、alert policy 或 missing semantics,本章 capture 不能继续 假装基于旧合同。
记录规则:可用性
五个窗口:
表达式:
注意三个设计:
- 分子是 bad eligible event,不是 instance;
- 分母没有任意
clamp_min(...,1)改变低流量比率; - 缺失由独立 freshness/metamonitoring 处理,不补健康零。
instrumentation 必须预初始化 outcome category,或者用能区分“零 bad”与“bad series 未产生”的设计。
记录规则:延迟
产生 5m/1h bad ratio。
规则可计算,但 alert governance 尚未接受:
记录规则:新鲜度
它不读取 pg_lag,因为用户 SLI 必须携带 commit token。
记录规则:PostgreSQL 诊断
这些是原因/容量输入,不自动 page。
记录规则:metamonitoring
一个重要限制:notification failure 为零仍不能证明 receiver receipt。还需要 外部 canary。
accepted 七条
validator 逐字段与第 24 章比对:
| alert | route | for |
|---|---|---|
| availability fast | page | 2m |
| availability slow | page | 5m |
| availability budget | ticket | 15m |
| freshness fast | page | 2m |
| correctness mismatch | page | 0m |
| capacity horizon | ticket | 1h |
| monitoring path broken | page | 5m |
比较:
- route;
- severity;
- objective;
- owner;
- runbook;
- first safe action;
- governance status;
for;- multiwindow token。
任意漂移使 lint 失败。
proposed 六条
| alert | 用途 | 为什么不直接上线 |
|---|---|---|
| latency fast burn | 用户延迟 | ch24 尚未接受 page policy |
| restore evidence stale | recovery control | route/severity 待接受 |
| archive failure active | recovery risk | threshold/pgBackRest 联动待评审 |
| transaction age | maintenance/capacity | workload-specific policy |
| freeze age | wraparound horizon | 应从配置/rate 生成 |
| SLI missing | observation path | expected traffic/fallback 待实现 |
统一:
validator 会拒绝 candidate 进入 page/ticket sink。
archive candidate 不比较历史总数
同时要求:
- 15 分钟出现新失败;
- 成功推进已经停滞。
仍需 runbook 复核 pgBackRest 和 restore evidence。
transaction candidate
只走 test,因为:
- batch 可能合法;
- idle in transaction 与 active 不同;
- cancel 权限与 unknown outcome;
- 阈值依赖 workload;
- user impact 可能不存在。
first action 是找 owner/queryid 与安全性,不是 terminate。
freeze candidate
固定值仅用于 synthetic test。生产应使用:
capacity accepted 规则仍需 reviewed input
accepted 是告警合同,未表示 forecast exporter 已实现。coverage 仍标记应用层 缺口。
rules 与 groups
三组 recording 和四组 alert 分开。原因是 VMAlert 的 recording result remote write 是异步的;同 group 依赖上一条结果可能读不到本轮。
25 个反例之一会把 alert 塞回 recording group,确认 validator 拒绝:
当前 live baseline
capture.py 只读:
不会读取 Alertmanager config,因为可能有 receiver secret;只保存 count 与
configuration_exported=false。
PostgreSQL target identity
工作站配置:
现场发现多个转发落到元节点。正式路径改为:
并验证:
这一检查会拒绝“查询成功但目标错误”。
实际 PG observation settings
正式快照记录:
两个待评审:
实验不修改配置。
25.7.2 注入可控症状,验证触发、路由、抑制和恢复
只做静态 lint
输出:
不访问远端。
正式完整运行
先创建一个新的私密路径;task.sh 也会拒绝覆盖非空目录:
动作:
capture 的 SQL 安全边界
query allowlist 不含:
即便如此,pg_locks 会看到采集查询自身的 relation lock;这是 observer
effect,证据不会假装完全无影响。
isolated workspace
exercise.py:
- 在元节点执行
mktemp -d /tmp/pg36-ch25.XXXXXXXX; - 验证路径符合严格 regex;
- 只上传四个无 secret 文件;
- 执行测试;
rm -rf只允许匹配该 prefix;- 断言目录已经不存在;
- 输出
remote_cleanup=ok。
如果 temp path 不匹配,宁可失败也不清理未知目录。
规则语法
证明 parser/规则结构可接受,不执行 live query。
合成时序
vmalert-tool:
availability recording 输入:
验证结果:
fast pending/firing/recovery
输入:
期望:
这验证 expression、for、label identity 和 recovery。
其他合成用例
correctness for=0m,capacity 需要 reviewed gauge,missing 不补零。
Alertmanager 配置检查
发现:
所有 receiver 只有 name,没有 integration。
八个 route
| 用例 | 期望 sink |
|---|---|
| availability page | pg36-shop-page-sink |
| correctness page | pg36-shop-page-sink |
| service ticket | pg36-shop-ticket-sink |
| platform ticket | pg36-platform-ticket-sink |
| metamonitoring page | pg36-platform-page-sink |
| diagnostic | pg36-null-sink |
| proposed | pg36-proposed-rule-sink |
| unknown | pg36-default-sink |
使用:
离线解析,不连接 live Alertmanager。
五个 inhibition
| source → target | 结果 |
|---|---|
| fast → same service/objective slow | inhibit |
| meta → derived missing | inhibit |
| meta → independent user symptom | do not |
| meta → correctness | do not |
| fast → other service slow | do not |
validator 同时解析 config 和测试表;添加 broad correctness inhibition 会被 反例拒绝。
25 个对抗性变体
类别:
validator 不是检查“文件存在”,而是逐个 mutation 后要求出现预期 rejection。
正式运行结果
组件:
在线:
PostgreSQL:
演练:
结论:
evidence
private bundle:
全部:
公共摘要只包含 allowlist,不包含 source body、SQL text、log body 或 receiver config。
重验
如果 source file 在 capture 后改变,hash 校验失败。必须新跑,而不是手工改 report。
没有 reset action
本章:
- 在线没有 mutation;
- remote temp 自动清理;
- isolated process 退出;
- 没有 database fixture;
- 没有 live rule。
所以没有数据库 reset。不要添加一个“为了形式完整”的 destructive reset。
25.7.3 输出告警覆盖表、盲区与 ch31 的诊断入口
14 行覆盖矩阵
每行:
五个应用缺口
| coverage | live | fallback |
|---|---|---|
| availability event | absent | independent place-order probe |
| latency histogram | absent | end-to-end duration probe |
| commit freshness | absent | unique-token probe |
| correctness reconciliation | absent | signed reconciliation |
| restore evidence age | absent | immutable restore manifest |
它们是 expected-gap,因为教学沙箱没有对应应用。
不得由:
代填。
已有 PostgreSQL/platform 覆盖
“已有”仍带盲区:
- one sample misses transient wait;
- PG I/O 不区分 OS cache/device;
- dead tuple 不是 exact bloat;
- queryid collision;
- endpoint 不证明 body/parse;
- backend 不证明 application spans;
- notification failure 0 不证明 receipt。
配置观察到的待评审项
auto_explain
生产前:
- 代表性 load test;
- short statement overhead;
- log volume;
- parameter leakage;
log_timing=off方案;- sampling;
- rollback;
- owner approval。
本章不直接说“必须关闭”,也不说“当前安全”。
log 0640
可能为 Vector collector group 提供 read。生产前:
- actual owner/group;
- group membership;
- directory mode;
- rotation;
- central storage ACL;
- retention;
- redaction;
- access audit。
alert coverage 不等于 implementation
七条 accepted alert 有规则,但 app source 不存在:
因此不能说“availability monitoring complete”。
proposed queue
需要治理拍板:
- latency page route/severity;
- restore evidence stale 的 change gate;
- archive risk 的 threshold 与 pgBackRest 证据;
- long transaction workload class;
- freeze horizon based on config/rate;
- expected traffic/missing source;
- real receipt canary。
每项完成后:
- 更新 ch24 governance;
- hash binding 失效;
- 重跑 ch25;
- canary deploy;
- 验证 route/receipt;
- 保留 rollback。
生产上线前的门禁
完成之前:
ch31 诊断入口
本章输出按 symptom 路由给第 31 章:
慢查询
锁与死锁
连接与池
复制与新鲜度
磁盘与容量
vacuum 与冻结
诊断入口的统一字段
第 31 章不需要从“打开所有 dashboard”重新开始。
完成标准
本章不是以“31 条规则存在”完成,而是:
本节验收
你应当能复述:
- 18 条 recording rule 分哪三组;
- 13 条 alert 中哪七条 accepted、哪六条 proposed;
- archive rule 为什么同时看增量与 last success;
- target identity 为什么需要 ProxyJump 后三源校验;
- capture 与 exercise 的风险/权限边界;
- synthetic time series 如何验证 pending/firing/recovery;
- route test 为什么使用空 receiver;
- inhibition 为什么必须有 must-not-inhibit;
- 25 个反例覆盖哪些失败模式;
- private evidence 与 public summary 如何分离;
- 五个 application gap 为什么不能用 PG component metric 替代;
auto_explain与0640为什么是待评审而非自动修复;- 生产门禁还缺哪些步骤;
- 第 31 章如何使用本章诊断包。
上一节:从告警到诊断包 · 返回本章目录 · 下一章:胸有成竹:容量规划与压测基线 · 查看全书目录 · 查看索引中心