5月27日 18:01

如何优化 Prometheus 的存储和性能?

Prometheus 存储架构基础

Prometheus 使用自研的 TSDB(Time Series Database)作为本地存储引擎。数据写入流程为:先写入内存中的 Head Block,同时通过 WAL(Write-Ahead Log)保证持久性;Head Block 满两个时间窗口后被持久化为磁盘上的 Block;后台 Compaction 进程定期合并小 Block 并清理过期数据。

每个 Block 由以下部分组成:

  • chunks/:存储实际的时间序列数据点,使用 Facebook Gorilla 压缩算法,16 字节的数据点可压缩至平均 1.37 字节
  • index:倒排索引,支持按标签快速查询时间序列
  • meta.json:Block 元信息
  • tombstones:删除标记,删除操作不会立即清除数据,而是记录标记等待下次 Compaction 时清理

理解这个架构是进行存储优化的前提。

数据保留策略配置

通过启动参数控制本地数据的保留时间和磁盘上限:

yaml
# prometheus.yml 或启动参数 storage: tsdb: retention.time: 15d # 数据保留时长,默认 15d retention.size: 50GB # 磁盘使用上限,达到后自动清理最旧数据

两个参数同时配置时,任一条件触发都会清理数据。生产环境建议同时设置,防止磁盘打满。

关键原则:本地存储只保留近期热数据,长期存储需求交给 remote write 后端处理。

标签基数控制

标签基数(Label Cardinality)是影响 Prometheus 存储和查询性能最关键的因素。每一个唯一的标签组合都会产生一条独立的时间序列,基数爆炸会导致内存飙升、查询变慢、磁盘膨胀。

必须避免的高基数标签:

  • 用户 ID、请求 ID、会话 ID
  • 原始 IP 地址
  • 未截断的 URL 路径
yaml
# 使用 metric_relabel_configs 在采集阶段丢弃或重写高基数标签 scrape_configs: - job_name: 'my-app' metric_relabel_configs: - source_labels: [__name__] regex: 'go_memstats_.*' action: drop # 丢弃不需要的指标 - source_labels: [path] regex: '/api/v1/.*' replacement: '/api/v1/:path' # 合并路径,降低基数 target_label: path

排查高基数指标的方法:

promql
# 查看当前时间序列总数 count({__name__=~".+"}) # 按指标名分组统计序列数,找出最大的 topk(20, count by (__name__)({__name__=~".+"}))

采集间隔优化

采集频率直接影响数据写入量和存储占用。合理分层设置采集间隔:

yaml
global: scrape_interval: 30s # 全局默认值 scrape_timeout: 10s scrape_configs: - job_name: 'critical-service' scrape_interval: 15s # 核心服务用短间隔 scrape_timeout: 10s - job_name: 'batch-job' scrape_interval: 60s # 后台任务用长间隔 scrape_timeout: 15s

注意事项:

  • scrape_timeout 不能大于 scrape_interval
  • 采集间隔从 15s 改为 30s,存储量直接减半
  • 不重要的指标可以单独配置更长的间隔

WAL 压缩与写入优化

WAL(Write-Ahead Log)是数据持久性的保障,但默认未压缩时会占用大量磁盘空间,崩溃恢复也较慢。

bash
# 启用 WAL 压缩(Prometheus 2.20+ 默认开启) --storage.tsdb.wal-compression # 控制写入队列大小(Prometheus 2.29+) --storage.tsdb.head-chunks.write-queue-size=0 # 默认 0 表示同步写入 # 设为非 0 值(如 1000)可异步写入,降低写入延迟但增加内存使用

WAL 压缩使用 Snappy 算法,可将 WAL 体积缩减约 50%,CPU 开销极小。一旦开启,无法回退到 2.11 之前的版本。

如果 WAL 异常增长,通常是 remote write 消费跟不上或 Compaction 失败导致,排查方法:

bash
# 检查 WAL 目录大小 du -sh /data/prometheus/wal/ # 检查 Compaction 是否正常 curl -s http://localhost:9090/metrics | grep prometheus_tsdb_compactions_failed_total

Recording Rules 预计算

对于复杂的聚合查询或频繁使用的仪表盘,Recording Rules 可以将计算结果预存为新指标,显著降低查询时的 CPU 和内存压力。

yaml
groups: - name: http_request_rules interval: 30s rules: - record: job:http_requests:rate5m expr: sum by (job) (rate(http_requests_total[5m])) - record: method:http_requests:rate5m expr: sum by (method) (rate(http_requests_total[5m])) - record: http:request_duration_seconds:p99 expr: histogram_quantile(0.99, sum by (le, job) (rate(http_request_duration_seconds_bucket[5m])))

