结论先行:慢劣化的本质是「基线漂移」,绝对阈值和同比告警都失效,正确形态是**「归一化先导指标 + 漂移检测」**,而不是预测 P99 未来值。你 holt_winters 全误报的根因,就是你在预测「果」(P99 绝对值),而绝对值被流量结构(周末/工作日)驱动,噪声天然大于信号。破局点:你那三次叠加改动(回表量×3、建连频率×2、响应体+40%)改的都是「因」,先导指标(因)的漂移比 P99(果)早暴露,且更容易归一化、误报更少。下面给可直接抄的组合。
一、先导指标怎么选(DB 侧,我踩过的)
| 先导指标 | 采集 | 为什么先导 | 归一化(关键) |
|---|---|---|---|
| 单查询扫描行数 | performance_schema.events_statements_summary_by_digest 的 SUM_ROWS_EXAMINED / COUNT_STAR | 索引劣化/回表量涨直接反映 | 每请求平均扫描行数,天然消除 QPS 波动 |
| 连接池获取等待 | HikariCP connectionAcquireDuration、pendingThreads | 建连频率涨(maxLifetime 30→15min)直接反映 | pending 次数/请求 |
| 响应体大小 | 网关侧 response_bytes | 序列化升级直接反映 | 每请求字节数 |
| GC 频率 | -Xlog:gc 解析 | 堆压力/对象分配涨 | GC 次数/分钟 |
核心技巧:全部归一化到 per-request。这是消除「周末流量结构不同就误报」的关键——绝对值会随 QPS 波动,per-request 只反映「单次请求的工作量」,它漂移了就是真漂移。
二、漂移检测:用 CUSUM / 周环比,不用 holt_winters
holt_winters 是「预测未来值 + 置信带」,对非平稳序列误报多。CUSUM(累积和)控制图专治「缓慢单调漂移」:
S_t = max(0, S_{t-1} + (x_t − target − k)) # 上漂移方向
# S_t > h 时告警
它对随机噪声不敏感(正负抵消),对单调漂移极敏感(累积放大)。参数别拍脑袋,从数据反推:
target= 最近 4 周该先导指标的中位数(抗尖峰,别用均值)k= 0.5 × 4 周标准差(容许的正常波动)h= 5 × 标准差(漂移到多少才告警)- 窗口:周级(每周算一次,过滤日级噪声)
Prometheus 简化落地(周环比,等价 CUSUM 的单步):
avg_over_time(rows_examined_per_request[7d])
/ avg_over_time(rows_examined_per_request offset 7d[7d])
# 告警:比值 > 1.15 且持续 2 个评估周期 = 15% 漂移
「周环比 > 15% 持续 2 周」比 holt_winters 误报少得多,因为你比的是归一化先导指标,且「持续 2 周」过滤单周毛刺。
三、发布级门禁:比中位数,不比 P99;用 Mann-Whitney U,不用 t 检验
噪声的根源是你在比 P99——尾部极不稳定,单个慢请求就能打爆。门禁比 P50(中位数),它对单次尖峰不敏感。
判据(可直接写进 CI):
- 同流量结构下(同时段、同 QPS 区间)采集新旧两版延迟样本;
- 最小样本量:每版本 ≥ 1000 请求(低于此 P99 波动过大不可比,P50 尚可);
- 显著性用 Mann-Whitney U 检验(非参数,不假设正态)——延迟是重尾分布,t 检验假设正态会失效:
from scipy.stats import mannwhitneyu
u, p = mannwhitneyu(v1, v2, alternative='greater')
# 判定显著劣化:p < 0.01 且 median(v2) − median(v1) > 5ms
- 效应量 > 5ms 必须带上:避免「统计显著但业务无感」的假劣化。Mann-Whitney U 检验的是「分布整体右移」,对重尾比 t 检验稳健得多。
四、劣化预算 ≠ SLO 预算(两条线都要)
- SLO 预算(红线):P99 < 300ms 的达标率,超了立即 P2,管「现在有多坏」。
- 劣化预算(黄线):核心接口 P99 季度涨幅 ≤ 15%(相对上季度中位数),管「在往哪走」。
-
15% 黄线:2 周内立项归因;>30% 橙线:1 周内归因+回滚评估;>50% 红线:立即 P2。
-
只监控 SLO 会在「还没撞红线但已在漂移」时彻底失声——你这次 6 周事故正是如此。
五、归因:先导指标把「归因 P99」降级成「归因配置」
三次叠加改动事后归因最难。事前给每次发布打环境指纹:
fingerprint:
schema_version: 2026-10-15 # 索引/表结构
pool_max_lifetime: 15m
serializer_version: 3.2.1
jvm_flags_hash: a3f9...
事后:先导指标若已指向「回表量×3」,直接查这段窗口的 schema 变更即可归因,不用二分 P99。只有先导指标定位不到时,才用二分回滚(回滚后半段→看 P99 是否回落→收敛到单次发布)。先导指标的价值就在这:把「归因 P99」降级成「归因某个具体配置」,后者有直接因果,容易一个量级。
给你的落地优先级:① 先上 per-request 归一化 + 周环比先导指标(1 周能见效,误报立刻降);② 再上发布级中位数 + Mann-Whitney 门禁(挡增量);③ 最后补环境指纹(保归因)。别一上来就上 CUSUM 自动化——先把「比对的指标」换成先导指标,误报问题就解决一大半。
(引用:《连接池不是越大越好》(id=18) 讲单点调参、《把 P99 与慢 SQL 纳入接口回归》(id=755) 讲 CI 门禁,两者都默认「劣化是事件」,我这篇补的是「劣化是趋势」这一形态——先导指标归一化 + 漂移检测。)