3119 字
约 10 分钟
20
S07-A09 · 变更后的验证:改完怎么知道真的对了

改完 Nginx 的 proxy_read_timeout,nginx -t 通过、reload 返回成功、监控一片绿。三天后才发现超时还是原值:配置写在 server 块里,被更靠内的 location 块覆盖了。整个过程没有一条告警。

这类事故结构相同:验证只证明了「没有新增报错」,变更要证明的是「目标行为已成立」。前者是「异常集合为空」,后者是「期望命题为真」。空集可以被无数原因造成——流量没进来、分支没走到、开关没读到、缓存没失效。

所以验证的判据只有一句话:能举出一条证据,它只在变更生效时才出现。 举不出来,就还停留在「看起来没问题」。


1. 先分清三个命题

命题 典型证据 能证明 不能证明
没报错 错误率、异常日志为空 新路径没抛错 新路径是否被执行
生效了 生效值快照、行为差异 目标行为出现了 是否覆盖全部目标范围
对了 业务语义指标、对账 结果符合预期 是否由本次变更造成

三个命题的价码差得很远:第一条几乎免费,第二条要专门埋点,第三条要对照组甚至等一个账期。跳过第二条直接看第一条,就是「三天后才发现」的标准成因。

2. 验证的四个层次

层 最小证据 代价 能拦住 拦不住
进程活着 进程存在 + 端口 listen + 启动日志最后一行 秒级 起不来、崩在启动 起来了但依赖没连上
请求通 健康检查 + 一条真实业务请求的状态码与耗时 分钟级 5xx、路由错、鉴权错 200 但内容是空数组
指标对 目标指标的方向与幅度 十分钟级到窗口 阈值写反、方向反 指标对但业务不对
业务语义对 一条可核对的事实:某笔单、某个计数、某个字段 小时级,需人工或对账 口径错、范围错 长尾样本

层次不是越高越好,而是按影响面选到第几层:改的是「能不能跑」,第 2 层够;改的是「跑得对不对」,必须到第 4 层。只做 1、2 层就声称验证过,是绝大多数隐性失效的来源。

3. 「生效」的三种证据与可信度

证据 证明什么 不证明什么 采集成本
配置读取日志 这个键被读到了 是否被使用 极低,改一次代码
运行时快照 值在内存里是对的 这条路径取的是它 低,命令即可
行为差异 结果确实变了 变化由本次变更导致 中,要固定输入

可信度排序是 行为差异 > 运行时快照 > 配置读取日志。倒序排是常见错误:日志只证明「读了」,常读的还是默认值。

// 层 3:取运行时生效值,不看启动脚本里的文本
$ nginx -T 2>/dev/null | grep -B5 -A3 'proxy_read_timeout'
$ jcmd 5123 VM.flags | tr ' ' '\n' | grep -iE 'heap|gc'
$ redis-cli CONFIG GET maxmemory
// 层 4:固定输入比对输出字段,不要只比状态码
$ curl -s "$BASE/api/quote?sku=S1" -o before.json -w '%{http_code}\n'
$ curl -s "$BASE/api/quote?sku=S1" -o after.json  -w '%{http_code}\n'
$ diff <(jq -S . before.json) <(jq -S . after.json)

4. 观察窗口由问题的时间尺度决定

窗口的正确写法是:这个变更可能以什么方式错,那种错最早、最晚什么时候可见,取后者乘安全系数。

变更类型 暴露尺度 最小窗口下限 原因
超时、连接池 秒到分 10–30 分钟 要等一次慢请求才触发
缓存策略 一个 TTL ≥ 2 × TTL 第一轮读到的仍是旧值
定时任务 一个调度周期 ≥ 1 周期 + 余量 只在触发时刻暴露
对账、结算 一个账期 ≥ 1 个账期 日终才跑批
内存增长 小时到天 按斜率反推 看趋势不看瞬时值
长尾客户 天到周 分群观察 小流量打不到

「拍 30 分钟」之所以流行,是因为它刚好覆盖「请求通」这一层。窗口不足的代价不是多等一会儿,而是「这个失效要靠下一个人偶然发现」。

5. 对照组:同口径才有结论

可比条件 不满足时的假象
负载量级相当 流量跌了 30%,指标自然变好
时段与业务周期一致 白天改、夜里比,曲线必好
样本口径一致 只统计成功请求,错误率当然降
缓存状态一致 一边冷一边热,耗时不可比
采集口径一致 埋点被顺手改过,指标换了定义
// 前后对比必须同查询、同粒度、同过滤
$ q() { curl -s "$PROM/api/v1/query" --data-urlencode "query=$1" | jq -r '.data.result[0].value[1]'; }
$ q 'sum(rate(http_server_requests_seconds_count{uri="/api/pay"}[5m]))'
$ q 'sum(rate(http_server_requests_seconds_count{uri="/api/pay",status=~"2.."}[5m]))'

没有对照组的「指标变好了」是最危险的一类结论,因为它在报告里和真的变好长得一模一样。

6. 自动化能判定的与必须人判断的

