本节摘要:滑动窗口(如
[5m])用于 rate/avg;固定窗口(calendar bucket)用于报表。Counter 必须先rate()或increase()再聚合;Gauge 可直接avg_over_time。长期保留需 降采样(downsampling) 或 continuous aggregate。本节对照四家窗口语义。
阅读完本节,你应当能够:
[5m] 与 SQL time_bucket('5 min') 对齐差异运维问「过去 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 |
# counter:先求每秒变化率,再在 5 分钟窗口上聚合 rate(http_requests_total[5m]) # gauge:直接窗口聚合 avg_over_time(memory_usage_bytes[5m]) # counter 的窗口增量(跨重启安全) increase(http_requests_total[5m])
Prometheus 的 [5m] 是滑动窗口:每个求值点向前回看 5 分钟。因此查询结果曲线上的每个点,其窗口与相邻点是重叠的——适合实时曲线,但不适合「按小时计费」这类需要互不重叠桶的报表。
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 的滑动窗口。
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 天前的数据少了」。

⚠️ 常见坑:
rate()窗口小于 scrape 间隔 2 倍——样本不足,rate 抖动或空值。
💡 关键直觉:窗口是「时间上的卷积核」——先选对 counter/gauge,再谈窗口宽度。
rate[5m] 与 time_bucket('5 min') 的对齐差异会造成什么实际影响?提示:第三题没有唯一答案,重点在于每一层都能回答「谁在什么场景查它、丢精度是否可接受」。
下一节用对照表串 PromQL 与 SQL 常见模式。