第 4 部分导论 · PromQL 的数据模型

PromQL 不是 SQL,它的心智模型是「向量」。这一章讲清即时向量与区间向量的区别——这个区分是理解整门语言的钥匙,也是 90% 报错的来源——以及 lookback 这个隐形但关键的机制。

作者 David更新于 第 25 篇(共 52 篇)

第 4 部分导论 · PromQL 的数据模型

数据存好了,用户怎么提问?用 PromQL。

很多人学 PromQL 的方式是“背几个常用查询”,然后一改就报错。根因是没建立心智模型。这一章补上——PromQL 的一切都建立在“向量”这个概念上。

两种向量:整门语言的钥匙

PromQL 有两种核心数据类型,搞混它们是 90% 报错的来源:

  • 即时向量(instant vector) —— 每条 series 在某一时刻的一个值。
    http_requests_total          →  {job="a"} 1027
                                    {job="b"} 3421      ← 一堆 series,每条一个值
  • 区间向量(range vector) —— 每条 series 在一段时间内的一串值。
    http_requests_total[5m]      →  {job="a"} [1001, 1010, 1020, 1027]
                                    {job="b"} [3400, 3408, 3415, 3421]   ← 每条一串值

区别就是那个 [5m]。 加了它就是区间向量,不加就是即时向量。

关键规则:

  • 区间向量不能直接画图、不能直接比较 —— 你写 http_requests_total[5m] > 100 会报错,因为“一串值”没法和一个数比。
  • 区间向量必须先被“降维”成即时向量 —— 靠 rate()、increase()、avg_over_time() 这类接收区间向量、返回即时向量的函数。
  • 反过来,rate() 只接受区间向量 —— 写 rate(http_requests_total) 也报错,因为 rate 需要“一段时间”才能算速率。

这个“降维”关系就是 PromQL 的核心语法约束:

区间向量 --[rate/increase/xxx_over_time]--> 即时向量 --[聚合/比较/画图]--> 结果

记住它,你就再也不会写错 [5m] 该加在哪。

为什么要分这两种

因为时序数据的两类问题本质不同**:**

  • “现在是多少?” —— 内存用量、队列长度、当前副本数。这是即时向量:每条 series 取一个值就够。
  • “这段时间变化了多少?” —— counter 涨了多少、平均值是多少、最大值是多少。这需要一段时间的所有点,即区间向量。

第 1 部分说过 counter 的绝对值几乎没意义,你要的是变化率——而变化率天然需要两个以上的点。所以 counter 几乎总是 rate(x[5m]) 这个形态:先取一段(区间向量),再算速率(降回即时向量)。

理解了这个,PromQL 里那些看起来奇怪的写法就都顺了。

lookback:那个隐形的 5 分钟

**一个几乎所有人都被坑过、但很少有人真正理解的机制:当你查询一个即时向量时,Prometheus 会往回找最多 5 分钟(默认 lookbackDelta,源码 promql/engine.go:64)。

// promql/engine.go:64
defaultLookbackDelta = 5 * time.Minute

为什么需要它?因为抓取是离散的(每 15 秒一个点),而你查询的时刻几乎不可能正好落在某个采样点上:

采样点:    ●--------●--------●--------●
           10:00:00  10:00:15  10:00:30  10:00:45
你查询:              ↑ 10:00:22 (中间!这里没有点)

lookback 的规则:往回找,找到最近的那个点就用它。 于是查 10:00:22 会返回 10:00:15 那个点的值。这让“任意时刻查询”成为可能,否则你只能查在采样点上。

但它带来两个陷阱:

  • 死掉的 series 会“续命”5 分钟 —— 目标挂了、不再有新数据,但接下来 5 分钟内查询仍会返回最后那个值。图上是一条平线,你以为服务还活着。这就是第 1 部分讲的 staleness marker 要解决的问题——它显式标记“这条 series 结束了”,让 lookback 立刻停止返回旧值。
  • 抓取间隔 > 5 分钟的指标查不到 —— 如果你的抓取间隔是 10 分钟,那么大部分时刻往回找 5 分钟什么都找不到,查询返回空。这是“我的指标时有时无”这类怪问题的常见根因。 规则:抓取间隔必须明显小于 lookback(建议 ≤ 2 分钟)。

一个查询的完整形态

把这些串起来,看一个真实的 RED 查询:

sum by (service) (
  rate( http_requests_total{status=~"5.."}[5m] )
)

逐层拆解:

图表加载中…

注意第 ① 步的 {status=~"5.."}——它直接对应上一部分的倒排索引。 而且这是个正则匹配,比等值匹配慢——如果 status 取值不多(通常就几个),影响有限;但如果你在高基数标签上用正则,这一步就会成为瓶颈。PromQL 的性能,一大半取决于第 ① 步选中了多少 series。

第 ④ 步的 by (service) 也值得注意:它丢弃了除 service 外的所有标签,把原本可能几千条 series 合并成几条。聚合是降基数最有效的手段——这也是下一部分 recording rules 的价值所在。

PromQL 不是 SQL

几个 SQL 直觉会害你的地方:

  • 没有 JOIN,只有向量匹配 —— PromQL 的“关联”是靠标签匹配自动完成的(后面讲)。a / b 会自动把标签相同的 series 配对,而不是像 SQL 那样写 ON 条件。
  • 没有“行”,只有 series —— 你不能“取出第 3 行”。一切都是“一组 series”。
  • 时间是隐式的 —— SQL 里时间是个普通列,你要 WHERE time > ...;PromQL 里时间是查询的固有维度,由查询范围和 [5m] 决定,你几乎从不显式写时间。
  • 不是关系代数 —— 没有子查询嵌套那套(虽然有个 [5m:1m] 的子查询语法,但语义完全不同)。

把 PromQL 当成“对一组时间序列做向量运算”来理解,而不是“查表”——这个心智切换是学会它的关键。

小结与下一章

PromQL 的钥匙是两种向量:即时向量(每条 series 一个值,答“现在是多少”)和区间向量(加 [5m],每条 series 一串值,答“这段时间变化多少”)。核心约束:区间向量不能直接比较 / 画图,必须先被 rate()/xxx_over_time() 这类函数降维成即时向量;而 rate() 又只接受区间向量——记住这条降维关系,就不会写错 [5m] 的位置。分两种是因为两类问题本质不同(counter 的绝对值无意义,变化率天然需要多个点)。lookback(默认 5 分钟,engine.go:64)让“任意时刻查询”成为可能(采样是离散的,你查的时刻几乎不在采样点上),但有两个陷阱:死 series 会续命 5 分钟(靠 staleness marker 解决)、抓取间隔 > 5 分钟的指标查不到(建议间隔 ≤ 2 分钟)。PromQL 不是 SQL:没有 JOIN(靠标签自动匹配)、没有“行”、时间是隐式维度。

心智模型有了。下一章看这门语言怎么被解析成可执行的东西、以及引擎怎么跑它。👉 解析与执行。

参考资料

  • [1] Prometheus 源码 promql/engine.go:64(defaultLookbackDelta)、promql/value.go(Vector/Matrix 类型)。
  • [2] Prometheus 官方文档:Querying basics、Query examples、staleness。
  • [3] 关联指标类型、抓取、倒排索引。

本页目录