阶梯式发压设计:如何准确探测 RAG 服务的最大容量承载点
在给企业级 RAG 系统或高并发大模型网关做容量评估时,很多测试人员最常犯的一个错误就是“一步到位”:直接拉起 2000 个并发并发线程,一股脑把流量轰向服务端。
这种粗暴的“洪水冲击法(Flood Testing)”往往只能得到一个毫无意义的失败结果:服务瞬间被击穿,错误日志铺天盖地,整个集群陷入雪崩。至于系统到底在多少并发下开始出现排队?瓶颈到底最先发生在网关、向量库还是推理 GPU?系统在临界点之前的表现如何?测试人员一无所知。
要准确探测出系统的安全容量水位(Knee Point)与极限崩溃拐点(Saturation / Breaking Point),必须采用科学严谨的**阶梯式发压(Step Load / Ramp-up Testing)**设计。
阶梯式发压的三大核心阶段
一个标准的阶梯式发压方案应当像攀登台阶一样,逐级递增负载,并在每个台阶上维持足够长的时间,观察系统进入稳态后的各项指标:
Concurrent Users / QPS ^ | +-------------+ (极限破坏区 2000 QPS) | +-----+ (性能衰退区 1500 QPS) | +-----+ (拐点探测区 1000 QPS) | +-----+ (安全水位区 600 QPS) | +-----+ (基准预热区 200 QPS) +-----------+-----+-----+-----+-----+-----+-----------------> Time (min) 0-2m 2-5m 5-8m 8-11m 11-14m 14-17m- 基准预热台阶(Warm-up Stage):
从系统额定容量的 20% 起步(如 200 QPS),持续 2~3 分钟。目的是让 OS 的 PageCache 充分预热、JIT/Python 字节码完成热点编译、数据库连接池完成握手建立。 - 渐进爬坡台阶(Step Increments):
以每次增加 20%~30% 的幅度递增(如 200 -> 400 -> 600 -> 800 -> 1000 QPS)。每个台阶必须保持稳定发压3 到 5 分钟。只有持续 3 分钟以上,各种后台异步排队(如 Compaction、GC 垃圾回收、动态批处理积压)才会充分暴露出来。 - 拐点捕捉与持续加压(Knee Hunting & Stress):
当观察到延迟开始呈现非线性陡增或错误率突破 0.1% 时,缩小加压步长(如每次递增 50 QPS),精准圈定系统的承载极限。
核心指标的三维拐点判定法
在阶梯加压过程中,测试工程师必须紧盯三条核心曲线的交叉关系:
1. 拐点一:线性增长拐点(The Knee Point)
- 现象:当发压 QPS 从 200 增加到 800 时,实际服务端处理的 TPS(每秒事务数)与发压量保持严格 1:1 的线性增长,平均延迟和 P99 延迟保持平稳(如均值在 35ms,P99 在 60ms)。
- 结论:800 QPS 即为系统的“最佳生产运行水位(Optimal Capacity)”。在此水位下,硬件算力利用率最高,且没有产生任何排队。
2. 拐点二:延迟爆炸拐点(Saturation Point)
- 现象:当发压量从 800 增加到 1200 时,服务端的 TPS 不再增长,卡死在 950 附近;与此同时,P99 延迟呈现指数组数级飙升(从 60ms 瞬间飙升到 850ms 以上),客户端开始出现少量超时(Timeout)。
- 根因:此时系统的某个核心组件(通常是向量检索并发线程池或 GPU Dynamic Batch 队列)已经达到饱和,新进来的请求只能在内存队列里排队等待。
- 结论:950 TPS 即为系统的“最大吞吐硬上限(Maximum Throughput)”。
3. 拐点三:雪崩崩溃点(Breaking Point)
- 现象:加压继续提升到 1600 时,TPS 发生断崖式下跌(从 950 暴跌至 100 以下),错误率飙升至 50% 以上,网关抛出大量 502/504 错误。
- 结论:系统彻底崩溃,必须介入重启。
k6 自动化阶梯发压脚本实操
使用k6可以极其优美地定义可重复执行的阶梯发压场景:
import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { scenarios: { rag_step_load: { executor: 'ramping-arrival-rate', startRate: 100, timeUnit: '1s', preAllocatedVUs: 200, maxVUs: 2000, stages: [ { target: 200, duration: '2m' }, // 台阶 1: 预热到 200 QPS { target: 200, duration: '3m' }, // 保持 3 分钟稳态 { target: 600, duration: '2m' }, // 台阶 2: 爬坡到 600 QPS { target: 600, duration: '3m' }, // 保持 3 分钟稳态 { target: 1000, duration: '2m' }, // 台阶 3: 爬坡到 1000 QPS { target: 1000, duration: '3m' }, // 保持 3 分钟稳态 { target: 1400, duration: '2m' }, // 台阶 4: 冲击极限 1400 QPS { target: 1400, duration: '3m' }, // 观察崩溃与排队 { target: 0, duration: '2m' }, // 降压观察系统自愈恢复能力 ], }, }, thresholds: { // 设定全链路红线:95% 的请求延迟必须在 150ms 以内,错误率低于 1% http_req_duration: ['p(95)<150'], http_req_failed: ['rate<0.01'], }, }; export default function () { const url = 'http://gateway.internal.local/v1/rag/search'; const payload = JSON.stringify({ query: '分布式锁时钟漂移如何应对?', tenant_id: 'tech_qa', top_k: 5, }); const params = { headers: { 'Content-Type': 'application/json' }, timeout: '2s', }; const res = http.post(url, payload, params); check(res, { 'status is 200': (r) => r.status === 200, }); }总结
容量规划不是猜谜语,而是用严谨的数据丈量系统的物理边界。通过台阶预热 -> 稳态采样 -> 拐点捕捉 -> 恢复测试的标准阶梯发压法,团队不仅能拿到一份有据可查的容量体检报告,更能精准定位出谁是拖垮系统的第一块短板,让每一次架构扩容和性能优化都有的放矢。