适用场景

本文适用于 Prometheus 通过 remote_write 把指标发送到 Thanos Receive、Grafana Mimir、VictoriaMetrics 或其他远端存储的场景。典型问题是本地抓取仍然正常,Prometheus 查询近期数据也没有明显异常,但远端查询、告警或长期存储中的数据逐渐落后,最终出现缺口。

这类故障的关键不是盲目增大队列,而是先区分下游变慢、网络重试、样本突增和本地资源不足,再决定扩容、限流还是调整队列参数。

现象描述

常见现象包括:

  • 远端存储中的最新时间戳落后当前时间数分钟甚至数小时;
  • Prometheus 本地规则计算正常,但依赖远端数据源的仪表盘出现延迟;
  • prometheus_remote_storage_samples_pending 持续增长;
  • prometheus_remote_storage_samples_retried_totalprometheus_remote_storage_samples_failed_total 快速增加;
  • prometheus_remote_storage_shards 已接近配置上限,吞吐仍追不上写入速度;
  • Prometheus 内存、CPU 或磁盘 I/O 同时升高,重启后短暂恢复,随后再次积压。

先从 Prometheus 自身指标确认延迟,而不是只看下游页面:

time()
- prometheus_remote_storage_queue_highest_sent_timestamp_seconds

结果表示最后成功发送样本相对当前时间的滞后秒数。多条 remote_write 配置时,应按 urlremote_name 等当前版本实际暴露的标签拆分观察。

再看待发送样本是否持续累积:

prometheus_remote_storage_samples_pending

如果发送延迟和 Pending 同时上升,可以确认队列正在积压;如果只有远端查询变慢而发送时间戳正常,应把重点转向远端存储的查询链路,而不是 Prometheus 写队列。

可能原因

  1. 远端存储限流、过载或返回 4295xx,导致 Prometheus 退避重试。
  2. DNS、TLS、代理或负载均衡链路不稳定,请求频繁超时或重置。
  3. 新增高基数标签后,每秒样本数突然增加,超过原有队列吞吐。
  4. 单个请求过大或下游响应过慢,批量发送耗时显著上升。
  5. max_shards 太小,队列无法增加足够并发;也可能设置过大,反而压垮下游。
  6. Prometheus CPU、内存或磁盘 I/O 饱和,WAL 读取和样本编码无法及时完成。
  7. 下游恢复后瞬间释放大量积压,形成恢复流量尖峰,再次触发限流。

排查思路

1. 建立故障时间线

同时绘制抓取速率、Pending、重试、失败和分片数,避免只依据单个瞬时值判断。

sum(rate(prometheus_remote_storage_samples_in_total[5m])) by (remote_name)
sum(rate(prometheus_remote_storage_samples_retried_total[5m])) by (remote_name)
sum(rate(prometheus_remote_storage_samples_failed_total[5m])) by (remote_name)
prometheus_remote_storage_shards

指标标签和部分名称可能随 Prometheus 版本变化,上线告警前应先在 /graph/api/v1/label/__name__/values 中确认本机实际暴露值。判断逻辑如下:

  • 输入速率突增、重试很少:优先检查样本量和队列吞吐;
  • 重试速率上升:优先检查下游状态码、超时和网络;
  • 失败速率上升:检查不可重试错误、认证、样本格式和下游拒绝原因;
  • 分片数顶到 max_shards:现有并发上限可能不足,但必须先确认下游还有余量。

2. 检查 Prometheus 日志

journalctl -u prometheus \
  --since "30 min ago" \
  | grep -E 'remote write|non-recoverable|429|timeout|connection reset'

容器化部署可使用:

kubectl -n monitoring logs prometheus-k8s-0 \
  --since=30m \
  | grep -E 'remote write|non-recoverable|429|timeout|connection reset'

重点区分:

  • 429:下游明确限流,继续提高并发通常会加重问题;
  • 5xx 或超时:下游容量或链路异常,样本通常会重试;
  • 4xx 且标记为不可恢复:常见于认证、租户头、标签或时间戳不合法,重试无法解决;
  • TLS 或 DNS 错误:优先修复连接链路,不要用扩大队列掩盖。

日志可能包含远端地址和租户信息,采集与共享时应脱敏,不要输出认证头或令牌。

3. 确认样本量是否突增

sum(rate(prometheus_tsdb_head_samples_appended_total[5m]))

如果该值在故障前明显抬升,可继续按任务拆分抓取样本量:

topk(10, sum by (job) (rate(scrape_samples_post_metric_relabeling[5m])))