使用原则:

  • 仪表盘反复执行的聚合查询都应该转为 Recording Rule
  • 规则名称采用 level:metric:operations 的命名约定
  • 规则的 interval 不应小于全局 scrape_interval

Remote Write 调优

Remote Write 是将 Prometheus 数据远程写入长期存储后端(Thanos、VictoriaMetrics、Mimir 等)的机制。配置不当会导致 WAL 堆积和内存溢出。

yaml
remote_write: - url: 'http://thanos-receive:19291/api/v1/receive' queue_config: max_samples_per_send: 500 # 每批发送样本数 batch_send_deadline: 5s # 批次等待最大时间 max_shards: 100 # 最大并发分片数 min_shards: 1 # 最小分片数 capacity: 2500 # 队列容量 write_relabel_configs: - source_labels: [__name__] regex: 'go_.*' action: drop # 远端不需要的指标可在发送前丢弃

调优要点:

  • 分片数(shards)根据吞吐量动态调整,max_shards 设为预估峰值即可
  • 如果远端持续写入失败超过 2 小时,WAL 会被 Compaction 截断,未发送数据将丢失
  • 监控关键指标:prometheus_remote_storage_samples_failed_totalprometheus_remote_storage_samples_pending

查询性能优化

使用标签过滤缩小范围

promql
# 差:全量扫描后过滤 sum(http_requests_total) by (job) # 好:在查询时就限定范围 sum(http_requests_total{job="api-server", env="prod"}) by (method)

控制查询时间窗口

大范围查询(如 30 天)会扫描大量 Block。建议:

  • 仪表盘默认显示 1-6 小时
  • 需要更长范围时使用 Recording Rules 预聚合的数据
  • 利用 API 的 step 参数控制返回点数

避免高开销函数

  • rate()irate() 的区间选择不宜过长,通常 [5m][1m]
  • 避免对高基数指标使用 group_left 做 1:N 的 join
  • histogram_quantile() 尽量配合 Recording Rules 预计算

监控 Prometheus 自身

生产环境必须对 Prometheus 自身进行监控和告警:

yaml
# 关键监控指标 - alert: PrometheusTSDBCompactionFailing expr: increase(prometheus_tsdb_compactions_failed_total[5m]) > 0 - alert: PrometheusWALCorruptions expr: increase(prometheus_tsdb_wal_corruptions_total[5m]) > 0 - alert: PrometheusRemoteWriteFailures expr: increase(prometheus_remote_storage_samples_failed_total[5m]) > 0 - alert: PrometheusHighCardinality expr: prometheus_tsdb_head_series > 1000000

同时关注以下运行指标:

  • prometheus_tsdb_head_samples_appended_total:写入速率
  • prometheus_target_interval_length_seconds:采集间隔偏差
  • process_resident_memory_bytes:实际内存占用
  • prometheus_tsdb_compaction_duration_seconds:压缩耗时

长期存储与架构扩展

单机 Prometheus 的本地存储有上限,大规模场景需要架构层面的扩展:

Thanos 方案:

  • Sidecar 模式对现有部署侵入最小,周期性将 Block 上传到对象存储
  • Receive 模式支持多 Prometheus remote write 汇聚
  • Store Gateway 提供对历史数据的查询能力

VictoriaMetrics 方案:

  • 专有压缩算法,压缩比可达 Prometheus 的 10 倍
  • 单节点部署即可替代 Prometheus + 远端存储的组合
  • 完全兼容 PromQL

Grafana Mimir 方案:

  • 支持 Multi-Tenant,适合平台级部署
  • 与 Grafana 生态深度集成

选择建议:中小规模优先考虑 VictoriaMetrics,多租户平台选 Mimir,需要兼容现有对象存储选 Thanos。

磁盘与硬件建议

  • 使用 SSD 存储 TSDB 数据目录,HDD 在高写入负载下 Compaction 性能极差
  • 磁盘剩余空间保持 30% 以上,Compaction 需要额外临时空间
  • 内存分配参考:每百万活跃时间序列约需 1-2 GB 内存
  • Kubernetes 部署时建议设置合理的 requests 和 limits,避免 OOM Kill

总结

Prometheus 存储优化是一个从基数控制到架构扩展的系统性工程。核心思路是:在采集端过滤无用指标、控制标签基数;在存储端合理配置保留策略、启用 WAL 压缩;在查询端善用 Recording Rules、避免全表扫描;在架构层通过 remote write 实现冷热分离、长期存储。每个环节的优化都建立在对 TSDB 工作原理的理解之上,监控 Prometheus 自身的关键指标则是发现和预防问题的最后一道防线。

标签:Prometheus