# 聚合与向量匹配 · by/without 与那些一对多

> sum by （job） 里的 by 到底在干什么？两个指标相除时标签怎么配对？这一章讲清聚合的「丢标签」本质、向量匹配的一对一/多对一规则，以及 on/ignoring/group_left 这几个最让人头疼的关键字。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/promql-aggregation
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 聚合与向量匹配 · by/without 与那些一对多

[`rate()` 之后](https://daiw.org/manual/observability/promql-rate)，你几乎总要**聚合**：把几百条 series 合并成几条有意义的。这一章讲聚合，以及它的孪生难题——**两个向量怎么配对**。

## 聚合的本质：丢标签

<Callout type="info">
  **`sum by (job) (x)` 干的事，本质是：**按 `job` 分组，丢掉其他所有标签，组内求和**。**

  ```text
  输入(6 条 series):
    {job="api", instance="1", status="200"}  10
    {job="api", instance="2", status="200"}  20
    {job="api", instance="1", status="500"}   1
    {job="db",  instance="3", status="200"}  30
    {job="db",  instance="4", status="200"}  40
    {job="db",  instance="3", status="500"}   2

  sum by (job) (x)  →  {job="api"}  31     ← instance 和 status 都没了!
                       {job="db"}   72
  ```

  **关键理解：聚合结果的标签集 = `by` 里列的那些，仅此而已。** 所有没被 `by` 提到的标签**全部消失**。

  **`without` 是反过来的**：列出**要丢掉**的标签，其余全保留。
  ```text
  sum without (instance) (x)  →  {job="api", status="200"}  30
                                 {job="api", status="500"}   1
                                 {job="db",  status="200"}  70
                                 {job="db",  status="500"}   2
  ```

  **实战建议：优先用 `without`。** 为什么？因为 `by` 是**白名单**——将来加了新标签（比如 `region`），`by (job)` 会**默默把它丢掉**，你可能根本没意识到结果被跨 region 混在一起了。而 `without (instance)` 是**黑名单**，新标签会**自动保留**，更符合“我只想合并掉实例维度”的意图。

  **这条经验很多人不知道，但它能避免真实的误导。**
</Callout>

## 聚合是降基数最有效的手段

<Callout type="info">
  **[第 2 部分讲基数](https://daiw.org/manual/observability/defining-metrics)时说过 series 数是**乘法**。聚合是那个乘法的**逆运算**——它把维度**消掉**。**

  ```
  原始:  5(method) × 50(route) × 8(status) × 12(le) × 20(instance) = 480,000
  sum by (le):                                                      = 12  ← 消掉了 4 个维度!
  ```

  这就是 [recording rules](https://daiw.org/manual/observability/recording-rules) 的价值：**把常用的聚合结果预先算好存下来**，查询时直接读那 12 条，而不是每次都从 48 万条里现算。[第 5 部分](https://daiw.org/manual/observability/recording-rules)会展开。

  **一个实用心法：聚合时问自己“我丢掉的这个维度，真的不需要吗？”** 丢 `instance` 通常没问题（你要看服务整体）；但如果你正在排查“是不是某个坏实例”，**丢掉 instance 就等于丢掉了答案**。**聚合是有损的，损失的正是[你埋点时费心保留的提问自由度](https://daiw.org/manual/observability/why-observability)。**
</Callout>

## 向量匹配：两个指标怎么配对

现在是难的部分。`a / b` 时，PromQL 怎么知道 `a` 的哪条 series 该除以 `b` 的哪条？

<Callout type="info">
  **默认规则（一对一）：**标签集**完全相同**的两条 series 配对**。**

  ```text
  a:  {job="api", instance="1"}  100
      {job="api", instance="2"}  200
  b:  {job="api", instance="1"}   10
      {job="api", instance="2"}   20

  a / b  →  {job="api", instance="1"}  10    ← 标签全同 → 配对
            {job="api", instance="2"}  10
  ```

  **注意：结果会丢掉指标名**（`__name__`），因为“a 除以 b”是个新东西，不再是 a 也不是 b。

  **问题来了：如果标签不完全相同呢？** 比如 `a` 有个额外的 `status` 标签，`b` 没有——**默认规则下，一条都配不上，结果是空**。

  **这就是“我的查询返回空但每一半都有数据”这个经典困惑的根源。** 解法是 `on` / `ignoring`：

  ```text
  # 只按 job 配对,忽略其他标签
  a / on (job) b

  # 忽略 instance,按剩下的配对
  a / ignoring (instance) b
  ```

  **`on` 是白名单（只看这些标签），`ignoring` 是黑名单（忽略这些标签）**——和 `by`/`without` 的关系一样。
</Callout>

## 一对多：group_left / group_right

<Callout type="warn">
  **最让人头疼的场景：一边多、一边少。**

  经典例子：你想给指标**打上元数据标签**（比如从 `kube_pod_info` 拿 node 名）：

  ```text
  # container_cpu 有很多条(每个 pod 一条)
  # kube_pod_labels 也是每个 pod 一条,但带着额外的标签

  container_cpu_usage_seconds_total * on (pod) group_left(node) kube_pod_labels
  ```

  **`group_left` 的意思是：“**左边是多的那一侧**”**——即左边的多条 series 可以匹配右边的同一条。`group_left(node)` 里的 `node` 是“**从右边额外拿过来的标签**”。

  **记忆口诀：`group_left` = 左边多、从右边捞标签；`group_right` 反之。**

  **两个常见的坑：**

  - **“multiple matches for labels”错误** —— 说明你**方向搞反了**，或者**匹配的标签不够唯一**（右边有多条 series 匹配同一个左边的）。检查：`on()` 里的标签组合在“一”的那侧**是否真的唯一**。
  - **忘了 `group_left` 的括号参数** —— `group_left` 不带参数只是“允许多对一”，**不会**把右边的标签带过来。要带标签必须写 `group_left(node)`。

  **这个 `xxx * on(...) group_left(...) yyy` 的模式是生产环境的高频套路**（给指标补元数据、做 join），值得专门记住。
</Callout>

## 实战：几个高频模式

```text
# ① 错误率(经典 RED)—— 注意分子分母标签要能配对
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)

# ② 给指标补元数据(一对多)
node_cpu_seconds_total * on (instance) group_left(node_type) node_metadata

# ③ 占比:每个 pod 占总量的比例(注意 on() 空括号)
container_memory_usage_bytes
/ on () group_left
sum(container_memory_usage_bytes)

# ④ 找出"存在于 A 但不在 B"的 series
up == 1 unless on (instance) some_other_metric
```

<Callout type="info">
  **模式 ③ 里的 `on ()`（空括号）值得注意：它表示“**不按任何标签配对**”，即**所有左边的 series 都匹配右边那唯一一条**（`sum()` 的结果没有标签）。这是“算占比”的标准写法。**

  模式 ④ 的 `unless` 是**集合运算**：PromQL 有 `and`（交）、`or`（并）、`unless`（差）。它们也遵循向量匹配规则，同样可以配 `on`/`ignoring`。**`unless` 特别适合“找出缺失的东西”**——比如“哪些实例 up 了但没上报某个指标”。
</Callout>

## 小结与下一章

**聚合的本质是丢标签**：[`sum by (job)` = 按 job 分组、丢掉其他所有标签、组内求和](https://daiw.org/manual/observability/promql-aggregation)；`without` 是黑名单版。**实战优先用 `without`**——[`by` 是白名单，将来加了新标签会被默默丢掉（可能让结果跨维度混在一起而你不知道）；`without` 会自动保留新标签](https://daiw.org/manual/observability/promql-aggregation)。**聚合是[基数乘法](https://daiw.org/manual/observability/defining-metrics)的逆运算**，消维度（[48 万 → 12 条](https://daiw.org/manual/observability/defining-metrics)），这正是 [recording rules](https://daiw.org/manual/observability/recording-rules) 的价值；但**聚合有损**——[丢掉的维度可能正是答案](https://daiw.org/manual/observability/why-observability)。**向量匹配**默认是[标签集完全相同才配对](https://daiw.org/manual/observability/promql-aggregation)（结果丢 `__name__`）——**这是“两半都有数据但结果为空”的根源**，解法是 `on`（白名单）/`ignoring`（黑名单）。**一对多用 `group_left`**（[左边多、从右边捞标签；`group_left(node)` 的括号参数才是“带过来的标签”](https://daiw.org/manual/observability/promql-aggregation)）；报 “multiple matches” 说明[方向反了或“一”的那侧不唯一](https://daiw.org/manual/observability/promql-aggregation)。高频模式：[错误率、补元数据、`on()` 空括号算占比、`unless` 找缺失](https://daiw.org/manual/observability/promql-aggregation)。

分位数查询有它自己的一堆坑。下一章看 `histogram_quantile()`。👉 [直方图查询](https://daiw.org/manual/observability/promql-histograms)。

## 参考资料

- [1] Prometheus 官方文档：Operators — Aggregation operators、Vector matching。
- [2] Prometheus 源码 `promql/engine.go`（向量匹配实现）、`promql/parser/ast.go`（`AggregateExpr`/`BinaryExpr`）。
- [3] 关联[基数乘法](https://daiw.org/manual/observability/defining-metrics)、[RED 查询](https://daiw.org/manual/observability/red-method)、[recording rules](https://daiw.org/manual/observability/recording-rules)。
