真正能进SLA的P99必须来自Prometheus直方图指标,用histogram_quantile配合rate()和by(le)计算,Sanic用sanic_prometheus,其他框架用prometheus_client的Histogram,并按endpoint、状态码打标。

直接用 sanic_prometheus 或 prometheus_client 埋点,别手写计时器
Python服务里测P99最常见错误,就是用 time.time() 包裹一次请求然后取最大值——这根本不是P99,只是你采样里最大的那个数,样本量、分桶逻辑、并发干扰全没控制。真正能进SLA的P99必须来自直方图(histogram)指标,靠Prometheus原生聚合能力算出来。
如果你用的是Sanic,sanic_prometheus 是最省事的选择;如果是Flask/FastAPI/自研HTTP服务,直接上 prometheus_client 的 Histogram 类就行。关键不是“有没有埋点”,而是“是否按请求路径+状态码维度打标”,否则查不出哪个endpoint拖垮了整体P99。
- 避免在业务逻辑里手动记录耗时:容易漏异常分支、忽略中间件开销、无法对齐Prometheus采样周期
- 必须给
Histogram配置合理的buckets,比如[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0](单位秒),太密浪费存储,太疏没法定位毛刺 - 多进程部署时,
prometheus_multiproc_dir环境变量必须设好且目录为空,否则各worker指标会互相覆盖或丢失
histogram_quantile(0.99, ...) 查询要带 rate() 和 by (le)
很多人写PromQL直接用 histogram_quantile(0.99, your_latency_bucket),结果发现数值跳变剧烈甚至为NaN——这是因为直方图原始数据是累积计数,必须先用 rate() 求单位时间增量,再按 le 标签分组聚合,最后才能量化分位数。
正确写法(以过去5分钟P99为例):
立即学习“Python免费学习笔记(深入)”;
histogram_quantile(0.99, sum(rate(your_app_request_latency_seconds_bucket[5m])) by (le, endpoint, method))
漏掉 sum(...) by (le, ...) 就会导致不同endpoint的桶混在一起算,结果完全不可信;不套 rate() 则会把历史累计值当瞬时值处理,尤其在服务重启后会出现归零再暴涨的假象。
- 如果只关心某个接口,务必加
endpoint=~"/predict"这类过滤,不然P99会被健康检查等低延迟请求拉低 - 不要用
increase()替代rate():在 scrape 间隔抖动或中断时,increase()容易误增 - Grafana里展示时,建议同时画出P99 + P95 + 平均值三条线,毛刺是否集中在尾部一目了然
GPU推理服务必须用CUDA Event同步,time.time() 会严重低估延迟
大模型API跑在GPU上时,time.time() 测出来的“延迟”其实是CPU发起调用到函数返回的时间,GPU还在后台跑。真实P99可能比它高3–5倍——尤其在batch size小、显存带宽饱和时,这个误差会直接让你的SLA承诺失效。
正确做法是用PyTorch/CUDA原生事件同步:
start_event = torch.cuda.Event(enable_timing=True)<br>end_event = torch.cuda.Event(enable_timing=True)<br>start_event.record()<br>output = model(input_tensor)<br>end_event.record()<br>torch.cuda.synchronize() # 等GPU真做完<br>latency_ms = start_event.elapsed_time(end_event)
这段代码拿到的才是模型前向传播的真实耗时,后续再喂给Prometheus的 Histogram 才有意义。
- 别在每次推理前都新建Event对象:创建开销不小,应复用实例
- 如果用了
torch.compile()或TensorRT,首次运行会有编译延迟,P99统计必须跨足够长窗口(至少10分钟)才能稳定 - 异步API(如Streaming响应)不能只测首token延迟,P99应定义为“完整响应收完”的时间,否则会掩盖下游网络瓶颈
线上P99突增时,优先查调度延迟和队列积压,不是模型本身
很多团队一看到P99飙升就去优化模型或换硬件,结果改完发现没用——因为真凶是Linux内核的调度延迟。当机器负载高、运行队列长度(procs_running)持续大于CPU核心数×2时,任务排队等待CPU的时间就会进入毫秒级,这部分延迟会计入你的P99,但模型代码里根本看不到。
快速验证方法:
- 查
/proc/loadavg第一个数是否远超CPU核心数 - 用
pidstat -w 1观察服务进程的%wchan(等待内核态时间占比),超过10%就要警惕 - Prometheus里拉
node_sched_delay_sec(需启用kernel exporter)或process_start_time_seconds波动,看是否与P99尖峰严格同步
这类问题没法靠改Python代码解决,得调系统参数(如 vm.swappiness)、限制进程数、或切到cgroups v2隔离资源。最有效的临时手段,其实是降低worker并发数——让每个请求抢到CPU的概率变高,反而整体P99下降。


















