第 4 部分导论 · PromQL 的数据模型
PromQL 不是 SQL,它的心智模型是「向量」。这一章讲清即时向量与区间向量的区别——这个区分是理解整门语言的钥匙,也是 90% 报错的来源——以及 lookback 这个隐形但关键的机制。
第 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(靠标签自动匹配)、没有“行”、时间是隐式维度。
心智模型有了。下一章看这门语言怎么被解析成可执行的东西、以及引擎怎么跑它。👉 解析与执行。