products 离群端点裁决包 v1(T4 产物 · 波波性能基线遗留 · 2026-09-15)

背景接令:波波 D2 §7 真机位性能基线(staging-baseline-2026-09-14.json)中 GET /api/miniapp/products 为全站唯一离群点——P95 26.6ms / MAX 66.6ms(抖动 2.5 倍)/ 响应 26,057B,三项均为全站最高,约为次高端(guides 5.0KB)5 倍、其余端点(1-4ms)10-20 倍。
本包只裁决,不动代码;不含 a 端 /api/admin/products(admin_backend.py:667,鉴权控制台路径+offset 分页,非 C 端热路径,明确排除在离群修复范围外)。

一、现状(读码结论,非估算处均标注来源)

1.1 端点实现(backend/app/routers/miniapp_products.py)

list_products(:79-89,默认 limit=50、上限 200)每次请求的查询构成:

#查询位置说明
1packages 全行(含 description TEXT):81-84一次取 50 行
2site_config.default_leader(领队):34-35每请求 1 次
3..Npackage_components(含 description TEXT):96-102 循环内 await _components_of(p["id"])每产品 1 次 → N+1

_map_product(:43-70)返回字段:id/title/cover/description/leader/includes[](每项含 description)/price/duration/difficulty(硬编码 medium)/dailySchedule(恒空)/tags/status(恒 published)/addons(恒空)/originalPrice(恒 null)。

1.2 延迟离群的根因=N+1(证据:线性拟合精确吻合)

1.3 25.4KB 包体构成(推算+定性,字段级精确占比待 staging 复测标定)

1.4 消费方现状(改动面评估依据)

二、优化选项 A / B / C

选项 A:响应裁剪(列表瘦身)

内容:list_products(连带 featured/curated/search 三个同源端点,均走 _map_product)增加列表态映射:

预估收益:

改动面:后端 1 个文件(miniapp_products.py,列表态开关+rider);前端 0-1 个页面(A1 需 category-package 视觉确认);契约零变更(Package 列表本无 description)。

选项 B:分页收紧 + 字段白名单

内容:默认 limit 50→20、cap 200→50;白名单化响应字段(剔除 originalPrice/difficulty/status/dailySchedule/addons 等恒定恒空字段)。

预估收益:

改动面:后端 1 文件 + typings 1 文件 + 前端多页面核对(category-package/category-hotel/home 等全部消费 products 列表的页面)。

选项 C:缓存(BFF 层 TTL≤60s)

内容:代码已存在(见 1.4)。剩余工作是验证+登记,不是开发:

  1. staging 确认 BFF 部署态且 /api/v2/products 二次请求返回 cached:true
  2. 观测冷 miss(每 TTL 窗口 1 次)仍为 ~26ms —— 该成本被 60s 摊薄,可接受
  3. 结果登记进 bff-map.md / 性能预算文档

预估收益:

改动面:零代码(除非 staging 验证发现 BFF 缓存失效 → 另计 0.5-1 人日修复)。缓存口径完全符合既有政策:docs/contracts/backend-info-pack-v2.md:152「缓存只许 site-texts/产品公开只读数据(TTL≤60s)」。

三、裁决建议(R1 倾向 A+C)

裁决:A1(或 A2,待 staging 字节分账后定)+ rider 批查 + C 验证登记

裁决理由:

  1. 互补而非重叠——A 管字节(客户端带宽+冷 miss 传输量,C 管不了),C 管服务端延迟屏蔽(A 的裁剪对往返数无贡献)。两者各覆盖对方盲区
  2. C 近乎零成本——代码已存在且政策早已背书(TTL≤60s 白名单明文覆盖产品公开只读),只剩验证登记;放弃 C 等于丢掉已付费资产
  3. rider 随 A 顺带——P95 离群的真根因是 N+1(1.2 的线性证据),只做 A+C 而不带 rider,直连 FastAPI 路径的 26.6ms 原样保留;rider 与 A 改同一函数、同一测试面,边际成本最低
  4. 契约风险最小——A 不动响应形状契约(Package 列表本就无 description),B 动;B 的白名单收益/风险比最差,否决 B(其分页收紧部分可在 A 落地后按需另议,不在本包强推)

反对意见(如实记录,均不推翻裁决):

  1. A2 截断是语义变更——列表 description 从全文变摘要,未来若有消费方按全文使用会踩静默截断坑 → 缓解:优先 A1(删字段,契约干净);A2 仅在前端坚持要 teaser 时选用
  2. 60s 缓存意味着价格最长延迟 60s 可见——公开只读政策的既有接受项;缓解:下单/详情链路不走列表缓存,价格敏感操作不经过该路径
  3. BFF 内存缓存是单实例 Map、无集中失效——当前流量规模可接受,登记为已知限制;多实例扩容时需重审
  4. A 的包体收益比例(−40~60%)是推算——若 staging 字节分账显示大头另有其人(如 cover_url 巨长、leader avatar 重复占比超预期),A 的刀法须按分账结果重切 → 开工前先拉一次真实响应做字节分账(半小时级),再锁 A1/A2
  5. C 的「已实现」未经 staging 实证——若验证发现 BFF 未部署或缓存不生效,C 从 0.5 人日涨到 1-1.5 人日,届时可评估是否降级为「仅登记待办」

四、工程量估算(人日)

选项内容后端前端验证/回归合计
前置staging 字节分账(拉真实响应逐字段计量)——0.50.5
A1+rider列表裁剪两级 description + 组件批查(N+1→3 查询)1.00.5(category-package 视觉适配)0.5(perf_probe 复测)2.0
A2 变体截断 120 字替代硬裁剪(前端零改动)0.500.51.5(替代 A1 时)
B分页收紧+字段白名单+契约同步1.01.0(多页面核对)0.52.5(收益最低,已否决)
CBFF 缓存 staging 验证+登记0(已实现)00.50.5(验证失败另计 0.5-1)
R1=A+CA1+rider + C 验证(含前置分账)1.00.51.5≈2.5-3.0 人日

复测判据(验收口径,沿用波波 perf_probe.py 同脚本同机位)


产物:本文件(裁决包,不含代码改动)。执行待刘总/派工岗按 R1 放行后另开工单。

依据链:staging-baseline-2026-09-14.json(波波实测)→ miniapp_products.py / bff.service.ts / bff-map.ts / api.d.ts / 005_packages.sql(读码)→ backend-info-pack-v2.md:152(缓存政策)。