bigywater.tech
微服务 · 70

基数爆炸:标签维度对时间序列数量的影响

标签加一个维度会让序列数涨多少?序列数和 Prometheus 的内存、抓取、查询耗时是什么关系?标签值不断变化(比如请求 ID)会怎样?sample_limit 和 labeldrop 能不能保护 Prometheus?这篇用 Prometheus 3.15.0 逐项量出来。

76 → 890 MB
Prometheus 抓取稳定后的 RSS:暴露 1,000 条序列与 300,000 条序列
500 → 29180
加上 user_id 标签前后的序列数(请求只有 3 万次)
30,735 vs 1,000
标签值不断变化 60 s 后 head 里的序列数,与同一时刻“活”着的序列数

序列数与资源

实测 · 本机执行输出
  暴露的序列数 N | Prometheus 里 count() 的结果 | Prometheus 刚启动时 RSS(MB) | 抓取稳定后 RSS(MB) | 抓取耗时(ms) | /metrics 体积(MB) | sum() 查询(ms) | topk(5) 查询(ms)
  ---------+--------------------------+-------------------------+---------------+----------+-----------------+--------------+---------------
  1,000    | 1,000                    | 91                      | 76            | 2        | 0.05            | 2            | 2             
  10,000   | 10,000                   | 87                      | 112           | 8        | 0.49            | 8            | 7             
  100,000  | 100,000                  | 88                      | 357           | 50       | 4.97            | 58           | 55            
  300,000  | 300,000                  | 92                      | 890           | 137      | 15.13           | 167          | 189           
  序列数从一千涨到三十万,内存、抓取耗时、/metrics 体积和查询耗时都随之增长;每个序列在内存里要存标签集合、倒排索引、最近的样本,抓取一次要解析所有序列。这里的值是固定的,真实的序列还要存时间戳和不同的值,内存会更多

内存、抓取耗时、/metrics 体积和查询耗时都随序列数增长。

标签相乘

实测 · 本机执行输出
  标签                      | 序列数   | /metrics 文本大小(KB)
  ------------------------+-------+------------------
  method × route × status | 500   | 64               
  再加 user_id              | 29180 | 4580             
  5 × 20 × 5 = 500 条;加上取值很多的标签(用户 ID、请求 ID、完整 URL、IP)之后,序列数由标签值的组合决定,而不是由请求量决定——3 万次请求产生了上表里这么多条序列

序列数由标签值的组合决定,取值很多的标签(用户 ID、请求 ID)会让它和请求量一起涨。

churn

实测 · 本机执行输出
  运行时间 | head 里的序列数 | 累计创建的序列数 | 当前能查到的“活”序列(count(rq)) | Prometheus RSS(MB)
  -----+------------+----------+------------------------+-------------------
  10 s | 5,735      | 5,735    | 1,000                  | 94                
  20 s | 10,735     | 10,735   | 1,000                  | 100               
  30 s | 15,735     | 15,735   | 1,000                  | 109               
  40 s | 20,735     | 20,735   | 1,000                  | 116               
  50 s | 25,735     | 25,735   | 1,000                  | 118               
  60 s | 30,735     | 30,735   | 1,000                  | 125               
  每次抓取都产生 1000 条新序列,旧的序列停止更新、被标记为过期(stale),但仍然留在 head 里直到被压缩进磁盘块并回收;所以 head 里的序列数和内存会持续增长,即使同一时刻“活”的序列数不变

标签值不断变化时旧序列留在 head 里,序列数和内存持续增长。

限制与保护

实测 · 本机执行输出
  配置                           | up | 抓到的样本数 scrape_samples_scraped | 重新打标签之后的样本数 | 实际存进去的序列数 count() | Prometheus 自己的 sample_duplicate_timestamp 计数 | 日志里含 duplicate 的行数
  -----------------------------+----+-------------------------------+-------------+-------------------+----------------------------------------------+-------------------
  (a)sample_limit: 5000        | 0  | 10,000                        | 10,000      | 0(没有存进去)          | 0                                            | 0                 
  (b)labeldrop user            | 1  | 10,000                        | 10,000      | 6                 | 0                                            | 0                 
  (c)hashmod 成 6 个桶后 labeldrop | 1  | 10,000                        | 10,000      | 36                | 0                                            | 0                 
  sample_limit 是整个抓取的熔断:超过限制,这一次抓取整体失败(up = 0),一条都不存;labeldrop 去掉区分序列的标签会让多条序列变成同一个标签集合,只能存下一个样本(这里一万条序列并成了 count() 那一列的条数);想保留一点分布信息,可以先 hashmod 再 labeldrop
ERRORS {}

sample_limit 超限整次抓取失败;labeldrop 会让序列悄悄合并;hashmod 能保留一点分布。

分步演示

基数爆炸
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。Prometheus 3.15.0(默认存储设置,没有设置内存限制),暴露端是我写的 Python HTTP 服务(固定内容的 /metrics);RSS 来自 ps,包含 Go 运行时尚未归还给系统的内存;每种 N 各测 1 次,查询耗时是 5 次取中位数;样本值固定,真实指标的值变化会让压缩率和内存略有不同
    • 没有验证:Prometheus 内存随序列数增长的精确公式;长时间运行时 head 压缩(2 小时块)后内存的回落;远程写入、VictoriaMetrics 等其他后端对高基数的处理;native histogram;relabel 把重复样本合并时到底保留了哪一个样本(Prometheus 的日志和计数里没有看到记录);exemplar
    京ICP备2026067502号-1京公网安备11010502063487号