4、分组聚合:by 与 without 子句的语义与最佳实践
在 PromQL 聚合查询中,by 和 without 子句决定了聚合后保留哪些标签维度。理解它们的语义差异,是编写高效、准确告警规则的基础,也是排查自愈剧本触发延迟的关键环节——错误的维度保留可能导致告警被错误分组或遗漏。
4.1 核心语义对比
| 子句 | 语义 | 保留的标签 | 典型场景 |
|---|---|---|---|
by (<label_list>) |
按指定标签分组聚合 | 仅保留列表中声明的标签 | 按 instance、job 聚合 |
without (<label_list>) |
排除指定标签,保留其余所有标签 | 除列表外的全部标签 | 聚合时保留 job、namespace 等上下文 |
关键区别:
by 是白名单模式,只保留你明确列出的标签;without 是黑名单模式,只移除你列出的标签,其余全部保留。在指标维度较多时,without 能避免遗漏重要上下文。
4.2 执行流程对比
以下 SVG 展示了 by(instance) 与 without(instance) 对同一组原始时间序列的处理差异:
4.3 自愈剧本中的典型陷阱
告警触发延迟案例:某自愈剧本使用
sum by(instance) 聚合 CPU 使用率,但原始指标包含 namespace 标签。聚合后 namespace 被丢弃,导致告警无法区分不同命名空间的实例,触发条件被错误合并,延迟了故障隔离。
以下表格总结了常见误用场景及修正方案:
| 误用写法 | 问题 | 修正写法 |
|---|---|---|
sum by(instance) (rate(...)) |
丢弃了 namespace、job 等上下文 |
sum without(cpu) (rate(...)) |
avg without(job) (...) |
意外保留了过多标签,结果维度爆炸 | avg by(instance, namespace) (...) |
count by() (...) |
空标签列表导致聚合为单条序列,丢失所有维度 | count by(severity) (...) |
4.4 最佳实践清单
- 优先使用
without保留上下文:在自愈剧本中,告警规则通常需要保留namespace、job、pod等标签,以便精准定位故障源。使用without排除无关维度(如cpu、device)更安全。 - 明确列出
by的必要标签:如果确定只需要按instance和job聚合,显式写出by(instance, job),避免隐式依赖。 - 避免空标签列表:
by()或without()会导致聚合为单条时间序列,通常不是预期行为。 - 测试聚合结果:在 Prometheus 的 Graph 或 Console 视图中,先执行不带聚合的查询,确认标签维度,再添加
by/without。
4.5 性能考量
分组聚合的性能与保留的标签基数直接相关:
| 标签基数 | 聚合结果数量 | 性能影响 |
|---|---|---|
| 低(< 100) | 少量时间序列 | 快速,内存开销小 |
| 中(100 - 1000) | 中等数量 | 可接受,注意查询范围 |
| 高(> 1000) | 大量时间序列 | 可能造成 OOM 或查询超时 |
优化建议:在自愈剧本中,如果
by 子句导致高基数标签(如 instance 包含大量动态 IP),考虑先使用 __name__ 或 job 缩小范围,再进行分组聚合。例如:sum by(instance) (rate(node_cpu_seconds_total{job="node-exporter"}[5m]))。