本篇是《Prometheus 从入门到生产实践》第 10 / 24 篇。配置以文中版本与环境前提为准,实验和故障均为教学场景。完整程序、配置及校验范围见配套实验包。

PromQL 两边都有值,除法结果却是空;加上 group_left 后又出现 many-to-many。问题往往不在数字,而在标签。向量运算会先按规则配对时间序列,配不到的项通常不会出现在结果里。
1. 先设计结果应该保留哪些标签
默认的两个瞬时向量做算术运算,需要相同的匹配标签集合。假设左侧按 job、route、code 汇总,右侧只按 job、route 汇总,即使数字看起来都正确,也不是默认的一对一匹配。
开始写表达式前,先在表格模式分别运行左右两边。记录每条序列的标签,明确想得到“每个接口一项”“每个状态码一项”还是“每个实例一项”。
sum by (job, route, code) (
rate(ops_demo_requests_total{job="demo"}[5m])
)
sum by (job, route) (
rate(ops_demo_requests_total{job="demo"}[5m])
)
by 列出保留的分组标签;without 列出要去掉的标签,其余保留。后者会自动保留未来新增的其他标签,可能意外改变结果基数。两种写法没有绝对优劣,关键是结果维度是否被有意设计。
2. on、ignoring 与 group_left 不同层次
on 指定用哪些标签匹配,ignoring 指定匹配时忽略哪些标签。group_left 或 group_right 则声明匹配关系里哪一侧有更多序列,不是自动修复重复数据的开关。
以下查询计算每个 code 对同一接口总流量的占比:
sum by (job, route, code) (
rate(ops_demo_requests_total{job="demo"}[5m])
)
/
on (job, route) group_left
sum by (job, route) (
rate(ops_demo_requests_total{job="demo"}[5m])
)
左侧同一个接口有多个 code,右侧同接口只有一个总值,因此是多对一。结果保留左侧的 code。若每个接口有流量且状态分类穷尽,所有 code 的占比相加应接近 1。
向量匹配和分组修饰符的语义见 PromQL operators。算术运算还可能移除指标名,不应继续依赖结果里原有的 metric name。
3. 给业务结果补充设备元信息
下面假设你另外提供了 ops_demo_target_info{job,instance,environment,team}=1。这不是共享实验默认导出的指标,而是说明元信息 join 的扩展示例。
rate(ops_demo_requests_total{job="demo"}[5m])
* on (job, instance) group_left (environment, team)
ops_demo_target_info{job="demo"}
左边同实例有不同状态码、路由;右边要求每个 job、instance 只有一条记录。乘以 1 不改变速率,只把右侧指定的 environment、team 复制进结果。
先检查右侧唯一性:
count by (job, instance) (ops_demo_target_info{job="demo"}) > 1
如果查询返回结果,不能靠 group_left 掩盖。需要查清重复来自两个抓取配置、资产记录冲突、标签变更暂时重叠,还是本来就选错了匹配键。
4. many-to-many 应该怎样排查
第一步分别打印两侧标签,第二步只保留准备匹配的键做 count,第三步检查“一侧”是否真的唯一。需要聚合时,还要确认聚合有没有业务意义。
例如发现两个资产记录,一个说 team=A,一个说 team=B,直接用 max 取一个并不能解释谁是负责人。技术上消除报错和业务上得到正确结果是两回事。
and、or、unless 是集合运算,主要按匹配关系保留、合并或排除序列,不是在每条样本上做普通的布尔逻辑计算。它们的匹配特性也不等于算术运算可以任意多对多。
5. 补零之前,必须有一份可信基线
有些程序只在出现错误后才创建 code=500 序列。此时“没有错误序列”与“总流量存在且错误为零”可能需要区分。
如果业务确认错误计数缺失可以在已有总流量标签集合上解释为零,可以这样构造分子:
sum by (job, route) (
rate(ops_demo_requests_total{job="demo",code=~"5.."}[5m])
)
or on (job, route)
(
0 * sum by (job, route) (
rate(ops_demo_requests_total{job="demo"}[5m])
)
)
它只为总流量一侧存在的 job、route 建立零值,不会为两边都不存在的服务凭空创造健康状态。相较 or vector(0),这种方式还能保留需要的业务标签。
从源头在固定低基数标签组合上初始化 counter 为零,通常能减少查询中的特判。但不能预初始化无穷的用户、订单或 URL 标签。
6. 除零和比较操作还要单独处理
分母存在但为零时,0/0 会得到 NaN。对于错误率,常见做法是保留“有请求才有比例”的语义,用分母大于零的条件过滤,并把无流量状态另外展示。
普通比较如 up == 0 只保留满足条件的序列;up == bool 0 则为已经参与比较的项输出 0 或 1。bool 不会为从未存在的目标自动生成一条序列。
图表里把空值连线或显示成零,只是展示策略,不会修复采集和匹配。告警表达式也不会因为面板显示了“0”就获得那条数据。
7. 练习与验收
先运行两侧查询,再运行 code 占比,检查同一路由的占比之和。最后故意让两侧维度不同,观察默认匹配的空结果,并用明确的 on、group_left 恢复预期关系。
交付一条 join 查询时,附上匹配键、“一侧”唯一性检查和缺失语义。这些说明比多写一个修饰符更能避免后续误报。
上一篇:延迟与分布:经典/原生直方图、P95 和 Summary · 系列目录 · 下一篇:查询性能与记录规则:从定位开销到合成测试