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>
This commit is contained in:
+14
-6
@@ -57,10 +57,18 @@
|
|||||||
每个任务要走的**一长串同步跳数**(JetStream 落盘 + 多次 NATS 回写 + Redis 录制 + 多次 PG 写)的累加成本。
|
每个任务要走的**一长串同步跳数**(JetStream 落盘 + 多次 NATS 回写 + Redis 录制 + 多次 PG 写)的累加成本。
|
||||||
要把单节点吞吐再往上抬,得减少每任务的同步跳数(如状态写批量化/异步化),而非简单加资源。
|
要把单节点吞吐再往上抬,得减少每任务的同步跳数(如状态写批量化/异步化),而非简单加资源。
|
||||||
|
|
||||||
### 4.3 C=256 硬崩的根因(易修)
|
### 4.3 C=256 的「停摆」:单节点饱和,非 bug
|
||||||
网关日志:`failed to connect ... lookup localhost: operation was canceled`。
|
现象:C=256 在 10s 窗口内 0 完成。
|
||||||
DSN 用了主机名 `localhost`,pgx 每新建一条连接都做一次 DNS 解析;极端并发下连接抖动 → 大量 DNS 查询
|
|
||||||
在请求超时里被取消 → 雪崩。**修法简单**:DSN 改 `127.0.0.1`(免 DNS)+ 连接复用调优。属配置级修复,非架构问题。
|
初判曾归因于 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 才是
|
### 4.4 核心判断:平台不是瓶颈,GPU 才是
|
||||||
- 平台固有开销 **~42ms / 任务**。
|
- 平台固有开销 **~42ms / 任务**。
|
||||||
@@ -96,11 +104,11 @@ DSN 用了主机名 `localhost`,pgx 每新建一条连接都做一次 DNS 解
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 6. 待补(后续)
|
## 6. 待补(后续)
|
||||||
|
- ✅ DSN `localhost → 127.0.0.1`(已修,消除每连接 DNS 解析;但不解决 §4.3 的单节点饱和)。
|
||||||
- 长稳压测(小时级):看内存 / 文件句柄 / goroutine 是否泄漏。
|
- 长稳压测(小时级):看内存 / 文件句柄 / goroutine 是否泄漏。
|
||||||
- 真实 LLM 端到端容量:受在线 provider 限流,需自部署模型后再测。
|
- 真实 LLM 端到端容量:受在线 provider 限流,需自部署模型后再测。
|
||||||
- DSN `localhost → 127.0.0.1`(顺手修,消除 §4.3 崩溃)。
|
|
||||||
- 状态写批量/异步化探索(抬高单节点天花板)。
|
- 状态写批量/异步化探索(抬高单节点天花板)。
|
||||||
- 多副本 + NATS 集群下的容量复测。
|
- 多副本 + NATS 集群下的容量复测(验证横向扩是否线性)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -235,8 +235,8 @@ dispatcher 开 `LLM_FORCE_STUB=1 LLM_STUB_*_MS=0` 绕开真实 LLM 推理,量*
|
|||||||
1. **单节点平台天花板 ≈ 110 全链路任务/秒**,单任务固有开销 ~42ms,饱和点 ~并发 32–64。
|
1. **单节点平台天花板 ≈ 110 全链路任务/秒**,单任务固有开销 ~42ms,饱和点 ~并发 32–64。
|
||||||
2. **吞吐瓶颈不是 DB 连接数**(池 25→80 吞吐不变),而是每任务多跳管线(JetStream fsync + 多次
|
2. **吞吐瓶颈不是 DB 连接数**(池 25→80 吞吐不变),而是每任务多跳管线(JetStream fsync + 多次
|
||||||
NATS 回写 + Redis 录制 + 3 次 PG 状态写)的综合成本。
|
NATS 回写 + Redis 录制 + 3 次 PG 状态写)的综合成本。
|
||||||
3. **C=256 硬崩**根因:DSN 用 `localhost`,pgx 每新建连接解析一次,极端并发下连接churn致 DNS 取消
|
3. **C=256 停摆**是单节点饱和(任务排队 + PG 状态写 + 网关 handler 顶不住),非 bug;C≤128 优雅可用。
|
||||||
→ 易修(DSN 换 `127.0.0.1` / 连接复用)。
|
DSN 已由 `localhost` 改 `127.0.0.1`(消除每连接 DNS 解析,卫生改进,但不解决饱和)。正解是横向扩。
|
||||||
4. **关键判断**:平台开销 42ms ≪ 真实 LLM 出一轮答案的秒级延迟,**平台不是瓶颈,GPU 才是**。
|
4. **关键判断**:平台开销 42ms ≪ 真实 LLM 出一轮答案的秒级延迟,**平台不是瓶颈,GPU 才是**。
|
||||||
单 GPU 跑 Qwen 32B 约出几十轮/秒,远低于平台的 ~110/s → 横向拆服务(队列组多副本喂满 GPU 集群)
|
单 GPU 跑 Qwen 32B 约出几十轮/秒,远低于平台的 ~110/s → 横向拆服务(队列组多副本喂满 GPU 集群)
|
||||||
的意义成立;要提总吞吐应加 dispatcher 副本与 GPU,而非优化平台代码。
|
的意义成立;要提总吞吐应加 dispatcher 副本与 GPU,而非优化平台代码。
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ func main() {
|
|||||||
defer func() { _ = shutdownTrace(context.Background()) }()
|
defer func() { _ = shutdownTrace(context.Background()) }()
|
||||||
|
|
||||||
natsURL := envOr("NATS_URL", "nats://localhost:4222")
|
natsURL := envOr("NATS_URL", "nats://localhost:4222")
|
||||||
pgDSN := envOr("POSTGRES_DSN", "postgres://sundynix:sundynix@localhost:5432/sundynix?sslmode=disable")
|
// DSN 用 127.0.0.1 而非 localhost:pgx 每新建连接解析一次主机名,高并发下连接池扩容时
|
||||||
|
// 大量 localhost DNS 查询会在请求超时里被取消而雪崩(见 LOAD_TEST_REPORT §4.3)。IP 免 DNS。
|
||||||
|
pgDSN := envOr("POSTGRES_DSN", "postgres://sundynix:sundynix@127.0.0.1:5432/sundynix?sslmode=disable")
|
||||||
redisAddr := envOr("REDIS_ADDR", "localhost:6379")
|
redisAddr := envOr("REDIS_ADDR", "localhost:6379")
|
||||||
// 对象存储(大文档正文):默认连 docker-compose 暴露的 MinIO;连不上则降级内联存 PG。
|
// 对象存储(大文档正文):默认连 docker-compose 暴露的 MinIO;连不上则降级内联存 PG。
|
||||||
blobStore := blob.Open(
|
blobStore := blob.Open(
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ func main() {
|
|||||||
defer func() { _ = shutdownTrace(context.Background()) }()
|
defer func() { _ = shutdownTrace(context.Background()) }()
|
||||||
|
|
||||||
natsURL := envOr("NATS_URL", "nats://localhost:4222")
|
natsURL := envOr("NATS_URL", "nats://localhost:4222")
|
||||||
pgDSN := envOr("POSTGRES_DSN", "postgres://sundynix:sundynix@localhost:5432/sundynix?sslmode=disable")
|
// DSN 用 127.0.0.1 而非 localhost:免 pgx 每连接 DNS 解析,避免高并发连接池扩容时 DNS 雪崩
|
||||||
|
// (见 LOAD_TEST_REPORT §4.3)。
|
||||||
|
pgDSN := envOr("POSTGRES_DSN", "postgres://sundynix:sundynix@127.0.0.1:5432/sundynix?sslmode=disable")
|
||||||
redisAddr := envOr("REDIS_ADDR", "localhost:6379")
|
redisAddr := envOr("REDIS_ADDR", "localhost:6379")
|
||||||
milvusAddr := envOr("MILVUS_ADDR", "localhost:19530")
|
milvusAddr := envOr("MILVUS_ADDR", "localhost:19530")
|
||||||
embBase := envOr("EMBED_BASE_URL", "") // OpenAI 兼容 embeddings 端点(空=向量检索降级)
|
embBase := envOr("EMBED_BASE_URL", "") // OpenAI 兼容 embeddings 端点(空=向量检索降级)
|
||||||
|
|||||||
Reference in New Issue
Block a user