Prometheus 的标签系统强大而危险:标签值每多一种组合,时间序列就翻一倍。一旦把 user_id、request_id、pod 这种高基数标签塞进指标,序列数指数级爆炸——存储撑爆、查询变慢、告警失效。本指南讲透高基数的机理、标签设计纪律,以及 PromQL 查询与存储的优化手法,让监控在规模增长时依然快而稳定。
关键概念:高基数(High Cardinality)=标签的取值种类非常多。Prometheus 按"指标名 + 标签组合"区分时间序列,基数 = 各标签取值数的乘积。基数爆炸会线性放大存储、查询与内存开销。
1. 高基数:Prometheus 的头号敌人
1.1 基数如何爆炸
一个指标 + 两个标签:
http_requests_total{method, status}
method ∈ {GET,POST,PUT,DELETE}(4)
status ∈ {2xx,3xx,4xx,5xx}(4)
→ 序列数 = 4 × 4 = 16
如果把一个高基数标签加进来:
http_requests_total{method, status, user_id}
user_id ∈ 10 万个用户(100000)
→ 序列数 = 16 × 100000 = 160 万 ✗✗✗
高基数的典型来源:
user_id / tenant_id(多租户直接进标签)
request_id / trace_id(每次请求唯一)
pod(每次重建新名字,长期累计)
ip / port / instance(动态实例爆炸)
时间戳/毫秒级参数
1.2 高基数的危害
存储:序列数 × 数据点 → 磁盘与内存暴涨
查询:扫描的序列多 → 查询慢、内存占用高
告警:计算变慢 → 告警延迟/漏报
成本:对象存储/TSDB 成本线性上升
可读性:指标字典膨胀,没人看得懂
核心:序列数 = 监控系统最昂贵的资源
ℹ️ 核心:设计指标的第一原则就是"控制基数"。基数失控,所有后续优化都是亡羊补牢。
2. 标签设计纪律
2.1 设计标签时的三道检查
检查一:这个标签的取值会不会无限增长?
会(request_id/pod/ip)→ 不要放标签
不会但很多(user_id)→ 用聚合导出,别进原始指标
检查二:这个标签有聚合价值吗?
能按它聚合出有意义的趋势/分组 → 保留
纯定位用 → 用日志/追踪,别放指标
检查三:会不会和其他标签相乘爆炸?
两个高基数标签组合 → 指数级 → 必须拆
2.2 常见标签取舍
推荐(低基数、有聚合价值):
method、status、job、instance(固定)、service、region
警告(中基数,评估后使用):
tenant_id:租户少(<100)可考虑,多租户用聚合
pod:短期可接受,长期用聚合 + 降采样
禁止(高基数):
request_id、user_id、trace_id、时间戳、动态参数
→ 这类信息应进日志/追踪,指标里不要
3. 降低基数的实用策略
3.1 指标拆分与分层
策略一:把高基数指标拆成"低基数聚合"+"高基数明细"
原始明细(含 user_id)→ 短期存储或推送网关
聚合结果(按秒/分钟分桶)→ 长期指标
策略二:指标分层命名
http_requests_total(低基数,用于告警/趋势)
http_requests_by_user_total(高基数,仅需要时启用)
核心:告警和长存指标用低基数版本,明细只按需保留
3.2 relabel 与标签精简
relabel 能做:
- 丢弃不需要的标签(- relabel)
- 合并/重命名标签
- 对高基数标签做"离散化"(如把 100 个用户映射到 10 个桶)
示例(prometheus.yml):
relabel_configs:
- action: labeldrop
regex: user_id|request_id # 采集时直接去掉高基数标签
注意:
去掉标签 = 永久丢失该维度
→ 确定"不再需要明细"再 drop
→ 或保留明细到单独的低保真目标
3.3 用聚合替代高基数
推荐姿势:
原始含高基数标签 → 只做本地/短期
长期指标 = 对高基数聚合后的低基数序列
聚合方式:
Recording Rule 定时聚合(见下)
或应用侧在导出前聚合(exporter 内聚合)
或查询时按需聚合(不落盘,适合低频分析)
原则:高基数数据"算完就丢",不长期占用存储
4. PromQL 查询性能优化
4.1 避免查询爆炸
PromQL 性能杀手:
1. 无标签过滤的全量聚合
sum(rate(http_requests_total[5m])) 扫描所有序列
→ 先缩小范围:{job="api"} 或按 namespace 过滤
2. 高基数上的聚合
sum by(user_id)(...) → 结果序列数爆炸
3. 大范围 + 高密度
rate(...[2h]) 对高采样频率是重计算
优化原则:
先过滤(缩小序列集)再聚合
聚合维度控制在低基数
高频查询用 Recording Rule 预聚合
4.2 Recording Rule:把常用查询提前算好
# recording rule:把昂贵聚合变成普通序列
groups:
- name: api_recording
rules:
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job)
- record: job:http_errors:rate5m
expr: sum(rate(http_requests_total{code=~"5.."}[5m])) by (job)
Recording Rule 的价值:
- 复杂聚合只算一次,查询时直接读结果
- 高频面板/告警秒回
- 把"高基数 → 低基数"的聚合固化下来
注意:
- recording rule 本身也占存储(别建太多)
- 命名遵循约定(job:metric:op),便于识别
4.3 索引与内存调优
TSDB 侧:
- 合理设置 block 时长与 retention
- 高基数时关注 head 内存(指标越多内存越大)
- 必要时拆分为多套 Prometheus(按团队/职责)
查询侧:
- 使用 Grafana 变量时限制为低基数标签
- 长范围查询拆短或降采样
- 对超大集群评估 Thanos/Mimir(见长期存储专题)
5. 高基数检测与治理工具
5.1 检测:怎么知道基数超标
# PromQL 直接查序列数
count({__name__=~"http_requests_total.*"}) # 一个指标的序列数
count by(__name__)({__name__=~".+"}) # 各指标序列数
topk(20, count by(__name__)({__name__=~".+"})) # 序列数 top20
治理阈值经验:
- 单指标序列数 > 1 万 → 黄灯,评审
- 单指标序列数 > 10 万 → 红灯,立即治理
- 全库序列总数监控 → 设上限与告警
5.2 治理流程
高基数治理 SOP:
1. 用 count 找出 top N 高基数指标
2. 分析标签来源(exporter/应用埋点)
3. 决定策略:drop 标签 / 聚合 / 拆分指标
4. 应用侧改埋点 + 采集侧 relabel
5. 观察序列数下降 + 查询变快
6. 设序列数上限告警,防止复发
6. 常见避坑
| 坑 | 现象 | 对策 |
|---|---|---|
| user_id 进指标 | 序列爆炸 | 用日志/聚合,别进原始指标 |
| 无过滤全量聚合 | 查询超时 | 先过滤再聚合 |
| 动态 pod 名 | 序列只增不减 | 聚合 + 降采样或去实例维度 |
| recording rule 滥用 | 存储也涨 | 只固化高频高成本查询 |
| 发现晚了 | 存储成本失控 | 序列数上限告警前置 |
| 一次性 drop 标签 | 明细永久丢失 | 先确认不再需要再 drop |
7. 最佳实践清单
□ 设计指标前评估基数(标签取值 × 组合)
□ user_id/request_id/trace_id 一律不进指标
□ 多租户用聚合导出,不把租户直接进标签
□ 长期/告警指标用低基数版本
□ 查询先过滤再聚合,聚合维度低基数
□ 高频查询用 Recording Rule 预聚合
□ relabel 丢弃确实不需要的高基数标签
□ 监控各指标序列数,设上限与告警
□ 高基数指标拆分:明细短期、聚合长期
一句话原则
高基数治理 = 标签设计把关 + 聚合替代明细 + 查询先过滤再聚合,
让序列数可控,监控才快得起、存得住。
小结
Prometheus 高基数治理的核心是"控制序列数这个最贵的资源":设计时评估标签基数、禁止 user_id/request_id 进指标;存储时用 relabel 丢弃、用聚合替代明细;查询时先过滤再聚合、用 Recording Rule 预聚合。落地记住五件事:高基数标签一律不进指标、多租户用聚合导出、长期指标用低基数版本、查询先缩小范围再聚合、设序列数上限告警。当序列数被牢牢控制在可控范围,Prometheus 才会在规模增长时依然"快、稳、便宜"——这是所有指标工程的地基。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。