Files
sundynix-agentix/LOAD_TEST_REPORT.md
T
Blizzard 468107ea7a docs: 容量压测报告 + 架构拆分验证(LOAD_TEST_REPORT.md)
把本次压测的方法/数据/结论独立成报告落项目根目录:单节点平台天花板 ~110 任务/s、
单任务开销 ~42ms、瓶颈是多跳管线而非 DB 连接、C=256 崩于 DSN localhost DNS 抖动(易修);
正面验证'平台不是瓶颈 GPU 才是'→ 横向拆服务+队列组喂满 GPU 集群的设计成立,并列出
网关 HA / PG 状态写 / 集群复测三个待补点。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:57:57 +08:00

118 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sundynix-agentix 容量压测报告(首版)
> 日期:2026-06-26 环境:单机本地(macOS / Docker 基建)| 撰写:Opus 4.8(基于本次实测,刻意压低水分)
> 部署相关讨论留待开发完成后另议;本报告只回答一个问题:**平台自身扛得住多少、瓶颈在哪。**
---
## 1. 目的
回答两个一直悬而未决的问题:
1. 平台(去掉 LLM 推理本身)的吞吐天花板是多少?单任务固有开销多大?
2. 把系统拆成「网关 / 总线 / 调度 / 工具」多服务,到底有没有意义?
---
## 2. 方法
- **加压工具**`sundynix-dispatcher/cmd/loadtest`。闭环加压(N 个 worker 不停 `submit → 等收尾 → 再来`),
阶梯提升并发,每档跑 10s。
- **完成判定**:经 **SSE token 流**读到收尾(而非轮询状态)。
> 早期版本用 25ms 轮询,结果把"轮询放大"自身测成了瓶颈(海量 status 请求压垮网关);
> 改用流后每任务仅 1 条推送连接,量到的才是真实任务管线吞吐。
- **隔离 LLM**dispatcher 开 `LLM_FORCE_STUB=1` + `LLM_STUB_TTFT_MS=0` + `LLM_STUB_INTERTOKEN_MS=0`
绕开真实模型推理与计费,**只压全链路 plumbing**
`网关 HTTP → NATS/JetStream → 调度消费 → 图执行 → Token 回流(SSE) → PG 状态写`
- **配置**:单 dispatcher 实例,`DISPATCHER_CONCURRENCY=64`,网关放开限流(`RATE_LIMIT_PER_MIN`)。
---
## 3. 实测数据
| 并发 | 吞吐 (任务/s) | p50 | p95 | max | 说明 |
|---:|---:|---:|---:|---:|---|
| 1 | 23 | 42ms | 46ms | 54ms | **单任务纯平台开销 ≈ 42ms** |
| 2 | 37 | 53ms | 56ms | 70ms | |
| 4 | 54 | 74ms | 77ms | 83ms | |
| 8 | 76 | 103ms | 115ms | 157ms | |
| 16 | 96 | 161ms | 176ms | 257ms | |
| 32 | **112** | 269ms | 345ms | 402ms | **吞吐峰值** |
| 64 | 112 | 557ms | 676ms | 707ms | 饱和:延迟翻倍、吞吐不增 |
| 128 | 102 | 1173ms | 1590ms | 1629ms | 过载,优雅降速(未崩)|
| 256 | 0 | — | — | — | 硬崩(见 §4.3)|
吞吐随并发上升到 ~32 后见顶(~110/s),之后增并发只增延迟不增吞吐 —— 典型饱和曲线。
---
## 4. 分析
### 4.1 单节点平台天花板 ≈ 110 全链路任务/秒
单任务固有开销 ~42ms,由这些串起来:网关收请求 → JetStream 持久化发布(含 fsync)→ 调度消费 →
建图执行 → Token 经 core NATS 回流 + Redis 录制 → 3 次 PG 状态写(submitted / running / done)。
### 4.2 瓶颈不是数据库连接数,是「每任务多跳」
把 PG 连接池从 25 调到 80,吞吐**纹丝不动**(仍 ~110/s)。说明瓶颈不在某一处资源,而是
每个任务要走的**一长串同步跳数**(JetStream 落盘 + 多次 NATS 回写 + Redis 录制 + 多次 PG 写)的累加成本。
要把单节点吞吐再往上抬,得减少每任务的同步跳数(如状态写批量化/异步化),而非简单加资源。
### 4.3 C=256 硬崩的根因(易修)
网关日志:`failed to connect ... lookup localhost: operation was canceled`
DSN 用了主机名 `localhost`,pgx 每新建一条连接都做一次 DNS 解析;极端并发下连接抖动 → 大量 DNS 查询
在请求超时里被取消 → 雪崩。**修法简单**:DSN 改 `127.0.0.1`(免 DNS)+ 连接复用调优。属配置级修复,非架构问题。
### 4.4 核心判断:平台不是瓶颈,GPU 才是
- 平台固有开销 **~42ms / 任务**。
- 真实 LLM 出一轮答案(哪怕自部署 Qwen 32B)是 **秒级**(几百 ms ~ 数秒)。
-**平台开销比 LLM 推理小 1~2 个数量级**,淹没在模型延迟里、可忽略。
→ 一个 dispatcher 副本就能喂 ~110 turns/s 的调度能力;而单张 GPU 跑 32B 大约出
**几十 turns/s**。**平台调度能力 > 单 GPU 产能**,意味着:
- 平台永远不会先于 GPU 成为瓶颈;
- 要提总吞吐,正确做法是**加 dispatcher 副本 + 加 GPU**(NATS 队列组自动负载均衡),而非优化平台代码。
---
## 5. 架构 / 服务拆分是否成立?—— 成立
本次压测正面验证了"拆服务"的设计前提:
| 设计选择 | 压测给出的判断 |
|---|---|
| 网关 / 调度 / 工具 拆成独立服务 | ✅ 成立。各层可独立横向扩;调度是无状态消费者,加副本即扩吞吐 |
| NATS 队列组做负载均衡 | ✅ 成立。这是"加副本即扩容"的基础,且天花板(110/s)远高于单 GPU 产能 |
| 调度与工具用消息总线解耦 | ✅ 成立。42ms 的总线开销相对 LLM 秒级延迟可忽略,换来的解耦/弹性是划算的 |
| dispatcher 不持有 DB、状态经总线回网关落库 | ✅ 成立。让调度保持无状态、可水平复制 |
**一句话**:对"喂满自部署 GPU 集群、按 LLM 产能横向扩"这个生产目标,**当前拆分是对的**。
总线那 ~42ms 的代价,买到了无状态、可水平扩、故障隔离 —— 在 LLM 秒级延迟面前完全值。
### 但有三个要正视的点(不是"拆错了",是"拆了之后要补齐的")
1. **网关目前是单点且偏重**HTTP + 每任务一个 Token 录制 goroutine + 多个订阅者)。横向扩/HA 还没做(P1)。
2. **PG 是全局共享瓶颈**:每任务 3 次状态写。规模再上一个量级时,状态写需要批量/异步化。
3. **单机数字 ≠ 集群数字**:本报告是单节点天花板;多副本下还需验证 NATS 集群、PG 连接总量、跨副本一致性。
---
## 6. 待补(后续)
- 长稳压测(小时级):看内存 / 文件句柄 / goroutine 是否泄漏。
- 真实 LLM 端到端容量:受在线 provider 限流,需自部署模型后再测。
- DSN `localhost → 127.0.0.1`(顺手修,消除 §4.3 崩溃)。
- 状态写批量/异步化探索(抬高单节点天花板)。
- 多副本 + NATS 集群下的容量复测。
---
## 复现
```bash
# dispatcher 以 stub 模式起(绕开 LLM),网关放开限流
LLM_FORCE_STUB=1 LLM_STUB_TTFT_MS=0 LLM_STUB_INTERTOKEN_MS=0 DISPATCHER_CONCURRENCY=64 ./sdx-dispatcher
RATE_LIMIT_PER_MIN=1000000 ./sdx-gateway
# 加压(阶梯并发,每档 10s
go run ./cmd/loadtest -url http://localhost:8080 \
-email <user> -pass <pass> -levels 1,2,4,8,16,32,64,128,256 -dur 10s
```