关于 prometheus-cardinality-troubleshooter
用于排查线上 Prometheus 基数问题的运维技能,适合调查性能、内存或成本异常的团队。它通过 PromQL 和 API 分步定位问题,并提醒避免在抓取时移除会合并不同序列的标签。
主要功能
- 01按症状定位可能原因
- 02活跃序列与 TSDB 分析
- 03按指标深入排查标签
- 04调查部署变更与序列 churn
- 05安全的缓解建议
- 06PromQL 与 API 示例
适用场景
- 01调查 Prometheus 内存增长
- 02查找导致查询变慢的指标
- 03诊断序列限制或远程写入问题
- 04追踪部署后的基数增长
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ grafana/ skills --skill prometheus-cardinality-troubleshooter安装后在 Claude Code 中输入/prometheus-cardinality-troubleshooter确认已加载。
详细介绍
Prometheus 基数问题排查
你是诊断 Prometheus 实时基数问题的专家。当用户反馈 Prometheus 性能、内存或成本问题,且疑似与基数有关时,请使用本指南系统地进行排查。
此技能用于诊断和运维。如需设计和预防指标模式问题,请转至 prometheus-label-strategy。
补救之前:一条原则
压力之下,人们很容易选择在抓取时对高基数标签使用 labeldrop。不要这样做。 在抓取时,不能移除任何会使时间序列保持唯一的标签——不是 pod,不是 instance,也不是任何能区分不同真实时间序列的标签。这样看似能止住问题,实际上却会破坏数据:
- 不同时间序列的计数器重置会被合并 →
rate()和increase()会返回垃圾结果,往往是高得离谱的值。 - 每次抓取有多个样本落到同一时间序列上 → 出现重复样本/乱序错误,DPM 反而会膨胀,而不是降低。
- 这种破坏是无声的(不会出现配置错误),数据中也没有任何线索能说明问题出在哪里。几周后有人问“为什么我的 DPM 这么高/为什么
rate()离谱?”,却无从查起。
唯一安全的补救方式是:
- 丢弃整个不需要的指标(对
__name__使用action: drop)——丢弃的是整个指标,而不是合并不同的时间序列。 - 修复来源——让应用停止生成有问题的标签(这是修复无界
path、user_id等标签的真正方法)。 - Adaptive Metrics——适用于无法从源头修复的时间序列结构性基数问题。它能正确聚合(感知计数器重置、经过审计且可逆)。这是降低
pod等标签成本的正确方法。请转至adaptive-metrics。
下文凡是提到“丢弃标签”,都应按这条原则理解:丢弃整个指标、修复来源,或使用 Adaptive Metrics——绝不要使用 labeldrop 移除用于区分时间序列的标签。
症状 → 可能原因
| 症状 | 可能原因 | 首要操作 |
|---|---|---|
| Prometheus 被 OOMKilled,或内存持续线性增长 | 活跃时间序列增长(通常由新增的有问题指标或标签导致) | 活跃时间序列排查 |
| 单个 PromQL 查询缓慢或导致查询器 OOM | 查询中的一个或多个指标具有高基数 | 逐指标深入排查 |
| 远程写入滞后,WAL 持续增长 | 样本吞吐量激增——时间序列数量或抓取间隔发生变化 | 活跃时间序列排查 + 检查抓取间隔 |
429 Too Many Samples/out of bounds 错误 | 触及 Mimir/Cortex 每租户的摄取器时间序列上限 | 逐指标深入排查,找出新增的问题来源 |
| Grafana Cloud 活跃时间序列账单激增 | 新增指标、新增标签或导致数据 churn 的部署 | 逐指标深入排查 + 检查 churn |
| Grafana Cloud DPM 账单激增,但活跃时间序列数平稳 | 缩短了抓取间隔,或 remote_write 发送了重复数据 | DPM 相关问题——转至 dpm-finder |
部署后出现 series_limit_per_user 错误 | 应用变更引入了新的问题标签 | 近期变更差异 |
| 时间序列数量增长后,每次重启都会重置 | 临时标签值导致时间序列 churn | Churn 诊断 |
步骤 1:活跃时间序列排查
获取关键数字
# Total active series in the local Prometheus
prometheus_tsdb_head_series
# Or for Mimir / Grafana Cloud Metrics (per tenant)
cortex_ingester_memory_series{user="<tenant>"}
与近期历史数据比较:
# Growth over the last 7 days
deriv(prometheus_tsdb_head_series[7d]) * 86400
对于稳定的应用集合,每日增长率超过几个百分点就是危险信号。
使用 TSDB 状态端点
Prometheus 提供了内置的基数分布:
curl -s http://prometheus:9090/api/v1/status/tsdb | jq
返回:
seriesCountByMetricName— 按序列数排序的指标labelValueCountByLabelName— 按唯一值数量排序的标签memoryInBytesByLabelName— 按内存占用排序的标签seriesCountByLabelValuePair— 按序列数排序的标签-值对
这通常是最快找到“哪个指标/哪个标签出了问题”的方法。
对于 Grafana Cloud:
# Same endpoint, authenticated against the per-tenant Mimir
curl -s -u "<user>:<token>" \
"https://prometheus-prod-XX.grafana.net/api/prom/api/v1/status/tsdb" | jq
第 2 步:读取输出
按序列数排序的指标
"seriesCountByMetricName": [
{ "name": "http_request_duration_seconds_bucket", "value": 184320 },
{ "name": "go_gc_duration_seconds", "value": 80 },
...
]
判断依据:
- 排在前面的直方图(
_bucket)几乎总是问题所在——它们有 14× 的乘数(桶数量 + 3)。通常应在源头(检测代码中)减少底层直方图的标签,而不是在抓取时剥除标签,也不要改动桶本身。 - 前 5 名中有不认识的指标 → 在代码库中搜索它;它很可能是新功能标志,或是已发布到生产环境的调试指标
- 同一指标以多个变体出现(
_total、_count、_sum)——这是直方图或摘要指标;应将所有变体的数量相加,才能了解实际影响
按唯一值数量排序的标签
"labelValueCountByLabelName": [
{ "name": "url", "value": 84210 },
{ "name": "trace_id", "value": 41000 },
{ "name": "pod", "value": 1820 }
]
危险信号:
- 任何唯一值超过 10K 的标签几乎肯定是错误的。唯一例外是大型集群中有意设置的每目标标签。
trace_id、request_id、session_id、query、email、path、url——这些绝不应该作为标签。它们应该放在范例、日志或跟踪中。pod的值有数千个——参见变动诊断;近期频繁变动往往会抬高这个数字
第 3 步:深入分析单个指标
确定可疑指标后,找出导致问题的标签。
统计某个指标每个标签的不同值数量
# How many unique values does each label have on this metric?
count by (__name__) (
count by (__name__, label_name_here) (
http_request_duration_seconds_bucket
)
)
对每个标签重复操作,或使用辅助工具:
# Via the Prometheus HTTP API
curl -s "http://prometheus:9090/api/v1/labels?match[]=http_request_duration_seconds_bucket" | jq -r '.data[]' | \
while read label; do
count=$(curl -s "http://prometheus:9090/api/v1/label/${label}/values?match[]=http_request_duration_seconds_bucket" | jq '.data | length')
echo "${count} ${label}"
done | sort -rn | head -20
查找某个标签的前几项标签值
# Top 20 path values for http_requests_total
topk(20,
count by (path) (http_requests_total)
)
如果前几项值中出现 UUID、哈希值、时间戳或数字 ID → 该标签的值来自源头,且没有上限。
按组统计单个指标的序列数
# Series-per-instance breakdown — if uneven, one instance is misbehaving
sum by (job, instance) ({__name__=~"my_metric.*"})
第 4 步:比较近期变更
如果基数问题是近期才出现的,原因几乎总是近期变更。比较当前状态与之前的状态。
当前与昨天的指标列表
通过 Grafana Cloud 基数仪表板,或:
# Current metrics
group by (__name__) ({__name__!=""})
# Compare to last week (offset)
group by (__name__) ({__name__!=""} offset 7d)
在外部进行比较。seriesCountByMetricName 前几项中出现了一个一周前还没有的新指标 → 这就是问题所在。
与部署关联
# Active series correlated with build_info
prometheus_tsdb_head_series
# Overlay with:
changes(app_build_info[1d])
序列数出现垂直跃升,且与部署时间吻合,就能确定原因。
第 5 步:变动诊断
高变动意味着序列创建和废弃的速度快于其过期速度。症状:序列数持续攀升,然后在 Prometheus 重启时骤降。
变动信号
# Series created vs. removed per second
rate(prometheus_tsdb_head_series_created_total[5m])
rate(prometheus_tsdb_head_series_removed_total[5m])
# Ratio of churned to live
prometheus_tsdb_head_series_created_total / prometheus_tsdb_head_series
如果创建速率持续显著高于移除速率,就意味着基数只会一路上升。常见原因:
| 原因 | 特征 |
|---|---|
Pod 滚动更新时生成 pod 标签 | 变动峰值与部署时间吻合;影响通过 Pod 发现的抓取 |
每个指标都带有 version / git_sha / image_tag 标签 | 每次部署都会导致多个指标的变动出现峰值 |
instance 中使用临时主机名 | 与云自动扩缩容事件时间吻合 |
| 错误:动态标签名称 | 变动持续攀升,永不趋于稳定 |
| 应用错误地将新生成的 UUID 作为标签 | 线性且无限制地增长,与部署无关 |
变动对内存的影响
# A churn-driven head block carries old series until tsdb compaction
prometheus_tsdb_head_chunks
go_memstats_heap_inuse_bytes{job="prometheus"}
重启 Prometheus 可以清除变动产生的序列,但这并不能解决问题。应该从源头修复。
常见元凶一览
直方图膨胀
判断方法:seriesCountByMetricName 中的 *_bucket 指标排名靠前。乘数约为 14×。
修复方法:
- 首先,在源头减少直方图的标签——每移除一个标签,就能减少 14× 的序列。在埋点代码中精简
path、method或status_code(不要在抓取时通过labeldrop删除它们——这会合并不同的直方图并破坏桶数据)。对于已写入 Grafana Cloud、无法更改的序列,可使用 Adaptive Metrics 聚合。 - 然后,在适当的情况下减少桶的数量(使用自定义桶,而非默认值)。
- 对于高分辨率延迟跟踪,可考虑使用原生直方图(Prometheus 2.40+)——单个稀疏序列即可替代整个桶系列。
kube-state-metrics 标签膨胀
判断方法:kube_pod_labels 或 kube_pod_annotations 排名靠前,且 label_* 或 annotation_* 标签导致基数膨胀。
修复方法:使用 --metric-labels-allowlist 和 --metric-annotations-allowlist 配置 kube-state-metrics。默认情况下,它会将所有标签和注释都作为序列发出。
# kube-state-metrics flags
--metric-labels-allowlist=pods=[app,team,version]
--metric-annotations-allowlist=pods=[checksum/config]
新端点导致路径/路由膨胀
判断方法:http_requests_total(或框架中的对应指标)一夜之间增长了 10× 以上。topk(20, count by (path) (http_requests_total)) 显示数百个类似 /users/123456 的值。
修复方法:真正的修复方法是在应用代码中对路径进行模板化(/users/:id)——将用户引导至 prometheus-label-strategy。对于已写入 Grafana Cloud 的序列,可使用 Adaptive Metrics 正确聚合掉 path——引导至 adaptive-metrics。
不要通过 relabel replacement 规则来“规范化” path——在抓取时将 /users/123、/users/456……合并为一个 /users/:id 值,会合并不同的序列,导致重复样本错误和 rate() 出错。必须在源头(模板化)或摄取后(Adaptive Metrics)进行合并,绝不能在抓取时合并。
如果你必须立即处理生产环境故障,且暂时无法部署模板化,则抓取时唯一安全的操作是删除整个问题指标(在代码修复上线前会彻底失去该指标——这是有意为之的权衡,而非无声的数据损坏):
# Emergency: drop the whole metric until the source is templated
metric_relabel_configs:
- source_labels: [__name__]
regex: http_requests_total
action: drop
应用在生产环境中发出调试指标
判断方法:排名前 10 的指标中有一个你不认识的指标。搜索源代码——通常是开发者忘记设置开关的 _details 或 _per_request 调试指标。
修复方法:在抓取时彻底删除:
metric_relabel_configs:
- source_labels: [__name__]
regex: my_app_request_details
action: drop
创建工单,要求相关团队从代码中移除该指标。
应用发出的标签与目标标签冲突
判断方法:某个 job 的序列数量远超预期。查看单个序列时,你会发现应用发出的 instance=... 与目标标签 instance=... 冲突,最终变成了奇怪的值(Prometheus 会将冲突的标签重命名为 exported_instance)。
修复方法:正确的修复方法是在应用中解决——停止从代码中发出 instance、node 或 host;这些标签应属于抓取目标。确认 honor_labels 为 false(默认值),以便保留目标标签。
如果你需要抓取时的临时措施,可以仅在某个标签与目标标签完全重复时将其删除——这是唯一安全使用 labeldrop 的情况,因为目标标签仍能保证唯一性。严格限定要删除的重复标签名称,绝不要包含 pod(或任何其他用于保证唯一性的标签):
# Stopgap ONLY for app-emitted duplicates of target labels.
# Drops the `exported_*` collisions — NOT pod, which makes K8s series unique.
metric_relabel_configs:
- regex: exported_(instance|node|host)
action: labeldrop
这样,relabel_configs 中的目标标签就能正常应用。优先修复应用。
联邦导致基数膨胀
判断方法:联邦 Prometheus 或 Mimir 全局视图中的序列数量远超预期。每个源都有自己的 cluster / region 标签,导致数量倍增。
修复方法:这通常是预期行为——联邦设计上会保留源标签。如果序列数量过高,只联邦聚合后的 recording rules,而不是原始指标:
- job_name: federate
honor_labels: true
metrics_path: /federate
params:
'match[]':
- '{__name__=~".*:.*"}' # Recording-rule naming convention only
修复决策树
Cardinality fire confirmed
│
├── Need to stop the bleeding NOW (production OOM, ingest 429s)
│ └── Drop the ENTIRE offending metric via metric_relabel_configs (action: drop on __name__)
│ (also applies to Alloy/Agent — same syntax)
│ Do NOT labeldrop a distinguishing label — it breaks the data, see "The One Rule".
│ Then schedule the proper fix.
│
├── It's a Grafana Cloud Active Series bill issue, not a perf issue
│ ├── Cardinality is structural and you can't fix the app
│ │ └── Route to `adaptive-metrics` skill (post-ingest aggregation rules — the safe way)
│ └── You want metric-by-metric DPM breakdown
│ └── Route to `dpm-finder` skill
│
├── It's a fixable application bug (unbounded label, debug metric in prod)
│ ├── Short-term: drop the whole metric at scrape, OR aggregate via Adaptive Metrics
│ └── Long-term: fix in code; route to `prometheus-label-strategy` for design guidance
│
├── It's histogram cardinality
│ ├── Reduce labels on the underlying histogram AT THE SOURCE (14× win per label)
│ ├── Reduce bucket count if appropriate
│ └── Consider native histograms for high-resolution latency
│
└── It's churn (deploy-driven)
├── Stop EMITTING `version`/`git_sha`/`instance` from app code (use info-metric for version)
├── Keep `pod` — never drop it; if pod-level series are too costly, use Adaptive Metrics
└── Verify K8s SD relabel rules aren't mapping in `uid` or other ephemeral fields
紧急丢弃模式(可直接复制粘贴)
以下是安全的抓取时紧急操作:丢弃不需要的整个指标。它们不会合并不同的序列,因此不会破坏数据。
⚠️ 这里有意不使用
labeldrop丢弃用于区分序列的标签,也不使用归一化值的重标记。这两种做法都会合并不同的序列,并破坏rate()/DPM(参见唯一规则)。若要在不丢弃整个指标的情况下减少基数,请修复数据源,或使用Adaptive Metrics(路由到adaptive-metrics)。唯一安全的labeldrop用法是移除与目标标签完全重复的标签(例如exported_instance)——参见应用发出的标签与目标标签冲突。
对于 Prometheus scrape_configs:
metric_relabel_configs:
# Drop a specific bad metric entirely
- source_labels: [__name__]
regex: bad_metric_name
action: drop
# Drop a set of debug/temporary metrics by name prefix
- source_labels: [__name__]
regex: debug_.*
action: drop
对于 Grafana Alloy(prometheus.relabel 组件):
prometheus.relabel "drop_bad_metric" {
forward_to = [prometheus.remote_write.default.receiver]
rule {
source_labels = ["__name__"]
regex = "bad_metric_name"
action = "drop"
}
}
务必先在暂存环境中测试,并优先修复数据源或使用 Adaptive Metrics,而不是在抓取时丢弃指标。
何时转交
- “现在设计一套标签策略,避免再次发生这种情况” →
prometheus-label-strategy - “我们需要保留这些指标,但降低成本” →
adaptive-metrics - “从 DPM 角度看,哪个指标成本最高?” →
dpm-finder - “编写 PromQL 来查找这个问题” →
promql - “在 Alloy 中配置这个” →
alloy - “为什么我的 Loki 这么慢?” →
loki-label-analyzer(不同的系统,同一类问题)
这项技能专注于高压下的诊断。预防、设计和摄取后成本优化由其他工具负责。