Onyx 如何用 ThreadHogUser 与 HealthProbeUser 压测调整 api-server 的 worker 与 threadpoolSize
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
当 api-server 同时处理大量长时运行的 agent 请求(deep research、code interpreter 等流式聊天回合)时,每个请求会占住一个 anyio threadpool 线程直到回合结束。线程池被占满后 event loop 得不到调度,Kubernetes 的 liveness 探针httpGet /health(timeoutSeconds: 10、failureThreshold: 3)开始超时,pod 被 SIGKILL——这是 Onyx 文档中明确列出的生产失败模式。
Onyx 仓库自带的 Locust 压测套件(tools/loadtest/README.md)提供了一对专门针对该失败模式的场景用户:ThreadHogUser制造线程占用,HealthProbeUser持续探测/health并记录失败率。本文的任务是:用这两个用户跑一轮并发扫描,在 Helm chart 的api.workers、api.threadpoolSize与 CPU 的组合中,找到能让HEALTH:probe在你目标并发下保持 0 失败的最小配置。
实验原理:两个场景用户如何映射到生产故障
- ThreadHogUser(指标前缀
hog:*,实现见 scenarios/thread_hog.py):每个回合流式拉取一个故意很慢的 mock 响应,默认模型mock-ttft1000-itl200-len600≈ 1s + 600 × 0.2s ≈ 121 秒,整个回合占住一个 threadpool 线程。它的思考时间ONYX_HOG_WAIT_SECONDS默认为 1 秒,所以并发数-u几乎直接等于线程池压力。 - HealthProbeUser(指标名
HEALTH:probe,实现见 scenarios/health_probe.py):固定ONYX_HEALTH_PROBES(默认 1)个用户,每ONYX_HEALTH_INTERVAL(默认 1s)GET 一次ONYX_HEALTH_PATH(默认/health),与-u无关,这样聊天负载放大时探测节拍保持稳定。任何慢于ONYX_HEALTH_SLA_MS(默认 10000,即 liveness 的timeoutSeconds)的探测记为失败,失败原因形如slow 12000ms > 10000ms SLA。
HEALTH:probe的失败率就是本次实验的主要信号:它在某并发下开始变非零,说明该配置在这个并发下会开始被 liveness 杀掉。
准备条件
- 安装压测依赖。Locust 依赖在根项目可选的
loadtest依赖组里(默认不同步),必须用uv run --group loadtest跑,裸uv run会重新同步默认组并丢掉 Locust:
uv sync --group loadtest cd tools/loadtest- 启动 mock LLM server并在 Onyx 中注册。压测只测 Onyx 自身代码,LLM 是可控依赖:
uv run --group loadtest uvicorn mock_llm.app:app --port 8001注册方式:Admin Panel → LLM,或PUT /api/admin/llm/provider,provider 类型必须填openai_compatible(不能是openai,后者会走 mock 未实现的 OpenAI Responses API 桥),api_base指向 mock server(如http://localhost:8001),api_key 任意。ThreadHogUser 默认使用模型名mock-ttft1000-itl200-len600,mock 的行为参数(TTFT/ITL/长度)就编码在这个模型名里,litellm 会原样透传。
- 创建 API key。由管理员通过
POST /api/admin/api-key(请求体{"name": "loadtest", "role": "basic"})或 Admin Panel → API Keys 创建,压测时作为ONYX_API_KEY传入。
先跑一轮基线:50 个长请求 + 全程健康探测
ONYX_API_KEY=<key> ONYX_HEALTH_PATH=/health \ uv run --group loadtest locust --headless -u 50 -r 5 -t 15m \ -H https://<your-onyx-url> ThreadHogUser HealthProbeUser其中<key>替换为第 3 步创建的 API key,<your-onyx-url>替换为被测 Onyx 实例地址;-u 50即 50 个并发长请求。跑完看 Locust 输出里HEALTH:probe的失败率:为 0 表示 50 并发下该配置扛住了;出现slow ...ms > 10000ms SLA失败则说明这个并发度下 liveness 已经会开始失败。
扫描 workers / threadpoolSize / CPU 组合
单点运行只能说明一个配置行不行。找"最小够用"配置需要扫描。README 给出的扫描设计是:
- 并发长请求 ∈
{10, 20, 40, 80},用ONYX_SHAPE=stepramp阶梯式爬升,而不是猜一个固定并发数; - 对每一档并发,遍历 chart 配置
api.workers ∈ {1, 2, 4}×api.threadpoolSize ∈ {40, 80}× CPU∈ {2, 4}; - 正确配置 =
HEALTH:probe在整个目标并发内保持 0 失败的最小配置。
stepramp 模式的命令(ONYX_RAMP_STAGES设为扫描用的四档并发,ONYX_RAMP_DWELL为每档停留秒数):
ONYX_SHAPE=stepramp ONYX_RAMP_STAGES=10,20,40,80 ONYX_RAMP_DWELL=300 \ ONYX_API_KEY=<key> ONYX_HEALTH_PATH=/health \ uv run --group loadtest locust --headless -t 25m \ -H https://<your-onyx-url> ThreadHogUser HealthProbeUserONYX_SHAPE=stepramp时形状参数会覆盖-u/-r。
被测侧在 Helm values 中修改对应字段(默认值与含义见 values.yaml 的api:段):
api.workers(默认 1):每个 uvicorn worker 是独立进程,有各自的事件循环和线程池,>1增加并行容量,也能避免单个卡死的 worker 拖垮整个 pod 的/health。chart 会在workers > 1时给 uvicorn 启动命令追加--workers N(见 api-deployment.yaml)。api.threadpoolSize(默认 0):anyio 线程池大小,服务包括流式聊天生成器在内的同步端点。0保持 anyio 默认值 40;每个线程是真实线程,文档要求按 CPU/内存来定大小。chart 将其渲染为容器环境变量ONYX_API_THREADPOOL_SIZE。api.resources:文档特别指出 CPUlimit 必须明显高于 request——在并发慢流负载下,1 核的 limit 会耗尽 CFS quota、拖住异步/health端点并引发 liveness kill。空闲 RSS 约 1.1Gi,memory request 应保持在 1Gi 或以上(chart 默认 request 1000m/1Gi、limit 3000m/3Gi)。扫描 CPU ∈ {2, 4} 时同时调整 request 与 limit。
每改一组 values 就重新部署 api-server,再用同一套压测命令复跑,记录该配置下HEALTH:probe首次出现非零失败的并发档位。
结果判读与可选的 Prometheus 关联
判读标准只有一条且来自文档:HEALTH:probe失败率在你的目标并发下为 0。出现失败(或探测超时被记为 exception)就意味着该配置在该并发下会被 liveness 连续失败 3 次后杀 pod。在所有能通过的配置里选最小的一组(workers / threadpoolSize / CPU 均最小)。
如果需要看资源侧的对应关系,master 会在独立端口(默认9646,LOCUST_PROMETHEUS_PORT可覆盖)的/metrics暴露locust_requests_total、locust_failures_total、locust_response_time_p95_milliseconds等指标;配合导入 dashboards/chat-loadtest-correlation.json,可以在一条时间线上叠加里程碑延迟、失败率与服务端 CPU/内存,观察是哪个资源先饱和。注意 Prometheus Operator(kube-prometheus-stack)不识别 pod annotation,需要针对 metrics service 端口配 ServiceMonitor(示例注释在 k8s/locust.yaml)。
限制与路由注意事项
- 探测路径取决于目标指向:
LOCUST_HOST指向 web/nginx host 时,/loadtest子路径会被 catch-all 路由遮蔽,应指向专用 host 或 port-forward,并把ONYX_HEALTH_PATH设为/api/health;直接指向 api Service 时用/health是正确的。 - 绝对数值不可跨环境比较:文档以 st-dev 为例说明,该环境是 direct-RDS,绝对延迟数字与客户环境不同——不同 workers/threadpoolSize/CPU 配置之间应做相对比较,而不是对某个绝对 SLA 下结论。
- 探针路径不要混用:
/health/ready在线程池饱和时会报 not-ready(适合 readiness),/health只报告进程存活(适合 liveness)。values.yaml 明确警告不要把/health/ready放到 liveness:饱和是负载诱导的、会同时命中所有副本,按它重启会把一次变慢放大成服务中断。 - 想改变单回合占用时长时,用
ONYX_HOG_MODEL换成带不同ttft/itl/len参数的 mock 模型名,而不是改并发数;ONYX_HOG_STREAM_READ_TIMEOUT(默认 600s)是单回合分块间最大等待,121s 的默认回合时长远在阈值内。
【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考