图表不是数据,而是数据的摘要
一句话总结
仪表板不是数据,而是数据的摘要。摘要的方法有两种,答案也就有两个,而且两个都对。所以“图上没有看到”并不等于“没有发生”。
为什么需要它
在事故会议上会出现这样的对话。“错误率涨到了 28%。”“我们的仪表板上看起来是 10%。”两个人看的是同一个 Prometheus,而且谁的查询也没写错。只是一个人用 5 分钟窗口来问,另一个人用一小时窗口来问。
这种偏差看起来微不足道,却会改变决定。28% 是一天就能烧掉错误预算的数字,而 10% 是“再观察一下”的数字。如果告警设在 15%,那么在一张图上应该响起,而在另一张图上则没有响起的理由。实际上,事故之后追查“为什么告警没响”,罪魁祸首往往不是规则的阈值,而是规则所使用的窗口长度。
还有相反方向的错觉。有数据,图却是空的。如果对抓取间隔为 15 秒的指标使用 rate(...[15s]),窗口内只有一个样本,无法算出变化率,Prometheus 返回的不是错误,而是空结果。屏幕上会显示“No data”。看到它就解读为“流量中断了”,就会凭空制造出一场不存在的故障。
工作原理
范围查询(/api/v1/query_range)接收三个参数——start、end、step。Prometheus 会建立一个从 start 到 end、以 step 为间隔的网格,并在每个网格点各执行一次即时查询,再把这些值连接起来。图上的线不是数据,而是这些网格点。
如果在此基础上加上 rate(...[창])(占位符为窗口),每个网格点的值就成为该点之前一个窗口长度内的平均变化率。所以窗口起着低通滤波器的作用。把一次 20 分钟的激增放在一小时窗口里看,值大约会被压低到三分之一,而激增会在进入窗口的时间范围内向左右扩散,使宽度变宽。高度和宽度都变了,但下方的面积几乎保持不变——所以“总共有多少次”是对的,只有“有多严重”是错的。
窗口还有下限。Prometheus 的查询函数文档和 Grafana 都建议至少是抓取间隔的四倍。Grafana 的 $__rate_interval 是把这条规则自动化的变量,按 max($__interval + scrape_interval, 4 * scrape_interval) 计算。其中 $__interval 是面板的时间范围除以面板像素宽度所得的值。也就是说,把面板放宽来看,窗口会自动变宽。 同一个面板,从 12 小时换成 30 天,峰值会自己变低,原因就在这里。
聚合也有同样的陷阱。如果用 avg 对比率求平均,流量少的时段和流量多的时段就有了相同的权重。想知道整体比率,必须用总和除以总和。分位数更糟——百分位数从根本上就不是可以求平均的值。直方图必须先用 sum by (le) 汇总桶,再调用 histogram_quantile,如果对已经算出的多个 p99 求平均,得到的就是毫无意义的数字。
increase() 和 rate() 会在窗口两端做外推。因为样本很少恰好落在窗口边界上,结果是 increase() 会把本应是整数的值,比如请求数,返回成 20316.33 这样的小数。如果原样显示在面板上,就会被问到“0.33 次是什么意思”。
| 想问的问题 | 应该使用的 | 常被使用的 |
|---|---|---|
| 最糟的时候是多少 | 短窗口 + 密集的 step | 面板默认值原样不动 |
| 整个周期的比率 | 总和 ÷ 总和 | 比率的平均值 |
| 尾部延迟 | sum by (le) 之后再 histogram_quantile |
分位数的平均值 |
| 精确的次数 | 计数器的差值 | increase() 的小数点 |
在现场相遇的样子
有个团队把 30 天的仪表板原样用在了事故复盘上。那个面板的 $__rate_interval 超过了两小时,这次 20 分钟的事故,在线上只留下一个很小的凸起。复盘结论是“影响不大”。用 6 小时的范围重新打开同一次事故,峰值以 28% 的高度立在那里。改变的只是浏览器地址栏中的时间范围。
在另一个团队里,“昨晚失败次数”面板显示出 20316.33。负责人为了去掉小数点,套上了 round(),此后没有人知道那个数字是外推值。在计数器重置的区间里,那个面板一直在悄悄地显示错误的值。
下一项实验要做什么
制作一个发出范围查询、输出点数、最大值、超过阈值次数的小工具,并用这个工具,以多种方法询问同样的 12 小时。把窗口缩小到 15 秒,看图变空;把窗口从 1 分钟加宽到 1 小时,测量峰值如何降低、宽度如何变宽;在 step 中亲自计算 $__rate_interval,重现面板宽度决定窗口的过程。比较比率的平均值与总和 ÷ 总和,确认长窗口的 p99 为什么不是最坏情况的 p99,最后修复从生产环境仪表板上照搬来的三个坏面板并提交——评分器会真正发出修复后的查询,并根据值来判定。