用 AI 分析压测结果:曲线、拐点与可疑指标
把 wrk、k6、ghz 的原始输出整段丢给 AI,通常只换回「性能良好,建议优化数据库」这类没有信息量的话。问题不在模型,而在于你把一堆数字当成了一次完整任务:压测结论的价值藏在指标之间的关系里,先把关系组织好,AI 才能给出可被验证的判断。
原始数据要凑齐四类
| 类别 | 具体内容 | 缺了它会怎样 |
|---|---|---|
| 延迟分位(percentile) | P50、P90、P95、P99、P99.9、最大值、平均值 | 只看平均值,长尾被抹平 |
| 吞吐曲线 | 各并发或到达率档位下的 QPS 与对应延迟 | 找不到拐点,只能报一个孤立峰值 |
| 错误分布 | 各错误码计数与占比、一个完整错误样本 | 把失败请求算进成功,吞吐虚高 |
| 系统指标 | CPU 及其时间分布、内存、GC 停顿、load、上下文切换(context switch)、连接数、下游指标 | 无法区分「服务慢」与「机器已经满了」 |
再加上两项元信息:压测机的自身水位,以及数据量、缓存状态与预热情况。没有这两项,后面几乎必然误判。
分析提示词模板
你是性能工程师。下面是一次压测的原始数据。请只基于我给的数据下结论,
不要补充任何我没提供的数字,也不要用「可能」「建议进一步排查」这类无证据措辞。
【环境】服务:Go 1.22,8 核 16G 单实例;DB:MySQL 8.0 单机;Redis 单节点。
压测机:4 核 8G,与目标同机房,压测机 CPU 峰值 55%(未饱和)。
【场景】wrk2 定速压测 /api/order/detail,连接复用,预热 60s,每档稳定 300s。
【数据】
档位(QPS) 实测QPS P50 P90 P99 P99.9 最大值 错误率 CPU GC_CPU cs/s
500 500 4ms 8ms 12ms 20ms 45ms 0% 22% 1% 21k
1000 1000 5ms 11ms 15ms 30ms 60ms 0% 41% 1% 23k
2000 1998 7ms 20ms 38ms 90ms 210ms 0% 76% 2% 31k
3000 2890 12ms 90ms 260ms 700ms 1.5s 1.2% 88% 8% 96k
4000 2950 40ms 600ms 1.8s 3.2s 6.1s 9.0% 91% 14% 185k
【错误码分布】3000 档:503 占 78%,499 占 22%;4000 档:503 占 65%,超时(read timeout) 35%。
【任务】输出 5 条以内的结论,每条按「结论 / 证据 / 反例」三段式给出,并给出下一步要采的两个数据点。
模板里有三处关键约束:档位至少三档、每档时长写清、压测机水位必须给出。三档以下无法判断趋势,时长不明则无法区分拐点与噪声。
判读要点一:吞吐曲线上找拐点(knee)
拐点的定义是:再增加负载,吞吐几乎不再增长,而延迟开始明显上升。
| 现象 | 判断 | 需要补充的证据 |
|---|---|---|
| 拐点处服务端 CPU 已过 85% | 容量型瓶颈,服务端资源打满 | 确认是业务 CPU 还是 GC、系统态 CPU |
| 拐点处 CPU 只有 60%~70% | 排队型瓶颈:锁竞争、连接池、线程数、下游等待 | 阻塞剖析、连接池 active、下游延迟 |
| 越过拐点后吞吐反而下降 | 排队崩溃:超时、重试、GC 陷入恶性循环 | 超时计数、重试放大倍数 |
| 曲线平滑无拐点 | 压测机或客户端先到极限 | 压测机 CPU、网卡、端口耗尽 |
上表例子里,拐点落在 2000 与 3000 档之间:3000 档实测只有 2890,P99 从 38ms 跳到 260ms,CPU 88% 且 GC 占用从 2% 涨到 8%——这是典型的「资源接近饱和 + 长尾开始失控」。
判读要点二:平均值与分位的背离
| 组合 | 含义 | 顺着查什么 |
|---|---|---|
| 平均值正常、P99 异常 | 少数请求被偶发阻塞 | GC 停顿、锁等待、连接池取连接、下游毛刺 |
| 平均值与 P99 同步恶化 | 整体变慢,通常是资源饱和 | CPU、磁盘 IO、网络带宽 |
| P50 稳定、P99 波动大 | 尾部随机性,可能是重试或缓存击穿 | 缓存命中率、慢日志按时间聚合 |
| 平均值远低于 P50 | 数据统计有问题(如把超时请求剔除了) | 重新核对统计口径与错误码计数 |
在例子里,3000 档的平均值约 30ms 而 P99 为 260ms,差了近 9 倍:只报平均值会得出「延迟良好」的错误结论。凡是平均值与 P99 相差超过 3 倍,就必须单独解释长尾。
判读要点三:队列堆积特征
队列堆积在数据上的指纹很规整:吞吐不再增长,延迟却随并发近似线性上升。
按利特尔法则(Little's Law),在途请求数 ≈ 到达率 × 平均延迟。若 3000 档的平均延迟是 30ms,在途请求约 2890 × 0.03 ≈ 87;4000 档延迟涨到 1.8s,在途请求变成 2950 × 1.8 ≈ 5310——数量级跳变说明请求根本没被处理,而是堆在了队列、连接池或 socket 缓冲区里。此时排查方向不是优化单次请求耗时,而是找出队列的容量上限与消费者数量。
| 堆积位置 | 旁证指标 | 采集方式 |
|---|---|---|
| 应用线程池或 goroutine 队列 | 队列长度、活跃线程数、goroutine 数 | 应用指标、goroutine 剖析 |
| 数据库连接池 | active 顶到上限、等待获取连接时长 | 连接池指标、连接数统计 |
| 内核 socket 队列 | Recv-Q 非零、连接建立失败 | ss -lnt、ss -s |
| 下游服务排队 | 下游自身 P99 同步抬头 | 下游大盘、客户端超时计数 |
判读要点四:GC 与上下文切换旁证
上下文切换(context switch)从 23k/s 涨到 185k/s,通常意味着大量线程在锁或 IO 上被唤醒又挂起;GC CPU 占比从 1% 涨到 14%,说明对象分配速率随压力放大。这两项都能推翻「CPU 不够用,加机器就行」的结论:加机器不会降低单实例的切换频率,也不会减少分配。
| 系统指标 | 可疑阈值 | 旁证方向 |
|---|---|---|
| 用户态 CPU 高 | 持续 > 80% | 计算密集、序列化、编解码 |
| 系统态 CPU 高 | 持续 > 20% | 大量 syscall、网络收发、锁系统调用 |
| GC CPU 占比 | > 10% | 分配热点、缓冲区过小、响应体过大 |
| 上下文切换 | 与 QPS 增长不成比例 | 锁竞争、线程数过多、IO 阻塞 |
| load 高于核数 | 持续大于核数 | 排队而非并行 |
要求 AI 给「结论 + 证据 + 反例」
改写以下每条结论:
结论:一句话,带数字,可证伪。
证据:引用我提供数据中的具体行与列(如「3000 档 P99 260ms」),不许引用未提供的数据。
反例:写出这条结论在什么情况下不成立,以及需要什么数据才能排除该情况。
禁止:出现「建议优化」「可能存在」「需要注意」等无法验证的措辞。
| AI 的初稿 | 合格的写法 |
|---|---|
| 系统存在性能瓶颈,建议优化数据库 | 结论:拐点在 2000~3000 QPS,安全容量取 1200 QPS/实例。证据:3000 档 CPU 88%、GC 8%、P99 260ms。反例:若 3000 档的下游 Redis 同时被其他业务压满,则拐点由下游引起,需补测下游独占时的曲线 |
| 可能存在内存泄漏 | 结论:本轮数据无法判断内存泄漏。证据:仅给了 GC CPU 占比,没有堆大小随时间的变化。反例:给出 300s 内 inuse_space 斜率即可判定 |
反例这一段最容易被省略,但它是整条结论的价值所在:写不出反例,说明这条结论只是在复述数字。
收尾两件事
- 每个可疑指标都要有明确的「下一个数据点」:这次没采到的,写进下一轮压测的采集清单,而不是靠推测补全。
- 把 AI 的结论与原始输出一起归档,标注哪些是观测事实、哪些是推断,便于后续压测做对比。
小结:先把分位、曲线、错误码、系统指标四类原始数据凑齐并约束档位与时长,再让 AI 按「结论 / 证据 / 反例」三段式输出;分析时重点盯四点——吞吐曲线的拐点、平均值与 P99 的背离、队列堆积的延迟指纹、GC 与上下文切换的旁证。