判定项 能否自动 判据
进程/端口/健康检查 能 布尔结果
关键字出现在日志 能 正则命中
指标方向与阈值 半自动 方向可判,「幅度是否够」要人
业务语义等价 半自动 对账脚本算差异,人签字
影响面是否覆盖全部调用方 人 需要调用图之外的业务知识
回滚后是否回到原状 半自动 快照对比 + 人判断残留可接受性

自动化的边界在「差异」与「判断」之间:凡是需要判断「差异是否可接受」的地方,脚本只能算出差异,判定在人。硬编码阈值的代价是误报频繁,误报多了整条流水线会被绕过。

7. 「看起来好了」的三种假象

假象 观测特征 判别方法
缓存未失效 改完立刻「生效」,旧值仍周期出现 用必然未命中的键(随机后缀)再请求
灰度只覆盖小流量 指标几乎不动,日志里却有新版本 先查流量分配再看指标,顺序不能反
指标口径变了 指标改善但业务无感 变更清单里核对是否动了埋点与过滤条件
// 三件事按序核:流量到没到、缓存绕没绕、口径变没变
$ curl -s "$BASE/api/ver" | jq -r '.build, .instance'
$ curl -s -H 'Cache-Control: no-cache' "$BASE/api/quote?sku=S1&probe=$RANDOM" -o /dev/null -w '%{http_code}\n'
$ curl -s "$PROM/api/v1/query" --data-urlencode 'query=count by (version)(up{job="api"})' | jq .

8. 回滚的验证同样是验证

回滚只回到代码原状,不回到系统原状。变更里有「写」的动作,回滚就得逐项验证残留。

回滚后仍需确认 为什么不会自动恢复
已写入的数据形态 脏数据不会因代码回滚而消失
缓存条目 新格式被旧代码读到,反序列化失败或读出错值
注册与连接 新实例仍在注册中心、长连接仍指向新节点
外部副作用 已发出的消息、已扣的额度、已置的开关
表结构与索引 DDL 通常不可逆
// 回滚验证的最小集合:版本、缓存、注册中心
$ curl -s "$BASE/api/ver" | jq -r '.build'                       // 期望回到上一个 build
$ redis-cli --scan --pattern 'quote:v2:*' | head -20             // 新格式键是否残留
$ curl -s "$REGISTRY/api/instances?service=api" | jq -r '.instances[].version' | sort -u

可操作的判据:变更清单每出现一个写动作,回滚清单就加一行验证;行数对不上,说明回滚预案是抄的。

9. 责任归属与度量

分工只有一句话:改的人负责证明生效,看的人负责定义「什么算生效」。这句落不下来,验证就退化成「谁有空谁瞄一眼」。

度量 定义 分母 用途
变更验证覆盖率 有生效证据的变更数 / 全部变更数 全部变更 看漏测面在哪些类型上
验证到发现问题时长 变更时间点到「发现未生效」 未生效变更 判断窗口是否普遍偏短
假阳性率 验证通过但实际未生效 / 验证通过数 验证通过数 判断证据是否只是「没报错」
单次验证人工成本 人工分钟数 / 变更数 变更数 判断是否过度验证

这四个数不需要平台,变更单加两列就能统计。难的不是度量,是承认「验证通过」有时只是「没人看」。

10. 排错:六种「验证了但没验证到」

现象 原因 处理
配置改了没生效,-t 却通过 块级覆盖、include 顺序 看 nginx -T 合并后的全文,不看源文件
日志里找不到生效值 只打了入参,没打最终值 装配完成后打一次 effective-config
指标变好但业务无感 流量下降、分母变小 加对照组,固定分母
重启生效,运行期改不生效 值被缓存或只在启动读一次 区分启动参数与热配置
灰度通过,全量回归 灰度流量不含问题路径 按租户或调用方分群
回滚后仍报错 缓存、schema、消息残留 回滚清单包含清理新格式副作用

11. 边界:什么时候验证可以压到一行

变更类型 压到什么程度 理由
纯文档 一行:链接可访问 + 版本匹配 无运行时状态
纯样式、纯文案 一行:目标页面渲染无回归 影响面静态可推
常量替换等可完全静态推导 编译 + 一次抽样请求 没有运行时分支
只读查询加索引 到第 3 层 可回退、无写入
涉及写入、资金、权限 到第 4 层 + 对照组 + 对账 错了不会自己暴露
跨服务契约 到第 4 层 + 消费者抽样 影响面在别的系统里

反过来的边界也要写:影响面「说不清」的变更,不能因为「改动很小」而降级验证。改动大小与影响面是两个维度,小改动打穿全局更多,它绕过了评审。


12. 自测

  1. 「错误率为零」这条证据能证明哪个命题,不能证明哪个命题?
  2. 只改了「跑得对不对」类的逻辑,验证该做到第几层?只做到第 2 层会漏掉什么?
  3. 三种「生效」证据里,哪一种可信度最低,为什么它最容易骗人?
  4. 一个缓存 TTL 是 10 分钟的变更,观察窗口为什么不能取 10 分钟?
  5. 指标改善了 20%,你怎么在十分钟内判断这是变更效果还是流量下降?
  6. 回滚之后,为什么「版本号回去了」不等于验证完成?
S07-A09 · 变更后的验证:改完怎么知道真的对了
https://www.clxhxhhr.top/posts/6272/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。