9.7 KiB
public 端口性能审查报告(1w 瞬时并发请求口径)
- 审查日期:2026-09-03
- 审查对象:https://official.inkreach.cc 的公开访问链路(public 端口 = 边缘 nginx 80/443 → v2-api:3001 → v2-postgres)
- 审查口径:1w 瞬时并发请求——同一时刻 1 万个请求同时到达边缘(压力测试式,万 QPS 级)
- 结论:当前形态不能承压,差距约 2~3 个数量级。瓶颈全部位于「配置与架构层」,与商品数据规模无关(goods 仅 240 行),改造后单机有希望在口径内达标,需压测实证。
1. 现状架构
单机(4 核 / 14GB / 无资源限额)docker compose 单栈 4 容器,全部单副本:
公网:80/443 (official.inkreach.cc)
└─ deploy-admin-1 (边缘 nginx 1.27-alpine,官方默认主配置)
├─ /public/ /v2-api/ /uploads/ /assets/ ──► deploy-v2-api-1 (NestJS :3001)
├─ /v2/admin/ /v2/h5/ ──► deploy-v2-admin-1 (SPA 静态)
└─ /admin/ /h5/ ──► try_files 本地静态
deploy-v2-api-1 ──► deploy-v2-postgres-1 (postgres:16-alpine)
关键事实(均已核对源码/配置原文):
| 项 | 现状 | 出处 |
|---|---|---|
| nginx worker | worker_processes auto(4 worker)× worker_connections 1024 → 最多 4096 条客户端连接 |
容器内官方默认 nginx.conf(deploy/nginx/admin.conf 仅覆盖 conf.d) |
| 边缘路由 | /public/ 路径原样透传至 v2-api:3001 |
deploy/nginx/admin.conf L72-79 |
| 压缩 | 无 gzip(JSON/JS/图片全裸传) | nginx 默认 gzip 注释关闭;main.ts 无 compression 中间件 |
| 上游 keepalive | 无(每请求新建到 v2-api 的 TCP 连接) | admin.conf 无 upstream keepalive |
| 限流 | 全局限流 120 req/min/IP | apps/api/src/app.module.ts L27-32 |
| API 连接池 | Prisma 零配置 → 默认池 = 4核×2+1 = 9 条连接,排队默认 10s 后报错 | apps/api/src/prisma/prisma.service.ts L12;DATABASE_URL 无参数(compose L35) |
| PG | max_connections=100(默认)、无 statement_timeout、shared_buffers 默认 |
docker-compose.yml 未覆盖;postgres:16 默认 |
| 缓存 | 全链路零缓存(无 Redis/cache-manager/nginx proxy_cache) | 全仓 grep 零命中 |
| 同步任务 | 整点分类同步(长事务串行 ~226 分类)、03:30 详情全量同步 + 逐单族重算,与 public 同进程同池 | apps/api/src/sync/sync.service.ts L106-114、L138-145 |
| 数据规模 | goods 240 / origin_goods 542 / variants 12,155 / categories 243 / countries 12 | 生产导出 data-dump.json(2026-08-22) |
2. 逐层瓶颈证据
2.1 边缘 nginx:第 0 层就爆(连接数硬顶 4096)
worker_connections 1024 × 4 worker = 4096 条并发客户端连接。HTTP/2 下每个浏览器占 1 条连接;1 万并发请求到场时,约 六成连接进不了 accept 队列(内核 backlog 默认 511,其余直接 reset/拒绝)。这是与后端优化无关的硬天花板。
2.2 API 层:单进程 + 9 连接池,直连 DB 吞吐 2050 req/s
GET /public/goods无 SQL 分页:findMany不带 take/skip 全量拉取匹配行(public.service.tsL234),再内存分组、内存分页(L249-280)。数据量百级时可行,但每次请求的成本是「全表扫描 + 多表联表 + JS 排序」。- 每次请求两个全表聚合:
loadFamilyMinPrices()(L334-351):CROSS JOIN LATERAL jsonb_array_elements(price_matrix->'rows')展开全部族(每族 ~11KB JSONB),/public/goods每页与/public/home-goods每次都跑;注释实测同类全量扫描 ~330ms(L329-333)。- DEFAULT 排序额外
loadTreeOrderMeta()(L287-307):全 countries + 全 categories 联表 raw SQL。
- N+1 与串行链路:
- 分类树
getCategoriesTree:逐祖先串行findUnique(L102-113)。 - 详情
getGoodByFamilyId:3 条查询并发 + 响应内嵌整份 ~11KB price_matrix、全部变体 designData;resolveCategoryIcon串行向上最多 10 次 findUnique(L552-565)。
- 分类树
keyword走ILIKE '%kw%'(L199),无 pg_trgm 索引 → 必 seq scan(当前 240 行无感,量级放大后失效)。- 单请求 DB 成本合计
300500ms(列表)与100300ms(详情),9 条连接串行化后:列表接口吞吐 ≈ 9 ÷ 0.4s ≈ 20~25 req/s,全端点混合3060 req/s。
2.3 数据库层:无防护,池耗尽即排队超时
- Prisma 池 9 < PG max_connections 100,但无任何超时参数:慢查询可无限期占住连接;同步长事务(分类同步单事务串行 ~226 分类、详情同步逐商品 upsert)与 public 抢同一池。
- 池排队默认 10s 超时(P2028):1 万瞬时请求直连 DB 时,绝大多数请求排队 >10s 直接报错。
- 无 statement_timeout,无连接池水位监控。
2.4 传输层:无 gzip、无缓存头
- 详情响应内嵌 ~11KB price_matrix + 全部变体 designData;列表响应平均 ~2-5KB。按
20KB/响应粗算,1 万 req/s ≈ 1.6Gbps,可打满常见 1Gbps 出口带宽;gzip 后 JSON 可压 510 倍。 /uploads//assets/静态资源无任何 Cache-Control(main.tsL73-81),每次访问重复下载。
2.5 全链路零缓存(杠杆最高的修复点)
分类树、国家、标签组、首页商品、商品列表、详情全部每次实时查库。数据变更频率为小时级同步(syncCategories/syncProducts 整点跑),这意味着对 public 读路径引入分钟级 TTL 缓存几乎无损数据新鲜度,却能把 DB 热路径负载降 95%+。
2.6 同步任务竞争
整点分类同步长事务、03:30 详情全量同步 + 逐单族重算(fire-and-forget 进程内任务)与 public 共享同一 9 连接池。同步窗口内池被长事务/串行往返占满时,在线请求直接排队甚至超时。
2.7 压测陷阱:Throttler 429
全局限流 120 req/min/IP(app.module.ts L27-32)。真实 1 万用户分布在不同 IP 无影响;但单机/少 IP 压测流量会被 429 打满,压测必须使用分布式源(≥数十个出口 IP)或临时抬高 THROTTLE_LIMIT,否则测出来的是限流器而不是系统容量。
3. 容量估算与结论
| 层 | 上限 | 1w 瞬时并发需求 | 差距 |
|---|---|---|---|
| nginx 连接数 | 4096 | 10000 | ~2.4x |
| API 直连 DB 吞吐 | ~30-60 req/s | ~10000 req/s | ~200-300x |
| 出口带宽(无 gzip) | ~1 Gbps | ~1.6 Gbps | ~1.6x |
| PG 连接 | 100 | 9 池上限前置 | — |
结论:当前形态不能承压 1w 瞬时并发请求。即便并发被 nginx 放进来,DB 直连吞吐差两个数量级以上,池排队 10s 超时会把瞬时洪峰转为大面积 5xx。
方向:瓶颈 100% 属于「配置与架构层」——进程内存缓存(TTL 5~10min + 同步后失效)可把 DB 热路径负载降 ~95-99%;nginx 连接数/gzip/keepalive/限流补齐后边缘可达 1w+ 连接;池参数与 statement_timeout 防排队超时与慢查询占池。改造后单机 4 核全缓存热路径(万级 req/s 简单 JSON 响应)有达标可能,但必须用分布式压测实证,达标口径见下。
4. 整改方向(摘要)
实施进度(2026-09-03):P0-1/P0-2/P0-3 已在
refactor/public-capacity-10k-yeuimu分支完成并全量测试通过(238/238)——进程内分域缓存 + 全写路径即时失效、nginx 主配置/keepalive/gzip/限流/缓存头、连接池与 PG 参数。尚未部署到生产(部署需按全局约束先备份存档、低峰 force-recreate/重建镜像);P1-3 压测基线(镜像栈 + 分布式源)为验收前置,未达标按计划扩展阶梯升级。
完整任务分解见 plans/refactor/public-capacity-10k-refactor.md:
- P0-1 进程内存缓存层(NestJS cache-manager 或等价实现):categories/countries/tags/tag-groups/home-goods/goods(全量物化+内存分页)/goods/:id/族最低价聚合/树序元数据;所有写路径(sync/族重算/admin 写/上传)提交后按域版本号主动失效(全局约束:等待 TTL 过期不是兜底)
- P0-2 nginx 调优:
worker_connections 16384、worker_rlimit_nofile、upstreamkeepalive 64、gzip on、/uploads /assets 缓存头、limit_req 兜底、proxy 超时显式化 - P0-3 连接与超时:
DATABASE_URL?connection_limit=50&pool_timeout=3(pool_timeout 单位为秒)、PGstatement_timeout=10s、shared_buffers/effective_cache_size 调参 - P1-1
/public/goodsSQL 分页(take/skip + count) - P1-2 同步任务与 public 隔离(错峰/批量化/独立 worker 开关)
- P1-3 压测基线:一次性 docker postgres + 生产 dump 测关键 SQL 真实耗时;k6 分布式压测
- P2 静态资源边缘直出、pg_trgm 索引、连接池水位监控告警
5. 验收口径(改造后)
- 分布式压测(≥数十个出口 IP,或
THROTTLE_LIMIT临时抬高):1 万并发瞬时请求下:- p95 延迟 < 300ms(CPU 侧),p99 < 1s
- 错误率 < 0.1%,无 nginx 连接拒绝/重置
- PG 连接水位不触顶(< 80%),无池排队超时
- 公布达标数字(req/s、p95/p99、错误率)后视为验收通过;不达标则加 v2-api 副本(上游 keepalive 已就位,扩容仅需加容器)。
附:相关文件索引
- 边缘路由:
deploy/nginx/admin.conf - 编排:
deploy/docker-compose.yml - 公开 API 实现:
apps/api/src/public/public.controller.ts、apps/api/src/public/public.service.ts - 入口中间件:
apps/api/src/main.ts;限流:apps/api/src/app.module.ts - 连接池:
apps/api/src/prisma/prisma.service.ts - 同步任务:
apps/api/src/sync/sync.service.ts、apps/api/src/product-families/family-recompute.service.ts - 数据规模:
deploy/data-dump.json(2026-08-22 导出)