Prometheus Recording Rules:AI 服务指标要预聚合再查询,否则 Grafana 面板等不起
Prometheus Recording RulesAI 服务指标要预聚合再查询否则 Grafana 面板等不起一、千卡集群里一条 7 天范围的 P99 查询等了 45 秒还没出图AI 推理服务每秒产生几万条指标——每张 GPU 的 SM 占用率、每路请求的延迟分桶、每个模型版本的 QPS。某天运维想看过去 7 天的 P99 推理延迟趋势Grafana 面板转了 45 秒的圈最后报了一个query processing would load too many samples的错误。Prometheus 的 query_range 在 7 天窗口里要扫描上亿个样本点TSDB 的 IO 和内存直接被打穿。基础设施不需要漂亮话这个问题不是 Prometheus 的锅是使用姿势不对。Prometheus 的设计哲学是存储便宜、查询贵原始指标在查询时做聚合是最吃资源的操作。你让它在 7 天范围内实时计算histogram_quantile(0.99, rate(request_duration_bucket[1m]))等于让它当场做几亿次浮点运算。Recording Rules 的核心价值不是把查询结果存下来而是把查询成本从读路径移到写路径。查询时聚合 每次打开面板都算一遍写入时聚合 算一次、反复读。对 AI 服务这种高频指标场景这不止是性能优化是能不能出图的区别。二、Recording Rules 的预聚合流水线Recording Rules 的本质是将昂贵的 PromQL 表达式在采集间隙预先计算结果以新的指标名写入 TSDB。查询面板不再怼原始数据而是直接读预聚合结果sequenceDiagram participant S as Scrape Targetsbr/(AI推理服务) participant P as Prometheus participant R as Recording Rules Engine participant T as TSDB participant G as Grafana S-P: 每15s暴露 /metrics P-T: 写入原始时间序列br/(request_duration_bucket, ...) Note over R: 每 evaluation_interval 触发一次 R-T: 1. rate() 计算 1m 窗口增量 R-T: 2. histogram_quantile() 算 P50/P90/P99 R-T: 3. 写入预聚合结果br/(ai:request_latency:p99_5m) G-T: 查询 ai:request_latency:p99_5m T--G: 直接返回预聚合结果毫秒级响应 Note over G,T: vs 直接查原始 histogram:br/需要实时扫描上亿样本Recording Rules 的评估周期由evaluation_interval控制默认与scrape_interval相同。规则文件里每一条规则在每次评估周期都会重新计算。关键性能参数不是规则数量而是每条规则背后的 PromQL 复杂度——一个带histogram_quantile的规则可能比 10 个简单的rate()规则更重。预聚合的指标命名需要遵循分层约定。Prometheus 官方推荐的格式是level:metric:operations比如ai:request_duration_seconds:rate5m— 5 分钟窗口的请求速率ai:request_duration_seconds:p99_5m— 5 分钟窗口的 P99 延迟ai:gpu_memory_utilization:avg5m— 5 分钟平均 GPU 显存利用率这种命名不是形式主义——当集群里有几百条 Recording Rules不按约定命名排查规则依赖关系就是灾难。三、生产级 Recording Rules 配置与 Go 侧验证代码以下是一套面向 AI 推理服务的 Recording Rules覆盖延迟分桶、GPU 利用率、QPS 三个核心维度# /etc/prometheus/rules/ai_inference.yml groups: - name: ai_inference_latency # 评估间隔独立设置延迟指标对时效性要求高 interval: 30s rules: # P50/P90/P99 延迟预聚合窗口 5m # 注意histogram_quantile 必须在 rate 之后计算 - record: ai:inference_latency_seconds:p50_5m expr: | histogram_quantile(0.50, sum(rate(inference_request_duration_seconds_bucket[5m])) by (le, model_name, model_version) ) - record: ai:inference_latency_seconds:p90_5m expr: | histogram_quantile(0.90, sum(rate(inference_request_duration_seconds_bucket[5m])) by (le, model_name, model_version) ) - record: ai:inference_latency_seconds:p99_5m expr: | histogram_quantile(0.99, sum(rate(inference_request_duration_seconds_bucket[5m])) by (le, model_name, model_version) ) # QPS 预聚合按模型版本维度 - record: ai:inference_requests_total:rate5m expr: | sum(rate(inference_requests_total[5m])) by (model_name, model_version) # 错误率预聚合 - record: ai:inference_errors:rate5m expr: | sum(rate(inference_errors_total[5m])) by (model_name, model_version, error_type) - name: ai_inference_gpu interval: 60s rules: # GPU 利用率 5m 平均 - record: ai:gpu_utilization:avg5m expr: avg by (gpu_uuid, gpu_name) (avg_over_time(gpu_sm_occupancy_ratio[5m])) # GPU 显存使用率 - record: ai:gpu_memory_used_ratio:avg5m expr: avg by (gpu_uuid, gpu_name) (gpu_memory_fragmentation_ratio) - name: ai_inference_slo interval: 30s rules: # SLO 达成率P99 延迟在 SLO 阈值内的请求比例 # 假设 SLO 阈值为 200ms - record: ai:slo_latency_compliance_ratio:5m expr: | ( sum(rate(inference_request_duration_seconds_bucket{le0.2}[5m])) by (model_name) / sum(rate(inference_request_duration_seconds_count[5m])) by (model_name) )规则配置好之后需要在 Go 侧做规则健康检查。以下代码周期性验证 Recording Rules 是否产出预期指标package observability import ( context fmt time github.com/prometheus/client_golang/api v1 github.com/prometheus/client_golang/api/prometheus/v1 github.com/prometheus/common/model ) // RuleHealthChecker 检查 Recording Rules 是否正常产出 type RuleHealthChecker struct { promAPI v1.API // 预期必须存在的预聚合指标列表 expectedMetrics []string // 检查结果回调接入告警通道 onStale func(metric string, lastSeen time.Time) } // NewRuleHealthChecker 创建规则健康检查器 // promAddr: Prometheus API 地址, expectedMetrics: 预期指标名列表 func NewRuleHealthChecker(promAddr string, expectedMetrics []string, onStale func(string, time.Time)) (*RuleHealthChecker, error) { client, err : api.NewClient(api.Config{Address: promAddr}) if err ! nil { return nil, fmt.Errorf(create prometheus client: %w, err) } return RuleHealthChecker{ promAPI: v1.NewAPI(client), expectedMetrics: expectedMetrics, onStale: onStale, }, nil } // Check 执行一轮规则产出检查 // 对每个预期指标执行即时查询确认最近 5m 内是否有数据 func (h *RuleHealthChecker) Check(ctx context.Context) error { now : time.Now() for _, metric : range h.expectedMetrics { // 带超时的子 context防止某个查询 hang 住阻塞全局检查 queryCtx, cancel : context.WithTimeout(ctx, 10*time.Second) // 查询最近 5 分钟是否有该指标的数据点 result, _, err : h.promAPI.Query(queryCtx, metric, now) cancel() if err ! nil { // 查询 API 错误不等于指标缺失继续检查下一条 continue } // 判断结果类型并提取最后样本时间 switch v : result.(type) { case model.Vector: if len(v) 0 { h.onStale(metric, now) } case model.Matrix: if len(v) 0 { h.onStale(metric, now) } default: // Scalar 或 String 类型不是 Recording Rule 的预期产出 // 不报错由上层决定是否告警 } } return nil } // Run 周期性执行健康检查 func (h *RuleHealthChecker) Run(ctx context.Context, interval time.Duration) error { ticker : time.NewTicker(interval) defer ticker.Stop() for { select { case -ctx.Done(): return ctx.Err() case -ticker.C: // 单次检查失败不影响后续周期 if err : h.Check(ctx); err ! nil { // 建议此处上报 check 失败次数指标用于自监控 } } } }四、Recording Rules 的设计陷阱与取舍指标基数爆炸Recording Rules 产出的指标也占用存储。如果按model_name × model_version × gpu_uuid三个标签维度做全排列预聚合假设有 20 个模型 × 5 个版本 × 100 张卡 10000 条时间序列每条规则就多 10000 个序列。5 条 P50/P90/P99 规则就是 50000 条加上原始 histogram bucket假设 20 个 bucket存储压力直接翻倍。应对策略延迟指标只按 model_name 聚合GPU 指标只按 gpu_uuid 聚合避免做笛卡尔积式的跨维度聚合。评估间隔的陷阱interval: 30s意味着 P99 数据滞后最多 30 秒。对于实时告警这个滞后可以接受。但对于依赖预聚合结果做自动扩缩容决策的场景30s 滞后可能导致扩容慢半拍。建议告警用 30s 间隔的预聚合自动扩缩容用 15s 间隔的独立规则组。histogram_quantile 的逼近误差Prometheus 的 histogram 是分桶统计分位数计算是基于桶的线性插值。如果 AI 推理延迟分布在 180ms-220ms 区间而 histogram bucket 只设置了 100ms 和 500ms 两个边界那 P99 的估算误差可以高达 30% 以上。Recording Rules 放大了这个误差——预聚合一旦算错后面所有面板都在用错误数据。正确做法Histogram bucket 必须覆盖你的 SLO 阈值周围的值域比如 SLO 是 200ms至少要有 50ms、100ms、150ms、200ms、250ms、500ms 这几个桶。规则更新的灰度风险修改 Recording Rules 后新老规则产出的指标名可能不一致导致 Grafana 面板断裂。如果规则变更引入了 PromQL 错误整个规则组会被跳过所有面板同时空白。建议规则变更前先在 Staging Prometheus 实例上验证 30 分钟确认产出指标的值域和标签维度符合预期后再推生产。五、总结Recording Rules 对于 AI 服务监控不是可选项是必修课延迟分桶必须预聚合P50/P90/P99 的计算成本极高用interval: 30s的规则组提前算好Grafana 面板直接读结果查询时间从 40 秒降到 1 秒以内。避免跨维度笛卡尔积延迟指标只按模型聚合GPU 指标只按设备聚合分开建规则组能省 80% 以上的额外存储。Histogram bucket 要精准Recording Rules 放大了分桶误差bucket 边界必须覆盖 SLO 阈值附近的值域否则预聚合数据本身就是错的。规则健康检查不能省用 Prometheus API 做自监控定期查询预聚合指标是否有数据产出别等运维发现面板空白了才知道规则挂了。