2. PromQL 基础回顾:瞬时向量、区间向量、匹配模式

在深入误差预算台账的聚合优化之前,必须牢固掌握 PromQL 的三大核心概念:瞬时向量、区间向量以及向量匹配模式。这些是构建任何复杂查询的基石,也是理解后续聚合操作与性能优化的前提。

2.1 瞬时向量与区间向量

PromQL 查询返回的数据类型决定了你能对时间序列执行何种操作。下表清晰区分了两种最常用的向量类型:

特性 瞬时向量 (Instant Vector) 区间向量 (Range Vector)
定义 每个时间序列在单个时间点上的最新样本值。 每个时间序列在一段时间范围内的一组样本值。
语法示例 http_requests_total{status="200"} http_requests_total{status="200"}[5m]
返回结果 一组键值对(标签集 → 值)。 一组键值对(标签集 → 值数组)。
典型用途 当前状态查询、告警规则、简单算术运算。 速率计算(rate/irate)、分位数计算(quantile_over_time)、滑动窗口聚合。
在误差预算中的角色 获取当前 SLI 值或当前错误计数。 计算过去窗口(如 1h、7d)内的错误率或可用性。
关键区分: 瞬时向量是“快照”,区间向量是“片段”。在误差预算计算中,rate() 函数必须作用于区间向量,因为它需要观察一段时间内的增量来计算每秒速率。

以下 SVG 展示了 PromQL 引擎如何从时间序列数据库中提取这两种向量:

瞬时向量 vs 区间向量 数据提取示意 当前时间点 瞬时向量 仅取一个点 区间向量 [5m] 取时间窗口内所有样本 过去 现在 时间

2.2 向量匹配模式

当使用二元运算符(如 +、-、/、and、or)连接两个向量时,PromQL 需要确定如何匹配标签。理解匹配模式是避免查询错误和性能问题的关键。

2.2.1 一对一匹配 (One-to-One)

这是最严格的模式。运算符两侧的向量必须具有完全相同的标签集。PromQL 会逐对匹配具有相同标签的时间序列。如果一侧存在另一侧没有的标签组合,该序列会被丢弃。

  • 语法: vector1 <operator> vector2
  • 典型场景: 对两个具有完全相同标签维度的指标进行运算,例如计算两个计数器之间的差值。
  • 误差预算案例: 计算每个服务实例的当前错误率与历史基线错误率的差值,要求两个指标具有完全相同的 service、instance 标签。

2.2.2 多对一 / 一对多匹配 (Many-to-One / One-to-Many)

当一侧向量拥有更细粒度的标签(更多维度)时,需要使用 group_left 或 group_right 关键字。这允许将“多”侧的多条序列聚合到“一”侧的一条序列上。

  • 语法: vector1 <operator> on(label_list) group_left(label_list) vector2
  • 关键点: 必须使用 on() 子句显式指定用于匹配的标签。未在 on() 中列出的标签,在“多”侧会被保留,在“一”侧则被忽略。
  • 误差预算案例: 将每个实例的详细错误率(带有 instance 标签)与一个全局的 SLO 目标(没有 instance 标签)进行比较。此时,全局 SLO 是“一”侧,实例错误率是“多”侧,使用 group_left。
性能警告: 滥用 group_left 或 group_right 会导致 PromQL 引擎产生巨大的笛卡尔积,严重消耗内存和 CPU。在误差预算台账中,应优先通过数据建模(如统一标签)来避免这种模式,或确保“多”侧的基数(唯一标签组合数)在可控范围内。

下面的 SVG 图直观对比了三种匹配模式的数据流:

PromQL 向量匹配模式对比 一对一 (One-to-One) 左: {a=1, b=x} 右: {a=1, b=x} 左: {a=2, b=y} 右: {a=2, b=y} 标签必须完全一致 多对一 (Many-to-One) 左(多): {a=1, b=x, c=p} 左(多): {a=1, b=x, c=q} 右(一): {a=1, b=x} 使用 group_left 一对多 (One-to-Many) 左(一): {a=1, b=x} 右(多): {a=1, b=x, c=p} 右(多): {a=1, b=x, c=q} 使用 group_right 匹配模式选择原则 1. 优先使用一对一:标签结构一致时,性能最佳,逻辑最清晰。 2. 必须使用 on() 子句:显式声明匹配键,避免意外匹配或高基数问题。 3. 谨慎使用 group_left/right:仅在无法统一标签时使用,并监控查询性能。

2.3 实战代码示例:匹配模式在误差预算中的误用

假设我们有两个指标:

  • sli_errors_total{service="api", instance="host1"} (细粒度)
  • slo_target{service="api"} (粗粒度,值为 0.99)

错误写法(导致空结果或错误):

# 错误:标签不匹配,无法进行一对一运算
sli_errors_total / slo_target

正确写法(使用多对一匹配):

# 正确:使用 on(service) 指定匹配键,group_left 保留 instance 标签
sli_errors_total / on(service) group_left() slo_target
优化要点: 在误差预算台账中,我们通常会将 SLO 目标作为配置项注入到 Prometheus 的标签中(例如通过 relabel_configs),使其与 SLI 指标具有相同的标签结构。这样就能使用最高效的“一对一”匹配,避免 group_left 带来的性能开销。