Files
sundynix-agentix/LOAD_TEST_REPORT.md
Blizzard 2e3cb8105d fix(store): PG DSN 默认用 127.0.0.1 免每连接 DNS 解析;订正压测报告 C=256 归因
DSN 由 localhost 改 127.0.0.1(gateway + mcp-go):pgx 每新建连接解析一次主机名,
高并发连接池扩容时省掉这层 DNS 开销与一类失败模式。

诚实订正:复测发现 DSN 改完 C=256 仍 0 完成(错误从 lookup localhost 变 dial 127.0.0.1 canceled)。
故 C=256 的「停摆」根因不是 DNS,而是单节点在 256 并发下整体饱和(任务排队 + 每任务 3 次 PG
状态写 + 网关 handler 顶不住),属单节点容量上限、非 bug;C≤128 优雅可用,正解是横向扩。
LOAD_TEST_REPORT §4.3/§6 与 project_analysis 容量实测已据实更新。

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

126 lines
7.0 KiB
Markdown
Raw Permalink 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 的「停摆」:单节点饱和,非 bug
现象:C=256 在 10s 窗口内 0 完成。
初判曾归因于 DSN 用 `localhost` 致 pgx 每连接 DNS 解析、极端并发下 DNS 查询被取消。
**已把 DSN 改为 `127.0.0.1`**(确实消除了 DNS 那一层 + 每连接解析开销,是值得的卫生改进),
但复测 C=256 仍 0 完成 —— 错误从 `lookup localhost` 变成 `dial tcp 127.0.0.1:5432: operation was canceled`
**真实根因**:单节点在 256 并发下整体饱和 —— 任务排队 + PG 状态写路径(每任务 3 写、池 25)+ 网关
handler/连接 顶不住,单任务延迟超过测试窗口,故窗口内零完成(那些 "canceled" 是窗口到点取消在途请求的尾部噪声)。
**这不是要修的 bug,是单节点容量上限的表现**:C≤128 优雅降速可用,256 已远超单 dispatcher 的 ~110/s 甜区。
**正解是横向扩**(加 dispatcher 副本,队列组自动分摊)——正好印证 §4.4 的判断。
单节点要再抬高,则需状态写批量/异步化(降低每任务的同步 PG 跳数)。
### 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. 待补(后续)
- ✅ DSN `localhost → 127.0.0.1`(已修,消除每连接 DNS 解析;但不解决 §4.3 的单节点饱和)。
- 长稳压测(小时级):看内存 / 文件句柄 / goroutine 是否泄漏。
- 真实 LLM 端到端容量:受在线 provider 限流,需自部署模型后再测。
- 状态写批量/异步化探索(抬高单节点天花板)。
- 多副本 + 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
```