# recording rules · 预聚合与命名规范

> 把昂贵的查询预先算好存成新 series。这一章讲 recording rules 的价值（查询提速几个数量级 + 降基数）、那个被低估的官方命名规范 level：metric：operations，以及什么该做成规则、什么不该。

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

# recording rules · 预聚合与命名规范

规则引擎的另一个用途：**recording rule**——**把一个查询周期性地算好，结果存成一条新的 series**。

它是[查询性能的利器](https://daiw.org/manual/observability/promql-performance)，但用好它需要一点纪律。

## 它干什么

```yaml
groups:
- name: api-aggregations
  interval: 30s
  rules:
  - record: service:http_requests:rate5m
    expr: sum by (service) (rate(http_requests_total[5m]))
```

<Callout type="info">
  **每 30 秒，Prometheus 跑一次这个表达式，把结果**当成新指标写回 [TSDB](https://daiw.org/manual/observability/tsdb-overview)**。之后你查 `service:http_requests:rate5m` 就是查一条普通 series——几乎零成本。**

  **收益有多大？回到[成本模型](https://daiw.org/manual/observability/promql-performance)：**

  ```
  原查询:  sum by (service) (rate(http_requests_total[5m]))
    ① 选中 series:  50,000 条
    ② 每次读点数:   20
    ③ 求值次数:     每个用户、每次刷新面板 × 每个 step

  recording rule 之后:
    ① 选中 series:  10 条(每个 service 一条)  ← 5000 倍!
    ② 每次读点数:   1
    ③ 求值次数:     不变,但每次都便宜到忽略不计

  而规则本身:每 30 秒算一次(不管有多少人在看)
  ```

  **关键洞见：成本从“**每个观众 × 每次刷新**”变成“**每 30 秒一次**”。** 一个被 20 个面板引用的查询，原本要算 20×N 次，现在只算 1 次。**观众越多，收益越大。**
</Callout>

## 官方命名规范：被低估的三段式

Prometheus 官方为 recording rule 定了一个命名规范，**很多人不知道，但它极有价值**：

```
level:metric:operations
```

<Callout type="info">
  **三段，冒号分隔：**

  | 段 | 含义 | 例子 |
  | --- | --- | --- |
  | **level** | **聚合到什么层级**（即 `by` 里剩下的标签） | `service`、`instance`、`job` |
  | **metric** | 原指标名 | `http_requests` |
  | **operations** | 做了什么操作（**倒序**） | `rate5m`、`sum:rate5m` |

  ```
  service:http_requests:rate5m
   └level┘ └── metric ──┘└ops┘
   聚合到 service 层级 · 原指标 http_requests · 做了 rate5m

  instance:node_cpu:rate5m
  job:http_errors:ratio_rate5m
  ```

  **为什么这个规范重要？因为它让名字**自带元信息**：**

  - **一眼知道聚合层级** —— `service:xxx` 告诉你“这条已经按 service 聚合过了，**没有 instance 维度**”。**你不会误以为还能按实例切**。
  - **一眼知道能不能再聚合** —— 看到 `service:http_requests:rate5m`，你知道它已经 `rate` 过了，**不能再 `rate()` 一次**（[那是错的](https://daiw.org/manual/observability/promql-rate)）。
  - **避免和原始指标混淆** —— **冒号是关键**：原始指标（来自[抓取](https://daiw.org/manual/observability/scrape-loop)）**永远不含冒号**，而 recording rule 的产物**必含冒号**。**看到冒号 = 这是算出来的，不是采出来的。**

  **这条“冒号即派生”的约定，是整个规范里最有价值的一点**——它让任何人看到一个指标名，立刻知道它的来路。
</Callout>

## 什么该做成 recording rule

<Callout type="info">
  **判据只有一条：**这个查询是不是被反复执行**？**

  **✅ 该做：**
  - **被多个面板 / 告警引用的查询** —— [一处算好，处处受益](https://daiw.org/manual/observability/promql-performance)。
  - **昂贵的聚合** —— [从几万条 series 聚合到几条](https://daiw.org/manual/observability/promql-aggregation)。
  - **[子查询](https://daiw.org/manual/observability/promql-performance)的内层** —— 把 `max_over_time(rate(x[5m])[1h:1m])` 的内层 `rate` 预算好，**成本降几个数量级**。
  - **长期趋势要用的数据** —— 比如“每天的 P99”，预聚合后可以[长期保存](https://daiw.org/manual/observability/blocks-compaction)而不必留全部原始数据。

  **❌ 不该做：**
  - **只用一次的查询** —— 规则也有成本（每个 interval 都要算），用一次不值。
  - **探索性查询** —— 你还在试的东西，别固化。
  - **会产生高基数的规则** —— **这是个真实的坑**：`record` 的产物也是 series，也吃[基数](https://daiw.org/manual/observability/defining-metrics)！如果你的规则 `sum by (pod)` 而 pod 有 10 万个，**你刚刚创造了 10 万条新 series**。**recording rule 应该降基数，不是造基数。**
</Callout>

## 一个高价值套路：SLO 多窗口预聚合

<Callout type="info">
  **[下一章讲 SLO 燃烧率](https://daiw.org/manual/observability/slo-burn-rate)时会用到多个时间窗口的错误率。它们是 recording rule 的经典用武之地：**

  ```yaml
  groups:
  - name: slo
    interval: 30s
    rules:
    # 分子分母各存一份,多个窗口
    - record: service:http_requests:rate5m
      expr: sum by (service) (rate(http_requests_total[5m]))
    - record: service:http_errors:rate5m
      expr: sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
    - record: service:http_requests:rate1h
      expr: sum by (service) (rate(http_requests_total[1h]))
    - record: service:http_errors:rate1h
      expr: sum by (service) (rate(http_requests_total{status=~"5.."}[1h]))
    # 错误率(依赖上面的,所以必须同组且在后!)
    - record: service:http_errors:ratio_rate5m
      expr: service:http_errors:rate5m / service:http_requests:rate5m
    - record: service:http_errors:ratio_rate1h
      expr: service:http_errors:rate1h / service:http_requests:rate1h
  ```

  **注意最后两条依赖前面的** —— 这就是[上一章说的“同 group 内顺序执行”](https://daiw.org/manual/observability/alerting-rules)的实际用途。**如果把它们放到不同 group，可能读到上一轮的结果或者干脆是空。**

  **也注意为什么分子分母要分开存，而不是直接存比值：** 因为**比值不可再聚合**——存了 `service` 级的比值，你就没法再算出全局比值了（[和 summary 的分位数不可聚合是同一个道理](https://daiw.org/manual/observability/metric-types)）。**分子分母分开存，保留了后续聚合的自由度。** 这是个容易忽略但很重要的设计判断。
</Callout>

## 实战注意事项

<Callout type="warn">
  **① 规则也会失败，要监控它**

  ```text
  # 规则求值失败
  rate(prometheus_rule_evaluation_failures_total[5m]) > 0
  # 规则求值太慢(超过 interval 就麻烦了)
  prometheus_rule_group_last_duration_seconds
    > prometheus_rule_group_interval_seconds
  ```
  **规则组求值时间超过它的 interval，规则就开始堆积、延迟**——这是个必须告警的条件。

  **② interval 别比抓取间隔还密**

  规则算的是[已抓回的数据](https://daiw.org/manual/observability/scrape-loop)，抓取间隔 15s 的话，规则每 5s 算一次纯属浪费（数据根本没变）。**规则 interval ≥ 抓取间隔**。

  **③ 改规则不影响历史数据**

  recording rule 的产物是**从规则生效那一刻开始**才有的。改了表达式，**历史数据不会重算**——你会得到一条在某时刻语义突变的 series。**这和[改指标名的问题](https://daiw.org/manual/observability/defining-metrics)一样：第一次就想清楚。**
</Callout>

## 小结与下一章

**recording rule 把查询周期性算好、结果存成新 series**——成本从“[每个观众 × 每次刷新](https://daiw.org/manual/observability/promql-performance)”变成“[每 30 秒一次](https://daiw.org/manual/observability/recording-rules)”，**观众越多收益越大**。**官方命名规范 `level:metric:operations`** 让名字自带元信息：[聚合层级、做过什么操作、能不能再聚合](https://daiw.org/manual/observability/promql-rate)；**最有价值的是“冒号即派生”**——[原始指标永不含冒号，看到冒号就知道是算出来的](https://daiw.org/manual/observability/scrape-loop)。**该做**：[被多处引用的、昂贵聚合的、子查询内层的、长期趋势要用的](https://daiw.org/manual/observability/promql-performance)；**不该做**：[只用一次的、探索性的、以及会**造**基数的](https://daiw.org/manual/observability/defining-metrics)（**规则产物也吃基数，它该降基数不该造基数**）。经典套路是 **[SLO 多窗口预聚合](https://daiw.org/manual/observability/slo-burn-rate)**——注意[依赖前序规则必须同组且在后](https://daiw.org/manual/observability/alerting-rules)，以及**分子分母要分开存**（[比值不可再聚合，和 summary 分位数同理](https://daiw.org/manual/observability/metric-types)）。实战：[监控规则失败与求值超时、interval ≥ 抓取间隔、改规则不重算历史](https://daiw.org/manual/observability/defining-metrics)。

规则会写了。但最难的问题还没答：**阈值到底该定多少？** 下一章。👉 [阈值怎么定](https://daiw.org/manual/observability/alert-thresholds)。

## 参考资料

- [1] Prometheus 源码 `rules/recording.go`、`rules/group.go`（组内顺序执行）。
- [2] Prometheus 官方文档：Recording rules（含 `level:metric:operations` 命名规范）。
- [3] 关联[查询性能](https://daiw.org/manual/observability/promql-performance)、[聚合](https://daiw.org/manual/observability/promql-aggregation)、[基数](https://daiw.org/manual/observability/defining-metrics)。