对比 scrape_samples_scrapedscrape_samples_post_metric_relabeling,可以确认 relabel 后实际进入 Prometheus 的样本规模。常见诱因是把 user_idrequest_id、完整 URL 或容器实例随机值写入标签。

4. 检查本机资源与 WAL

rate(process_cpu_seconds_total{job="prometheus"}[5m])
process_resident_memory_bytes{job="prometheus"}
df -h /var/lib/prometheus
df -i /var/lib/prometheus
iostat -xz 1 10

磁盘高延迟、空间不足或内存频繁触顶都会影响 WAL 和远端写队列。不要在积压期间直接删除 WAL;这会把可恢复的待发送样本变成永久数据缺口。

配置调整示例

下面配置展示一个受控起点,具体值应依据实际样本速率、下游写入容量和 Prometheus 内存预算压测确定:

remote_write:
  - url: https://metrics.example.com/api/v1/write
    remote_timeout: 30s
    queue_config:
      capacity: 10000
      min_shards: 2
      max_shards: 20
      max_samples_per_send: 2000
      batch_send_deadline: 5s
      min_backoff: 100ms
      max_backoff: 5s

关键参数含义:

  • capacity:每个分片的缓冲容量。增大会提高短时抗抖动能力,也会增加内存占用;
  • min_shards:最低发送并发,流量较小时不必维持大量连接;
  • max_shards:自动扩展的并发上限,必须与下游限流和连接容量匹配;
  • max_samples_per_send:单批样本数,过小会增加请求开销,过大则会拉长单次失败后的重试时间;
  • batch_send_deadline:低流量下等待凑批的最长时间;
  • min_backoffmax_backoff:可重试错误的退避范围,防止故障期间持续冲击下游。

修改后先校验配置:

promtool check config /etc/prometheus/prometheus.yml

如果使用 Kubernetes Operator,应修改对应的 Prometheus 自定义资源或受管配置,不要直接编辑生成后的 Pod 内文件。

安全恢复步骤

  1. 先确认下游已恢复且有足够余量,避免在故障状态下继续加压。
  2. 保持 Prometheus 运行,让它从 WAL 中逐步追赶;不要用重启代替诊断。
  3. 如果分片长期顶到上限且下游资源充足,小步提高 max_shards,每次调整后观察发送延迟、429、CPU 和内存。
  4. 若样本量突增来自错误标签,先通过 metric_relabel_configs 丢弃无价值高基数标签或指标。
  5. 恢复期间设置并发和速率边界,避免积压回放压垮刚恢复的下游。
  6. 当 Pending 下降且最高已发送时间戳持续追近当前时间,再宣布恢复完成。

例如删除不应进入时序标签的请求标识:

scrape_configs:
  - job_name: application
    static_configs:
      - targets: ["app:9100"]
    metric_relabel_configs:
      - action: labeldrop
        regex: "request_id|trace_id|user_id"

labeldrop 会改变时序身份,实施前必须确认这些标签不是告警、聚合或排障所必需。更理想的做法是在埋点源头不要生成无界标签。

告警与预防

发送延迟告警可以从 5 分钟阈值起步,并要求持续一段时间,避免短时网络抖动造成噪声:

groups:
  - name: prometheus-remote-write
    rules:
      - alert: PrometheusRemoteWriteLagging
        expr: |
          time() - prometheus_remote_storage_queue_highest_sent_timestamp_seconds > 300
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Prometheus remote_write 发送延迟超过 5 分钟"
          description: "远端写入队列持续落后,请检查 Pending、重试率、下游状态与样本增量。"

还应建立以下保护:

  • 对 Pending、发送延迟、重试率、失败率和分片利用率建立组合告警;
  • 对每个 job 的样本数和序列数设置基线,及时发现基数突增;
  • 为远端存储设置明确的写入 SLO、限流容量和故障演练;
  • 配置 Prometheus 磁盘空间与 WAL 保留窗口告警;
  • 变更 exporter、采集标签或 remote_write 参数时先做小范围验证;
  • 把配置校验纳入发布流水线,并保留可快速回滚的上一版本配置。

总结

Prometheus remote_write 延迟上升,本质上是样本进入速度长期高于成功发送速度。排查时应先用最高已发送时间戳确认真实延迟,再结合 Pending、重试、失败、分片数和样本输入速率定位瓶颈。下游限流时盲目提高并发会让故障恶化;样本基数失控时单纯扩容队列也只能延后爆发。通过受控分片、合理批量、退避重试、高基数治理和组合告警,才能让积压可观测、恢复有边界,并避免把短时抖动演变为长期数据缺口。