Onyx 如何用 stepramp 拐点爬升压测定位聊天服务容量上限
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
聊天服务(Onyx,danswer 仓库)在做容量规划时,问题往往不是“固定 100 并发能不能跑”,而是“用户数加到多少时系统开始跟不上”。仓库tools/loadtest/内置了一套基于 Locust 的压测装置,按每个回合的里程碑(首包、首个 token、整回合耗时)记录延迟,并且提供一个可选的ONYX_SHAPE=stepramp拐点爬升形状:把并发用户数按 25 → 50 → 100 → 200 这样的台阶逐级推高,让系统“停止跟得上”的那个拐点直接出现在时间线上,而不是靠猜一个固定并发数。本文的操作路径是:装好 loadtest 依赖 → 接入确定性的 mock LLM → 创建 API key → 执行一次 stepramp 爬升 → 用 Prometheus + Grafana 叠图读出拐点。前提是你有一个可被压测的 Onyx 实例,并有管理员权限配置 LLM provider 与 API key。
准备:安装 loadtest 依赖并接入 mock LLM
Locust 及其 gevent/flask 依赖树放在根pyproject.toml的可选loadtest依赖组里(locust>=2.32等),默认不同步。在仓库根目录执行:
uv sync --group loadtest cd tools/loadtest之后所有命令都要带--group loadtest,否则裸uv run会重新同步到默认组、把 Locust 丢掉(见 tools/loadtest/README.md 的 Setup 说明)。
压测装置的核心原则是:只测 Onyx 的应用代码与基础设施在压力下的表现,从不测 LLM 回答质量。因此 LLM 用仓库自带的 mock server 提供无限、零成本、确定性的调用量,任何回归都能归因到 Onyx 代码:
uv run --group loadtest uvicorn mock_llm.app:app --port 8001然后把这个 mock 注册进 Onyx(Admin Panel → LLM,或PUT /api/admin/llm/provider):
- provider 类型必须选
openai_compatible,而不是openai—— litellm 会把openai类型路由到 OpenAI Responses API 桥,mock 没有实现该接口; api_base指向 mock server(如http://localhost:8001),api_key 任意;- 为将要使用的模型名创建 model configuration。本场景按 README 建议配慢速推理模型
mock-ttft8000-itl40-len600(长静默 TTFT),慢 provider 会让流保持打开更久,正是这种压力更早暴露 api-server 的连接/内存耗尽问题; - 每个 model configuration 的max input tokens 需 ≥ 50,000,否则默认窗口过低(deep research 场景会直接拒绝低于该值的模型)。
mock 的行为旋钮就写在模型名里(litellm 原样透传):ttft<ms>首 token 时间、itl<ms>token 间隔、len<n>回答长度,例如mock-ttft8000-itl40-len600就是 TTFT 8 秒、token 间隔 40ms、600 token 的回答。这些名字不是真实模型,必须先在 Onyx 注册为 model configuration 才能使用。
创建压测用的 API key
所有请求用ONYX_API_KEY作为 Bearer token。由管理员通过 Admin Panel → API Keys 创建,或调用POST /api/admin/api-key并传{"name": "loadtest", "role": "basic"}。下文命令中的<key>即指这把 key。
配置并执行 stepramp 拐点爬升
爬升形状实现于 shapes.py 的StepRampShape:把用户数依次压在ONYX_RAMP_STAGES给出的各个平台上,每个平台停留ONYX_RAMP_DWELL秒,超过最后一个平台后测试停止。locustfile.py 只在检测到ONYX_SHAPE=stepramp时才绑定这个形状 —— 因为 Locust 会自动激活它发现的任何 shape 并覆盖-u/-r,所以不设这个环境变量时爬升不生效。相关参数(均可省略,默认值如下):
| 变量 | 默认值 | 用途 |
|---|---|---|
ONYX_RAMP_STAGES | 25,50,100,200 | 各平台的用户数,逗号分隔 |
ONYX_RAMP_DWELL | 300 | 每个平台停留秒数 |
ONYX_RAMP_SPAWN | 5 | 用户生成速率 |
README 给出的完整执行命令:
ONYX_SHAPE=stepramp ONYX_RAMP_STAGES=25,50,100,200 ONYX_RAMP_DWELL=300 \ ONYX_LLM_MODEL=mock-ttft8000-itl40-len600 \ ONYX_API_KEY=<key> uv run --group loadtest locust --headless -t 25m -H https://<your-onyx-url>需要注意两点:
- 形状激活后
-u/-r会被覆盖,因此命令里不再给-u/-r; <your-onyx-url>必须是能服务浏览器等价路径的地址,即把/api/*路由到 api server 的 URL —— 实践中就是部署的用户访问入口(ingress / 负载均衡器)。按 k8s/locust.yaml 的注释,走 ingress 还会顺带检验 LB 空闲超时这类长流杀手。
不指定任何 user class 时跑的是默认加权混合流量:BasicChat 70 / ChatWithSearch 20 / MultiTool 8 / DeepResearch 2,近似生产流量形态;其中 ChatWithSearch 依赖已索引的文档(会走 query 扩展、embedding model server 和 Vespa/OpenSearch 检索)。4 个平台 × 每平台 300 秒约 20 分钟爬升,所以示例用-t 25m兜住整轮。
用 Prometheus 与 Grafana 读出拐点
每个聊天回合会在里程碑包到达的瞬间触发命名伪请求<scenario>:<milestone>,Locust 按名字聚合分位数。与定位容量上限最相关的几个:
chat:first_packet—— 流的第一行(服务端已接受并开始工作);chat:first_answer_token—— 首个回答内容,即 TTFT;chat:total_turn—— 整回合墙钟时间,成功/失败在这里记录。
回合在以下情况判为失败:非 200、流中出现错误包、流在读超时(ONYX_STREAM_READ_TIMEOUT默认 180 秒,指 chunk 之间的间隔而非总时长)后仍无数据、或流在没有任何回答内容 / 没有stop包的情况下结束(截断)。
master/standalone 进程会在专用端口(默认9646,LOCUST_PROMETHEUS_PORT可覆盖)暴露/metrics(实现见 prometheus_exporter.py),worker 不导出。可用指标:
locust_userslocust_requests_total{name,method}/locust_failures_total{name,method}locust_response_time_p50_milliseconds/locust_response_time_p95_milliseconds{name,method}locust_current_rps{name,method}
采集方式取决于你的 Prometheus:annotation-based 方案直接用 master pod 上的prometheus.io/scrape注解(已包含在 k8s/locust.yaml 中);Prometheus Operator(kube-prometheus-stack)忽略注解,需要 ServiceMonitor 指向metricsservice 端口 —— 该清单文件里附了注释掉的示例,release标签要改成与你的 Prometheus 的serviceMonitorSelector匹配。
最后一步是把指标叠成一张时间线:导入 tools/loadtest/dashboards/chat-loadtest-correlation.json,把里程碑延迟与失败率和服务器侧 CPU/内存画在同一时间轴上,并将 dashboard 的$namespace/$workload变量设为被压测的 deployment。README 对拐点的读法就是:p95 / 失败率开始上扬的那一档用户数,以及最先打满的资源。台阶式爬升的好处是每一档延迟水平都对应一个固定的用户数,拐点落在哪一档、被哪个资源卡住,叠图上一眼可见。
在目标集群内运行压测(可选)
如果目标是 k8s 部署,README 建议把整套装置跑进目标集群,避免 WAN 抖动污染延迟测量、LLM 调用保持免费:
kubectl apply -n <onyx-namespace> -f k8s/mock-llm.yaml kubectl create secret generic onyx-loadtest --from-literal=ONYX_API_KEY=<key> kubectl apply -n <onyx-namespace> -f k8s/locust.yaml kubectl port-forward svc/onyx-loadtest-master 8089:8089然后从 8089 端口的 web UI 发起压测(host、用户数、scenario 均可在 UI 指定),或改 master 的 args 走 headless。注意:
- mock 注册地址改为集群内的
http://onyx-mock-llm:8000,并保持is_public=false、persona 作用域,避免真实用户看到它; - 大并发时扩
onyx-loadtest-worker副本数(注释说明一个 worker 可扛数百条并发流),有条件就把 worker 钉在专用节点组上,别让压测生成器与被测系统抢资源; - 清单里的镜像占位
<your-registry>/onyx-loadtest:v0.1.0要换成你自己构建的镜像(tools/loadtest/Dockerfile);分布式模式下所有 pod 必须跑同一构建,否则结果会被静默污染; - 形状与场景的环境变量(
ONYX_SHAPE、ONYX_RAMP_STAGES、ONYX_LLM_MODEL等)通过 worker 清单的env传入,清单注释已注明这一配置入口并指向 README。
限制与注意点
- README 明确:st-dev 集群是 direct-RDS,绝对数值与生产客户环境不同 —— 用这套结果对比不同配置之间的相对关系,不要拿绝对值当 SLA 承诺;
- st-dev 环境下
/loadtest子路径被 catch-all 路由遮蔽,LOCUST_HOST要指向专用 host 或用 port-forward; - 若
LOCUST_HOST走 web/nginx host,健康探测路径用/api/health;直连 api Service 则用/health(这条主要针对 worker/threadpool 扫描场景,stepramp 主路径不涉及); - 这套压测衡量的是 Onyx 代码与基础设施,回答质量不在测量范围内 —— 这是装置设计上的边界,不是缺陷。
跑完一轮 stepramp 后,你能拿到的结论是文档定义的两个量:p95/失败率开始上扬的用户数档位,以及最先饱和的服务器资源。前者就是这次配置下的容量拐点,后者指向上一步该调的方向(worker 数、线程池、资源上限等,README 的 worker concurrency sweep 一节有对应的扫描方法)。
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考