# 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 吞吐 ~20~50 req/s - **`GET /public/goods` 无 SQL 分页**:`findMany` 不带 take/skip 全量拉取匹配行(`public.service.ts` L234),再内存分组、内存分页(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 成本合计 ~300~500ms(列表)与 ~100~300ms(详情),9 条连接串行化后:**列表接口吞吐 ≈ 9 ÷ 0.4s ≈ 20~25 req/s**,全端点混合 ~30~60 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 可压 5~10 倍。 - `/uploads/` `/assets/` 静态资源无任何 Cache-Control(`main.ts` L73-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](../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`、upstream `keepalive 64`、gzip on、/uploads /assets 缓存头、limit_req 兜底、proxy 超时显式化 - **P0-3** 连接与超时:`DATABASE_URL?connection_limit=50&pool_timeout=3`(pool_timeout 单位为秒)、PG `statement_timeout=10s`、shared_buffers/effective_cache_size 调参 - **P1-1** `/public/goods` SQL 分页(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 导出)