3.1 窗口聚合与降采样


3.1 窗口聚合与降采样

本节摘要滑动窗口(如 [5m])用于 rate/avg;固定窗口(calendar bucket)用于报表。Counter 必须先 rate()increase() 再聚合;Gauge 可直接 avg_over_time。长期保留需 降采样(downsampling) 或 continuous aggregate。本节对照四家窗口语义。

上手前先明确

阅读完本节,你应当能够:

  1. 区分 counter 与 gauge 的聚合路径
  2. 解释 PromQL [5m] 与 SQL time_bucket('5 min') 对齐差异
  3. 设计 raw + 1h roll-up 两级保留

一、问题与直觉

运维问「过去 5 分钟 CPU 均值」——若对 counter 原值直接 avg,重启后计数归零会产生假尖峰。Prometheus 用 rate() 先差分再除时间;SQL 侧 Timescale 用 counter_agg() 或应用层 LAG。窗口边界对齐也关键:Prometheus 步长对齐 UTC;IoT 报表常对齐本地时区整点。

先看一个直观的对比:假设某个 counter 在 t0=10、t1=20、t2=40、t3=70,间隔 5 秒。直接对原值求 avg 得到的是「累计计数均值」,毫无速率含义;而 rate[15s] 在 t3 处计算 (70-10)/15 ≈ 4 次/秒,才是真正的速率。这就是「先差分再聚合」的意义。

二、核心原理

类型 含义 聚合前处理 示例 metric
Counter 只增不减(可 reset) rate/increase http_requests_total
Gauge 可升可降 直接 avg/sum memory_usage_bytes
Histogram 桶计数 histogram_quantile http_duration_bucket

Prometheus:rate 与 avg_over_time

# counter:先求每秒变化率,再在 5 分钟窗口上聚合 rate(http_requests_total[5m]) # gauge:直接窗口聚合 avg_over_time(memory_usage_bytes[5m]) # counter 的窗口增量(跨重启安全) increase(http_requests_total[5m])

Prometheus 的 [5m] 是滑动窗口:每个求值点向前回看 5 分钟。因此查询结果曲线上的每个点,其窗口与相邻点是重叠的——适合实时曲线,但不适合「按小时计费」这类需要互不重叠桶的报表。

InfluxDB Flux:管道式窗口

from(bucket: "telegraf") |> range(start: -1h) |> filter(fn: (r) => r._measurement == "cpu") |> aggregateWindow(every: 5m, fn: mean)

Flux 的 aggregateWindow(every: 5m) 是固定桶:把时间轴切成互不重叠的 5 分钟段,每段内求 mean。语义上更接近 SQL 的 GROUP BY time_bucket,而不是 PromQL 的滑动窗口。

QuestDB:SAMPLE BY 对齐窗口

SELECT ts, avg(price) FROM trades WHERE ts > dateadd('h', -1, now()) SAMPLE BY 5m;

QuestDB 的 SAMPLE BY 也是固定窗口,默认按时间戳对齐,可配合 FILL(PREV)FILL(LINEAR) 等策略处理空桶。

能力 Prometheus Timescale Influx QuestDB
滑动 rate rate()[5m] counter_agg derivative 需 SQL 表达
分位数 histogram_quantile approx percentile 有限 SQL percentile
降采样物化 recording rules continuous aggregate tasks materialized view

窗口对齐与缺点填充

「对齐」是跨引擎对比最容易翻车的细节。PromQL 滑动窗口的对齐点是查询步长(默认由 Grafana 的 step 决定);SQL time_bucket 默认 UTC 对齐,即桶边界是 00:00、00:05、00:10;IoT 场景想对齐本地时区,需要在 time_bucket 里指定 offset。空桶(没有任何样本的窗口)同样值得警惕:

填充策略 语义 适用
fill(0) 空桶填 0 计数类、SLA 计算
fill(previous) 沿用上一个值 波动平缓的 gauge
fill(null) 留空不画点 保留真实缺失信号
线性插值 两端取平均 近似补点

在告警与计费场景里,fill(0)fill(previous) 会给出完全不同的结果,SLA 面板与计费报表必须显式约定填充策略,否则跨面板对比时会出现「同一数据两套结论」。

三、工程实践要点

Recording rules:把高频 PromQL 预计算成新 metric,减轻查询端压力——适合 dashboard 固定面板。

# prometheus.yml 中的 recording rule 示例 groups: - name: cpu.rules rules: - record: job:cpu_usage:avg_rate5m expr: avg(rate(cpu_usage_total[5m])) by (job)

Continuous aggregate(Timescale):后台刷新 1h/1d roll-up,查询长周期只扫聚合表——某 IoT 场景 92.7% P99 延迟在索引定位,降采样后扫描量降一个数量级。

CREATE MATERIALIZED VIEW cpu_hourly WITH (timescaledb.continuous) AS SELECT time_bucket('1 hour', time) AS bucket, device_id, avg(usage) AS avg_usage FROM cpu_metrics GROUP BY bucket, device_id;

缺点填充fill(0) vs fill(previous) 会改变告警结果——SLA 面板与计费报表要显式约定。

两级保留的落地组合

把「降采样」与「保留」结合,最常见的生产形态是:原始数据保留 90 天,1 小时聚合保留 2 年,1 天聚合保留 5 年。在 Prometheus 系用 recording rule + 不同 retention 的 remote storage;在 Timescale 用两张 continuous aggregate + 两条 retention policy;在 Influx 用 task 写三个 bucket。三层粒度各有生命周期,查询端按时间范围自动落到对应粒度,这是「以时间换成本」最成熟的做法。

补充一点窗口聚合的直觉:窗口宽度本质上是对时间轴的「量化」。宽度越小,曲线越精细、数据量越大;宽度越大,曲线越平滑、数据量越小。降采样就是把这种量化从「查询时临时做」变成「写入时提前做」,用存储空间的节省换查询时间的稳定。做两级保留时,务必把「每层保留多久、每层供什么查询」写清楚,否则后人在线查询时会困惑于「为什么 90 天前的数据少了」。

03-03-fig01-4

⚠️ 常见坑rate() 窗口小于 scrape 间隔 2 倍——样本不足,rate 抖动或空值。

💡 关键直觉:窗口是「时间上的卷积核」——先选对 counter/gauge,再谈窗口宽度。

自测题

  1. 为什么对 counter 直接 avg 会产生假尖峰?用一组具体数字说明。
  2. rate[5m]time_bucket('5 min') 的对齐差异会造成什么实际影响?
  3. 设计一个「原始 90 天 + 小时 2 年 + 日 5 年」的三级保留方案,说明各层的查询用途。

提示:第三题没有唯一答案,重点在于每一层都能回答「谁在什么场景查它、丢精度是否可接受」。

要点速记

  • Counter:先 rate 再聚合
  • Gauge:直接窗口聚合
  • Histogram:桶 + quantile
  • 降采样:raw 热 + roll-up 冷
  • 对齐:Prometheus UTC vs 业务时区

下一节用对照表串 PromQL 与 SQL 常见模式。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U