2024 年我们的 Prometheus 监控集群把所有业务和基础设施的指标全集中存储,3 个月时间数据量从 200GB 涨到 8TB,单机本地存储顶不住,查询经常 OOM,日维度看板加载要 30 秒以上。投了一个月迁移到 VictoriaMetrics + Thanos + 分层降采样,P99 查询从 30s 降到 800ms,存储成本下降 70%。本文复盘 Prometheus 长期存储 + 高基数指标治理,覆盖架构选型、降采样、记录规则、查询优化、告警去噪。
问题背景
规模:
- 200 个 K8s 节点 × 30 个 exporter = 6000 个采集目标
- 业务指标:5000 个微服务 × 30 个 metric = 15w series
- 总活跃 series:1200w
- 采集间隔 15s
- 数据保留 90 天
性能问题:
- 本地存储 8TB,inode 紧张
- 查询 30 天看板:超时(30s 限制)
- Grafana 经常 502
- Prometheus 进程占用 80GB 内存
- 高基数 metric 拖累所有查询
# Top 高基数(用 promtool 看)
$ promtool tsdb analyze /data/prometheus
Highest cardinality labels:
url=12000 # 含路径 + 参数(高基数源头)
user_id=80000 # 直接打到 label 里(不允许!)
trace_id=... # 应该走 trace 系统,不该在 metric
request_id=... # 同上
# 内存占用
prometheus_local_storage_memory_chunks 150,000,000 # 1.5 亿 chunk
架构方案选型
三个主流方案对比:
1. Thanos
- 优:Prometheus 原生,生态完善
- 优:对象存储成本低
- 缺:架构复杂(Sidecar + Store + Compactor + Query + Ruler)
- 缺:查询合并慢(多 Prom 实例聚合)
2. Cortex / Mimir(Grafana 系)
- 优:水平扩展强
- 缺:多组件复杂
- 缺:运维成本高
3. VictoriaMetrics
- 优:单二进制,部署简单
- 优:压缩率 7x(比 Prometheus 节省 50% 存储)
- 优:查询快 3-10x(MetricsQL 优化)
- 优:支持 PromQL,几乎零迁移成本
- 缺:不是 Apache 协议(影响某些公司选型)
我们选 VictoriaMetrics 集群 + 对象存储归档
VictoriaMetrics 集群部署
# 组件:
# - vminsert: 写入分发(无状态)
# - vmstorage: 存储(有状态,水平扩展)
# - vmselect: 查询(无状态)
# - vmagent: 替代 Prometheus 抓取(可选)
# K8s 部署(使用 helm)
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: vmcluster
spec:
chart:
spec:
chart: victoria-metrics-cluster
version: "0.10.x"
values:
vmstorage:
replicaCount: 6 # 6 副本水平扩展
retentionPeriod: 90d
persistentVolume:
size: 2Ti
storageClassName: nvme-ssd
resources:
requests:
memory: 32Gi
cpu: 8
limits:
memory: 64Gi
cpu: 16
extraArgs:
dedup.minScrapeInterval: 30s # 去重(高可用 Prom 双采)
memory.allowedPercent: 70
vminsert:
replicaCount: 4
extraArgs:
replicationFactor: 2 # 写两份(高可用)
maxLabelsPerTimeseries: 30 # 限制 label 数
maxLabelValueLen: 1024
vmselect:
replicaCount: 4
extraArgs:
search.maxQueryDuration: 60s
search.maxConcurrentRequests: 16
search.maxQueryLen: 16384
# Prometheus 远程写入到 VictoriaMetrics
# prometheus.yml
remote_write:
- url: http://vminsert.monitoring:8480/insert/0/prometheus/api/v1/write
queue_config:
capacity: 100000
max_samples_per_send: 10000
batch_send_deadline: 5s
# 本地保留 2 天(应对网络故障),长期存 VM
storage:
tsdb:
retention: 2d
降采样(Downsampling)
# VictoriaMetrics 自动降采样(企业版)
# 开源版:用 recording rule 自己做
# 1. 原始数据保留 30 天(15s 间隔)
# 2. 5min 粒度保留 1 年
# 3. 1h 粒度保留 5 年
# recording rule(prometheus 配置)
groups:
- name: downsample_5m
interval: 5m
rules:
- record: instance:node_cpu_utilization:rate5m_avg
expr: avg_over_time(instance:node_cpu_utilization:rate5m[5m])
- record: instance:node_memory_used_bytes:avg5m
expr: avg_over_time(node_memory_MemTotal_bytes{}[5m] - node_memory_MemAvailable_bytes{}[5m])
- record: api:http_request_duration_seconds:rate5m
expr: rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])
- name: downsample_1h
interval: 1h
rules:
- record: instance:node_cpu_utilization:rate5m_avg_1h
expr: avg_over_time(instance:node_cpu_utilization:rate5m_avg[1h])
# Grafana 看板按时间范围选数据源
# 7 天内:原始数据
# 30 天内:5m 降采样
# 1 年内:1h 降采样
# 效果:
# - 1 年看板查询从 30s 降到 500ms
# - 长期存储减少 95%
高基数指标治理
# 高基数 = label 取值组合爆炸
# 1 个 metric 100 个 label 值 = 100 series
# 1 个 metric 含 user_id + url + ... = 千万级 series
# 不好的指标设计
http_requests_total{
method="GET",
url="/api/users/12345/orders/67890", ← URL 含 ID
user_id="12345", ← 用户 ID 不该是 label
trace_id="abc...", ← trace 不该是 label
}
# 好的指标设计
http_requests_total{
method="GET",
endpoint="/api/users/:userId/orders/:orderId", ← route pattern
status="200",
service="user-service",
}
# 业务标识查询用 trace 系统(Jaeger / Tempo)
# 找出高基数 metric
$ promtool tsdb analyze /data
Top 10 highest cardinality metric names:
nginx_http_requests_total: 234,567
http_server_request_duration_seconds_bucket: 1,234,567 ← 第一名
process_cpu_seconds_total: 12,345
# 限制 label 取值
# Prometheus relabel_configs
scrape_configs:
- job_name: 'app'
metric_relabel_configs:
# 删除高基数 label
- source_labels: [__name__, user_id]
regex: '.*;.+'
action: drop_label
# URL 规范化
- source_labels: [url]
regex: '/api/users/[0-9]+/.*'
target_label: endpoint
replacement: '/api/users/:id/...'
# 直接丢弃高基数 metric
- source_labels: [__name__]
regex: 'http_server_request_duration_seconds_bucket'
action: drop
# VictoriaMetrics 直接限制
-maxLabelsPerTimeseries=30
-maxUniqueTimeseries=10000000
# 超过限制写入失败,强制业务方规范
记录规则(Recording Rules)
# 复杂查询提前算好,看板秒开
groups:
- name: api_sli
interval: 30s
rules:
# SLI:成功率
- record: api:request_success_ratio:5m
expr: |
sum by (service, endpoint) (rate(http_requests_total{status!~"5.."}[5m]))
/
sum by (service, endpoint) (rate(http_requests_total[5m]))
# SLI:P99 延迟
- record: api:request_duration_p99:5m
expr: |
histogram_quantile(0.99,
sum by (service, endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
)
# SLI:P50 延迟
- record: api:request_duration_p50:5m
expr: |
histogram_quantile(0.50,
sum by (service, endpoint, le) (rate(http_request_duration_seconds_bucket[5m]))
)
# 实例资源使用率
- record: instance:cpu_usage:rate5m
expr: |
1 - avg by(instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
)
# Grafana 直接用 record:api:request_duration_p99:5m
# 不要现场算 histogram_quantile(..) — 看板会卡到怀疑人生
告警去噪
# 问题:每天 200 条告警,80% 是噪音
# 治理:多级阈值 + 时间窗口 + inhibit + 路由
groups:
- name: api_alerts
rules:
# 单条告警:用 for 避免抖动
- alert: HighErrorRate
expr: api:request_success_ratio:5m < 0.99
for: 5m # 持续 5 分钟才告警
labels:
severity: warning
team: backend
annotations:
summary: "{{ $labels.service }} 成功率 {{ $value | humanizePercentage }}"
runbook: "https://wiki/runbook/{{ $labels.service }}-error-rate"
# 严重级:阈值更低
- alert: VeryHighErrorRate
expr: api:request_success_ratio:5m < 0.95
for: 2m
labels:
severity: critical
team: backend
page: "true" # 触发 PagerDuty
# 资源类:用 deriv 看趋势,避免抖动
- alert: MemoryLeakSuspected
expr: |
deriv(process_resident_memory_bytes[1h]) > 10*1024*1024 / 3600
and process_resident_memory_bytes > 500*1024*1024
for: 30m
annotations:
summary: "内存持续上涨 > 10MB/h,可能泄漏"
# Alertmanager 配置:抑制 + 静默 + 分组
route:
group_by: ['cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'team-default'
routes:
- matchers:
- severity = critical
- page = "true"
receiver: 'pagerduty'
continue: false
- matchers:
- team = backend
receiver: 'backend-team-slack'
inhibit_rules:
# 服务全挂时,不报具体接口错误
- source_matchers:
- alertname = ServiceDown
target_matchers:
- alertname = HighErrorRate
equal: ['service']
# 大节点故障时,不报子任务告警
- source_matchers:
- alertname = NodeDown
target_matchers:
- alertname =~ "Pod.*"
equal: ['node']
receivers:
- name: 'pagerduty'
pagerduty_configs:
- service_key: '...'
- name: 'backend-team-slack'
slack_configs:
- api_url: '...'
channel: '#alerts-backend'
查询优化技巧
# 1. 避免现场算 histogram_quantile
# 不好(慢)
histogram_quantile(0.99, sum by(le, service) (rate(http_request_duration_seconds_bucket[5m])))
# 好(快):用 recording rule 提前算
api:request_duration_p99:5m{service="order"}
# 2. 减少 series 选择
# 不好:扫描所有 service
rate(http_requests_total[5m])
# 好:加 service 过滤
rate(http_requests_total{service="order"}[5m])
# 3. 不要用 regex(慢)
# 不好
http_requests_total{service=~"order|product|user"}
# 好(or):
http_requests_total{service="order"} or http_requests_total{service="product"}
# 4. 大时间范围用降采样数据
# 1 年看板用 1h 降采样,不用 15s 原始数据
# 5. 设置 vmselect 资源限制
search.maxQueryDuration: 60s
search.maxQueryLen: 16384
search.maxSamplesPerQuery: 1000000000
search.maxConcurrentRequests: 16
# 6. 利用 cardinality limit
# 单查询返回 series > 1w 直接拒绝
优化效果
指标 优化前 优化后
=========================================================
活跃 series 1200w 400w(治理高基数)
存储占用 8TB 2.4TB(VM 压缩 + 降采样)
P50 查询延迟 2s 80ms
P99 查询延迟 30s(超时) 800ms
日维度看板加载 30s+ 1-2s
1 年维度看板 不可用 3-5s(降采样)
Prometheus 内存 80GB Prom 4GB(只本地 2 天)
VM 32GB × 6 = 192GB
告警条数(天) 200 30(去噪后)
误报率 30% < 5%
成本:
- 本地 SSD 减少 5TB
- 对象存储归档替代,成本降 70%
- 整体存储成本 8000/月 → 2400/月
业务影响:
- SRE 看板秒开,故障定位快 10x
- 老旧数据可查(1 年内任意时间)
- 告警准确率高,oncall 不被打扰
- 长期趋势可分析(SLO 达成率、容量规划)
避坑清单
- label 值必须有限:URL 用路由 pattern,不要含 ID
- user_id / trace_id / request_id 永远不要放 metric label
- 恒用 recording rule 预计算 P99/P95/成功率
- VictoriaMetrics 部署简单,7x 压缩,首选长期存储
- maxLabelsPerTimeseries / maxUniqueTimeseries 必设上限
- 降采样:7d 原始 / 30d 5m / 1y 1h / 5y 1h
- 告警 for: 5m 起步,避免抖动
- inhibit_rules 抑制级联告警(NodeDown 时不报 PodDown)
- cardinality 限制单查询返回 series,防止"查死"
- 本地 Prom 只保留 2 天,长期存 VM/Thanos
总结
Prometheus 长期存储 + 高基数治理是监控系统从"能用"到"可用"的关键一跃。这次治理最大的认知改变:VictoriaMetrics 不是 Prometheus 的简单替代,是个全面更优的方案 — 部署更简单(单二进制 vs Thanos 5 组件)、压缩更高(7x vs 3x)、查询更快(3-10x)、配置更少。最被低估的是 recording rule,P99 现场算 30s,record 预算 100ms — 凡是 Grafana 看板用到的复杂表达式,都该预算好。最容易踩的坑是高基数:开发把 user_id 当 label 加进去,一个 metric 千万 series,Prometheus 直接 OOM;label 必须低基数(method / status / endpoint pattern),业务标识走 trace 系统。最后,告警去噪是 SRE 工作的核心:200 条 → 30 条,oncall 不再被打扰,这是真正的可持续运维 — for: 5m + inhibit_rules + 分级告警三件套,缺一不可。
—— 别看了 · 2026