打开一个仪表盘要花多少钱
一句话总结
打开一个仪表板的开销,不是“面板数量”,而是查询次数乘以刷新周期,而这个值没有人去数过。
为什么需要它
关于监控变慢的反馈,往往以“某个查询太重了”开头,以“仪表板太多了”收尾。但在这两者之间,有一个没有人数过的数字——打开一次仪表板,会发出多少次查询。
数法并不难。每个面板至少带有一个查询,把这些查询全部加起来;界面上方的变量中,如果有需要向服务器询问列表的,也按数量加上。八个面板、十个查询、两个变量查询,打开一次就是十二次。再乘上自动刷新。10 秒一个周期,一分钟六十次,一小时三千六百次。一块挂在墙上的屏幕,一天就会发出八万次查询,在没有人看这块屏幕的夜里也同样发出。
更糟的是,这种开销在界面上完全不会显现。多加一个面板只需要点三下,而开销却发生在别的团队的 Prometheus 上。所以仪表板总是只朝着变重的方向生长。
工作原理
开销分三层累积。
| 层 | 数什么 | 从哪里读 |
|---|---|---|
| 查询次数 | 打开时几次,一分钟几次 | 仪表板 JSON 的面板、查询、变量和 refresh |
| 序列数量 | 一个查询返回多少行 | 实际发出查询,数结果的行数 |
| 点的数量 | 一个序列上有多少个点 | 时间范围除以分辨率 |
第一层只看 JSON 就能数出来。仪表板 JSON 模型中,panels 数组、每个面板的 targets,以及自动刷新周期 refresh 都原样写在里面(仪表板 JSON 模型文档)。变量在 templating.list 中,其中会向服务器询问列表的是 type 为 query 的那些。
第二层必须发出查询才能知道。sum by (handler) (...) 返回四个序列,但如果去掉聚合,直接绘制 rate(http_request_duration_seconds_bucket{...}[5m]),就会得到四个处理器乘以十二个桶,共四十八行。同一个面板里画了四十八条线,从那个界面上什么也读不出来。序列数量决定了浏览器要绘制的点的数量,因此成为界面变慢的真正原因。
第三层是时间范围和分辨率。区间查询接收 start、end、step,并按 step 的间隔把这段时间切开后返回(Prometheus 查询 API 文档)。看两个小时、每隔 15 秒,一个序列有 481 个点;每隔 300 秒,则是 25 个。同样的问题,相差二十倍。在 6 小时的界面上,几乎不需要 15 秒的分辨率。
减少开销的方法中,最便宜的是把已经知道答案的计算提前算好。与其每次都从四十八个桶序列中计算分位数,不如让 Prometheus 按固定周期做一次这个计算,并把结果留成新的时间序列。这就是记录规则(recording rule)。仪表板只需要读取这个新的时间序列。命名有既定的惯例——写成 수준:지표:연산(占位符依次为级别、指标、运算)的形式(记录规则惯例)。
在现场相遇的样子
数据平台团队的 Prometheus 每天下午都会变慢。元凶不是人们打开的仪表板,而是挂在会议室墙上的两块屏幕。两块屏幕的刷新都是 5 秒,各有十四个查询。即使在没有人看的时间,一分钟也要发出三百三十六次。只把刷新改成 1 分钟,再删掉三个不用的变量,这个负载就变成了原来的二十八分之一。一个面板也没有删。
另一个常见的是没有人使用的变量。界面上方的下拉框一旦创建,就没有人去删,但每次打开仪表板时,都会为了填充列表而发出查询。即使没有任何面板引用这个变量,也是如此。
本环境能判定与不能判定的内容
在这个 Pod 中,用毫秒来衡量开销没有意义。两个 CPU 由学习者进程和评分器共用,同一台 Mac 上还有其他容器同时运行,所以同样的查询每次测量得到的时间都不一样。因此这里只用数量来判定——从仪表板 JSON 中重新数出的面板、查询、变量的数量,实际发出查询得到的序列数量,区间查询返回的点的数量。记录规则则要实际应用到 Prometheus 上,看其值是否与原查询一致。界面实际能多快显示出来,最终还是要在浏览器里用眼睛看。
下一项实验要做什么
拿到一个超出预算的仪表板,给它估值。从 JSON 中数出面板、查询和变量查询,得出“打开一次时的查询数”;把每个查询实际发出,测出序列数量,找出最重的面板。改变时间范围和分辨率,测量返回的点的数量,再乘以刷新周期,得出每分钟的查询数。把沉重的分位数计算迁移为记录规则,用更低的代价得到相同的值;把预算写成文件,再制作检查该预算的脚本,并确认缩减后的仪表板能通过这项检查。