适用场景

本文适用于 Prometheus 运行一段时间后出现以下现象的场景:

  • Prometheus 数据目录持续膨胀,磁盘空间很快被打满;
  • prometheus_tsdb_head_seriesprometheus_tsdb_head_chunks 持续上涨;
  • 查询变慢,Prometheus 重启时间明显变长;
  • 日志中出现 compaction、WAL replay、磁盘空间不足相关报错;
  • 明明保留时间设置不长,但实际磁盘占用仍然超出预期。

这类问题通常不是“Prometheus 天生吃磁盘”这么简单,更多时候和采集目标数量、指标标签基数、抓取间隔、保留策略、远程写入失败、业务指标设计有关。

现象描述

巡检时发现 Prometheus 所在节点磁盘告警:

df -h

示例输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vdb1       200G  182G   18G  92% /data

查看 Prometheus 数据目录:

du -sh /data/prometheus
du -h --max-depth=1 /data/prometheus | sort -h

可能看到:

1.8G  /data/prometheus/wal
176G  /data/prometheus

同时 Prometheus 日志里可能出现:

WAL segment loaded
write block resulted in empty block
compaction failed
no space left on device

如果此时只扩容磁盘,不分析增长来源,通常几天后还会再次打满。

可能原因

Prometheus TSDB 磁盘增长过快,常见原因包括:

  • 抓取目标突然增加,例如 Kubernetes Pod 数量扩大;
  • 抓取间隔过短,例如大量指标使用 scrape_interval: 5s
  • 高基数标签过多,例如把 user_idrequest_idtrace_id、完整 URL 放进 label;
  • 业务指标没有清理,历史版本指标长期保留;
  • retention.timeretention.size 未配置,默认保留时间不符合磁盘容量;
  • 某个 exporter 暴露了大量动态指标;
  • recording rule 生成了过多派生指标;
  • remote_write 堵塞导致 WAL 积压;
  • Prometheus 重启频繁,WAL replay 时间变长,进一步放大可用性问题。

排查思路

1. 先确认 Prometheus 启动参数

查看 Prometheus 进程参数:

ps -ef | grep prometheus | grep -v grep

重点关注:

--storage.tsdb.path=/data/prometheus
--storage.tsdb.retention.time=15d
--storage.tsdb.retention.size=150GB
--web.enable-lifecycle

关键字段说明:

  • storage.tsdb.path:TSDB 数据目录;
  • retention.time:按时间保留数据;
  • retention.size:按磁盘体积限制保留数据;
  • 同时配置时,Prometheus 会尽量满足更先触发的限制;
  • 如果没有配置 retention.size,磁盘容量不足时 Prometheus 不会自动按你的磁盘大小停止增长。

如果 Prometheus 由 systemd 管理:

systemctl cat prometheus
systemctl status prometheus

如果运行在 Kubernetes 中:

kubectl get pod -n monitoring -l app=prometheus -o wide
kubectl describe pod -n monitoring <prometheus-pod>
kubectl get cm -n monitoring | grep prometheus

2. 查看 TSDB 核心指标

在 Prometheus 查询页面或 API 中查看:

prometheus_tsdb_head_series
prometheus_tsdb_head_chunks
prometheus_tsdb_storage_blocks_bytes
prometheus_tsdb_wal_storage_size_bytes
rate(prometheus_tsdb_head_samples_appended_total[5m])

判断方式:

  • head_series 持续上涨:活跃时间序列变多,多半是目标增加或标签基数失控;
  • wal_storage_size_bytes 持续上涨:WAL 积压,可能是写入压力、远程写入堵塞或 compaction 异常;
  • samples_appended_total 速率升高:单位时间写入样本变多,需要检查抓取间隔和指标数量;
  • blocks 总大小快速增加:长期存储压力已经形成。

3. 找出高基数指标

Prometheus 自带工具 promtool 可以分析 TSDB。

先进入数据目录所在机器:

promtool tsdb analyze /data/prometheus

常见输出会包含:

Highest cardinality metric names:
100203 http_request_duration_seconds_bucket
85120  app_request_total
40211  container_cpu_usage_seconds_total

Highest cardinality labels:
120000 path
85000  pod
60000  instance

如果某个业务指标因为 pathurluser_id 产生大量组合,就会让时间序列爆炸。Prometheus 的成本主要取决于“指标名 + label 组合”的数量,而不是单纯取决于指标名数量。

没有 promtool 时,可以先用 PromQL 粗略判断:

topk(20, count by (__name__)({__name__=~".+"}))

查看某个指标按标签组合后的数量:

count by (job) (http_request_duration_seconds_bucket)
count by (path) (http_request_duration_seconds_bucket)
count by (pod) (container_cpu_usage_seconds_total)

4. 检查 scrape 配置

查看 Prometheus 配置:

grep -n "scrape_interval\\|job_name\\|metrics_path\\|kubernetes_sd_configs" /etc/prometheus/prometheus.yml

重点检查:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "kubernetes-pods"
    scrape_interval: 5s

如果大量 job 使用 5 秒抓取,而指标数量又很多,磁盘增长会非常快。一般业务指标可以从 15 秒、30 秒甚至 60 秒开始,根据告警粒度和容量预算再调整。

5. 检查 remote_write 是否堵塞

如果 Prometheus 配置了远程写入,需要关注:

prometheus_remote_storage_samples_pending
rate(prometheus_remote_storage_failed_samples_total[5m])
rate(prometheus_remote_storage_retried_samples_total[5m])
prometheus_remote_storage_shards

判断方式:

  • samples_pending 长时间不下降:远端写入能力不足或网络异常;
  • failed/retried 持续增长:远端存储、鉴权、限流或网络有问题;
  • WAL 目录增长明显:远程写入堵塞可能拖累本地磁盘。

