Prometheus rate & irate
背景
实例状态、服务状态、Spring MVC 接口等相关监控打点看板基本都由架构、运维实现
但业务中会存在和业务数据更密切的监控需求,一般也使用 Prometheus 或类似的数据库来实现
对于 Prometheus 的函数选择存在一些疑问,所以写了这边文档,着重关注
rate
和 irate
的实现原理和实践方法,可以解释以下问题
- 为什么一个 QPS 看板缩小时间窗口后,某个点的 QPS 会上升
rate
和irate
的区别和实现方式是什么;以及increase
和delta
- 时间窗口内数据 range-vector 和步长 step Interval 之间的关系
- 如何选择合适的函数
- 聚合函数和时间向量函数的区别
实现
rate
rate
方法用于计算一组向量之间的速率
- 筛选时间窗口内样本,选择首尾两个样本计算速率
- 窗口外推
- 兼容计数器重置
1 | // ... 兼容计数器重制 |
计数器重置
通过将之前的值加到累积的结果中来保持连续性
1 | // Handle counter resets: |
对于普通浮点数值的计数器,当检测到当前值小于前一个值时(表示计数器重置),会将前一个值加到最终结果中
外推
外推的本意是避免某个窗口内样本过少造成的数据波动
如果样本足够接近范围的(下限或上限)边界,会将速率一直外推至该边界,足够接近的定义是不超过范围内样本间平均持续时间的 10%
如果小于阈值,则只外推一半
1 | // Duration between first/last samples and boundary of range. |
这里省略了外推计算时间窗口时对于 Counter 值为 0 的兼容
窗口内的所有点
last - first
rate
方法计算窗口内所有点的平均变化率
1 | sampledInterval := float64(lastT-firstT) / 1000 |
体现了其特点
- 观察较长时间的趋势,提供更平滑的曲线
- 有助于减少短期波动引起的噪音
irate
irate
和 rate
的区别是其只取最后的 2
个点
- 筛选时间窗口内样本,选择最后的 2 个点
- 处理计数器重置
最后两个点
index 1 - index 0
1 | resultSample.F = ss[1].F - ss[0].F |
体现了其特点
- 提供瞬时变化率,对短期变化更敏感
- 计数器重置处理更简单,忽略变化结果,因为不重视整体趋势
- 需要较小的时间窗口和步长配合,否则可能会导致数据失真
计数器重置
直接选择最后一个点的值
1 | switch { |
如果没有重置,则将 resultSample.F
值置为两个点差值
如果重置,则忽略,此时值已经是 ss[1]
图示
借用 irate() vs rate() – What’re they telling you? – Tech Annotation
提供的示意图
假设一组数据,每 10s 进行一次打点
这是 irate
方法的计算,每 20s 作为窗口计算其速率,步长为
10s


这是 rate
方法的计算,每 40s 作为窗口计算其速率,步长为
10s


比较其曲线

总结
rate
函数注重整体趋势,适用于
- 平滑稳定的指标观察
- 服务稳定的 QPS
- 网络流量的整体变化趋势
- 错误率的整体评估
- 长期趋势的监控
- 每日 / 每周 / 每月的业务增长情况
- 服务容量规划和扩展需求
- 长期可用性、性能等指标计算
- 报警
- 需要避免短期波动导致的误报
- 容量规划
- 资源使用趋势分析
- 预测未来负载增长
irate
函数注重短时间内的变化,适用于
- 实时监控
- 快速检测突发问题
- 实时监控系统状态变化
- 实时资源使用可视化
- 高变化率指标的观察
- CPU 使用率的瞬时波动
- 服务响应时间的实时变化
- 系统负载变化实时响应
- 自动扩缩容触发条件监控
- 流量突变的即时检测
步长和时间窗口
除了打点数据和其时间窗口,Dashboard 的绘制还需要通过步长 Interval 来确定窗口移动的速度
这个值应该和时间窗口符合一定的匹配关系
例如在 Grafana 中
Min interval
用于为查询间隔设置一个最小值;建议将其设置为数据写入频率,例如如果数据每分钟写入一次,则设置为 1mInterval
为计算后步长间隔,Max data points / time range
此值会作为步长用于实际查询Max data points
限制了点的最大数量,同时影响步长的计算;其默认最大值受 Grafana 图标的像素宽度影响
步长和时间窗口之间应该合理搭配,假设一个步长大于时间窗口,则会造成数据缺失
有了步长后,就可以按照步长推进时间窗口

假设如上一组数据,通过图示可以看出
- 5s 一次采样
- 一共采样了 15 个点
- 时间窗口为 30s
- 步长为 10s
开始的两组数据平稳增长,rate
和 irate
结果一致;而后到了第三组有一个 105 → 150 的突变,此时体现到
irate
的变化更大;对于最后一组数据,rate
对边界进行了外推,补充了缺失的窗口数据,得出了 3.19 的结果
其他函数
向量函数
对于时间向量相关的函数,还有诸如 delta
、increase
等
实际在 Prometheus 的源码中,它们都与 rate
复用了大部分代码
底层方法和其业务函数
- extrapolatedRate
- rate
- delta
- increase
- instantValue
- irate
- idelta
1 | // === delta(Matrix parser.ValueTypeMatrix) (Vector, Annotations) === |
其中不同的参数是第 4 和 5,它们分别为
第 4 个 isCounter
;如果为
true
,则允许处理计数器重置
第 5 个 isRate
;如果为
true
,则返回为每秒值,否则返回的是总值
由此可见
delta
强调双向变化(Gauge),负值或数值降低有其业务含义increase
用于计算总值,所以不需要处以时间
聚合函数
Prometheus 支持以下内置聚合运算符,这些运算符可用于聚合单个瞬时向量的元素,从而生成一个元素更少且具有聚合值的新向量
sum
(calculate sum over dimensions)avg
(calculate the arithmetic average over dimensions)min
(select minimum over dimensions)max
(select maximum over dimensions)bottomk
(smallest k elements by sample value)topk
(largest k elements by sample value)- …
这些运算符既可以用于对所有标签维度进行聚合,也可以通过包含
without
或 by
子句来保留不同的维度
可以用在表达式之前或之后
1 | <aggr-op> [without|by (<label list>)] ([parameter,] <vector expression>) |
对于时间窗口的优化
参考了公司的一篇文章,讲解了现在的 VictoriaMetrics 对于
Delta
类型数据计算 QPS 不友好之处
看起来和 Promethues
的实现类似;其本质上是现有计算逻辑对于稀疏打点的结果无法准确表达;虽然
Counter
在 Prometheus 中是以 Cumulative
方式实现的,这里作为参考
increase
的效果是计算区间内的最新一个点和最老一个点的差值rate
的效果是计算区间内的最新一个点和最老一个点的差值,再除以两者之间的时间间隔
存在的问题
如下场景可能出现对应问题
- 时间窗口小于采样间隔;导致折线图无法连接(采样率太低)
- 上报的指标数据的间隔不固定,计算出来的数据和真实值的差距会非常大(稀疏)
- 绘图的时间窗口无法被查询的时间范围整除,或者最后一段的数据还没完全到达(被平滑或外推)
- 计算时间窗口和数据采样之间的 offset 误差
rate_over_delta 的提出
rate_over_delta
函数的核心思想是
利用时间窗口内的数据除以数据的真实增长时间间隔,而非除以时间窗口
新的概念
- Counter 类型的数据进行一次
rollup
收集数据,as_count
基于收集的数据做求和运算;as_rate
是基于收集的数据计算 QPS,解决现有的问题 - 效果类似于
as_rate(rollup(container.cpu.time, default, 1m))
可能产生的问题
as_rate
和as_count
不再具有转化关系,在查询使用的时间范围很短并且数据上报不均匀的时候,as_rate
和as_count / window
的差值会比较大as_rate
和as_count
不适合放在聚合函数之后;因为真实的计算逻辑是先计算出 QPS 或者增量,再使用聚合函数进行计算
1 | # Prometheus rate |
相关 PR
参考
prometheus/prometheus: The Prometheus monitoring system and time series database.
irate() vs rate() – What’re they telling you? – Tech Annotation