From 2e3cb8105dcf195184b3ca29cb690b3f0937c5d2 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 26 Jun 2026 12:49:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(store):=20PG=20DSN=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E7=94=A8=20127.0.0.1=20=E5=85=8D=E6=AF=8F=E8=BF=9E=E6=8E=A5=20?= =?UTF-8?q?DNS=20=E8=A7=A3=E6=9E=90=EF=BC=9B=E8=AE=A2=E6=AD=A3=E5=8E=8B?= =?UTF-8?q?=E6=B5=8B=E6=8A=A5=E5=91=8A=20C=3D256=20=E5=BD=92=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- LOAD_TEST_REPORT.md | 20 ++++++++++++++------ project_analysis.md | 4 ++-- sundynix-gateway/cmd/server/main.go | 4 +++- sundynix-mcp-go/cmd/server/main.go | 4 +++- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/LOAD_TEST_REPORT.md b/LOAD_TEST_REPORT.md index f7ad74b..4776523 100644 --- a/LOAD_TEST_REPORT.md +++ b/LOAD_TEST_REPORT.md @@ -57,10 +57,18 @@ 每个任务要走的**一长串同步跳数**(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.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 / 任务**。 @@ -96,11 +104,11 @@ DSN 用了主机名 `localhost`,pgx 每新建一条连接都做一次 DNS 解 --- ## 6. 待补(后续) +- ✅ DSN `localhost → 127.0.0.1`(已修,消除每连接 DNS 解析;但不解决 §4.3 的单节点饱和)。 - 长稳压测(小时级):看内存 / 文件句柄 / goroutine 是否泄漏。 - 真实 LLM 端到端容量:受在线 provider 限流,需自部署模型后再测。 -- DSN `localhost → 127.0.0.1`(顺手修,消除 §4.3 崩溃)。 - 状态写批量/异步化探索(抬高单节点天花板)。 -- 多副本 + NATS 集群下的容量复测。 +- 多副本 + NATS 集群下的容量复测(验证横向扩是否线性)。 --- diff --git a/project_analysis.md b/project_analysis.md index 77f6fe1..2c0d7ae 100644 --- a/project_analysis.md +++ b/project_analysis.md @@ -235,8 +235,8 @@ dispatcher 开 `LLM_FORCE_STUB=1 LLM_STUB_*_MS=0` 绕开真实 LLM 推理,量* 1. **单节点平台天花板 ≈ 110 全链路任务/秒**,单任务固有开销 ~42ms,饱和点 ~并发 32–64。 2. **吞吐瓶颈不是 DB 连接数**(池 25→80 吞吐不变),而是每任务多跳管线(JetStream fsync + 多次 NATS 回写 + Redis 录制 + 3 次 PG 状态写)的综合成本。 -3. **C=256 硬崩**根因:DSN 用 `localhost`,pgx 每新建连接解析一次,极端并发下连接churn致 DNS 取消 - → 易修(DSN 换 `127.0.0.1` / 连接复用)。 +3. **C=256 停摆**是单节点饱和(任务排队 + PG 状态写 + 网关 handler 顶不住),非 bug;C≤128 优雅可用。 + DSN 已由 `localhost` 改 `127.0.0.1`(消除每连接 DNS 解析,卫生改进,但不解决饱和)。正解是横向扩。 4. **关键判断**:平台开销 42ms ≪ 真实 LLM 出一轮答案的秒级延迟,**平台不是瓶颈,GPU 才是**。 单 GPU 跑 Qwen 32B 约出几十轮/秒,远低于平台的 ~110/s → 横向拆服务(队列组多副本喂满 GPU 集群) 的意义成立;要提总吞吐应加 dispatcher 副本与 GPU,而非优化平台代码。 diff --git a/sundynix-gateway/cmd/server/main.go b/sundynix-gateway/cmd/server/main.go index 53bf00f..fee4d2d 100644 --- a/sundynix-gateway/cmd/server/main.go +++ b/sundynix-gateway/cmd/server/main.go @@ -30,7 +30,9 @@ func main() { defer func() { _ = shutdownTrace(context.Background()) }() 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") // 对象存储(大文档正文):默认连 docker-compose 暴露的 MinIO;连不上则降级内联存 PG。 blobStore := blob.Open( diff --git a/sundynix-mcp-go/cmd/server/main.go b/sundynix-mcp-go/cmd/server/main.go index 71b067f..a1d9f30 100644 --- a/sundynix-mcp-go/cmd/server/main.go +++ b/sundynix-mcp-go/cmd/server/main.go @@ -30,7 +30,9 @@ func main() { defer func() { _ = shutdownTrace(context.Background()) }() 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") milvusAddr := envOr("MILVUS_ADDR", "localhost:19530") embBase := envOr("EMBED_BASE_URL", "") // OpenAI 兼容 embeddings 端点(空=向量检索降级)