定位示例

某集群 Prometheus 数据目录 3 天内从 60G 增长到 180G。启动参数如下:

--storage.tsdb.path=/data/prometheus
--storage.tsdb.retention.time=30d

没有配置 retention.size。查看写入速率:

rate(prometheus_tsdb_head_samples_appended_total[5m])

发现从 20 万 samples/s 上升到 75 万 samples/s。继续分析:

promtool tsdb analyze /data/prometheus

发现最高基数指标是:

app_http_request_duration_seconds_bucket

标签中 path 数量异常,包含大量真实请求路径:

/api/order/100001/detail
/api/order/100002/detail
/api/order/100003/detail

根因是新版本把订单 ID 放进了 path 标签,没有做路由模板归一化,导致每个订单都生成新的时间序列。

修复方案

1. 立即止血:限制保留体积

先根据磁盘容量设置 retention.size,避免 Prometheus 无限制吃满磁盘。

示例 systemd 启动参数:

--storage.tsdb.retention.time=15d
--storage.tsdb.retention.size=120GB

如果数据盘 200G,不建议把 retention.size 设置到 190G。需要给 WAL、compaction、系统日志和临时文件留空间,一般可以先控制在 60% 到 75%。

修改后重启:

systemctl daemon-reload
systemctl restart prometheus
systemctl status prometheus

Kubernetes 环境则修改对应的 Deployment、StatefulSet、Prometheus CR 或 Helm values,然后滚动更新。

2. 降低抓取频率

对非核心业务指标,将 5 秒抓取调整为 15 秒或 30 秒:

scrape_configs:
  - job_name: "app-api"
    scrape_interval: 30s
    static_configs:
      - targets:
          - "api:9100"

粗略估算:

日样本量 = 活跃时间序列数量 * 86400 / scrape_interval

如果 100 万条活跃序列从 15 秒改到 30 秒,日写入样本量会直接减半。

3. 修复高基数标签

错误示例:

http_request_duration_seconds{path="/api/order/100001/detail",method="GET"}
http_request_duration_seconds{path="/api/order/100002/detail",method="GET"}

推荐改成路由模板:

http_request_duration_seconds{route="/api/order/{id}/detail",method="GET"}

不要把这些字段放入 label:

  • 用户 ID;
  • 订单 ID;
  • 请求 ID;
  • trace ID;
  • 完整 URL;
  • 错误堆栈;
  • 原始 SQL;
  • 时间戳。

如果确实需要分析这些维度,应放到日志或 tracing 系统里,而不是 Prometheus label。

4. 使用 relabel 丢弃无用指标

对不需要的指标,可以在抓取侧丢弃:

scrape_configs:
  - job_name: "app-api"
    metric_relabel_configs:
      - source_labels: [__name__]
        regex: "go_memstats_.*"
        action: drop

也可以丢弃高风险标签:

metric_relabel_configs:
  - regex: "request_id|trace_id|user_id"
    action: labeldrop

注意:metric_relabel_configs 发生在采集后、写入 TSDB 前,可以减少本地存储压力;但目标端已经生成和传输了这些指标,应用侧仍应从源头治理。

5. 清理过大的历史数据

如果磁盘已经接近打满,优先保住 Prometheus 可启动和可压缩空间。常规方式是调整保留策略后重启,让 Prometheus 自己清理旧 block。

不建议手工删除 WAL 文件。WAL 保存 head block 中尚未落盘的数据,误删可能导致近期数据丢失或启动异常。

如果必须应急删除历史 block,要只删除完整 block 目录,并保留当前 WAL:

ls -lh /data/prometheus

典型 block 目录名称类似:

01J1ABCDEF2G345H6789KLMNPQ

应急删除前至少先停止 Prometheus,并确认目录不是 walchunks_headlock

systemctl stop prometheus
du -sh /data/prometheus/*

生产环境更推荐扩容磁盘、调小 retention,再让 Prometheus 正常启动后自行清理。

预防措施

  • 为 Prometheus 同时配置 retention.timeretention.size
  • 对每个 job 设定合理的 scrape_interval,不要全局使用过短间隔;
  • 建立指标 label 规范,禁止动态 ID 进入 label;
  • 新服务接入监控前先检查指标数量和标签维度;
  • head_series、WAL 大小、磁盘可用率、样本写入速率设置告警;
  • 定期用 promtool tsdb analyze 审查高基数指标;
  • 对 Kubernetes Pod 级指标控制采集范围,避免采集无意义的临时任务;
  • remote_write 场景要监控 pending、failed、retried 指标。

推荐告警规则示例:

groups:
  - name: prometheus-tsdb
    rules:
      - alert: PrometheusTSDBHighHeadSeries
        expr: prometheus_tsdb_head_series > 1000000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Prometheus active series is too high"

      - alert: PrometheusDataDiskAlmostFull
        expr: node_filesystem_avail_bytes{mountpoint="/data"} / node_filesystem_size_bytes{mountpoint="/data"} < 0.15
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Prometheus data disk free space is below 15%"

      - alert: PrometheusRemoteWriteBacklog
        expr: prometheus_remote_storage_samples_pending > 100000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Prometheus remote write backlog is increasing"

总结

Prometheus 磁盘增长过快时,排查顺序应是:先确认保留策略和数据目录,再看 TSDB 活跃序列、写入速率、WAL 大小,最后定位高基数指标和异常抓取配置。短期可以通过 retention.size、降低抓取频率、清理旧 block 止血;长期要治理指标标签设计、接入规范和容量告警。Prometheus 的稳定性不只靠扩容,关键是控制时间序列规模和样本写入速度。