batch-write-bind-limit.spec.ts
3.32 KB
-
perf(分配): 落库改 unnest 数组 —— 甩掉 PG bind 上限那堵墙,上限 500 → 20000 · aabd8ae9
生产实遇(杭州大厦 2026-08-20):23 位客服按默认值 23×15×3=1035,提案出了 537 条, 主管点确认只回一句「请求字段校验失败」,在卡片上改什么都没用。 根因不是业务,是**写法**:落库那条 UPDATE 用 `VALUES (…),(…),…`,每行 9 个 bind, 而 PG 单条语句上限 32,767 → 32767/9 ≈ 3,640 行就是硬墙。测试机实测 N=4000 直接报 `too many bind variables in prepared statement, expected maximum of 32767, received 36000`。 ── 改法 ────────────────────────────────────────────────── 每列打成一个数组,`unnest($1::uuid[], $2::text[], …)` —— **不管多少行永远 9 个 bind**。 墙没了,而且顺带更快(PG 不用再解析几千个占位符)。 测试机实测(事务内跑完回滚,一行数据没改): N=500 VALUES 444ms → unnest 74ms N=2000 VALUES 458ms → unnest 220ms N=4000 VALUES
❌ 炸 → unnest 433ms 忠实形状(13 列全写 + 同事务账本写入),线性到 5 万条: 500→0.25s 2千→0.6s 5千→1.4s 1万→2.7s 2万→5.6s 5万→13.6s(≈0.27ms/条) ── 事务保持不变 ────────────────────────────────────────── · 仍然是**一个事务**,全成或全不成 ——⛔ 没有拆成多批提交 · 并发安全的那三个 where 守卫(status='active' / assignee IS NULL / superseded_at IS NULL) 和 RETURNING 一字未动 —— 抢先认领的行照旧落不上、照旧出现在 skipped 里 · 超时改成**随条数放宽**:30 秒打底 + 每条 3ms,上限 180 秒。 一刀切 30 秒的话小批次白留 100 倍余量、大批次又不够。⛔ 不设无限:事务开着就持有行锁。 ── 一起改的 ────────────────────────────────────────────── · 调整在手的三条写路径(改派/改时限/移出)同样换 unnest · 那条 `fp.id IN (Prisma.join(...))` 换 `= ANY($1::uuid[])`(3 万个 id:521ms → 219ms) · 报错文案说清他能动哪个旋钮 —— 他手上只有时限和每天几通,没有"本批人数"那个输入框⚠ ️ 上限没敢一次拉满。再往上调之前要先量这三样(注释里列了):事务外那几个 Prisma `id:{in:[]}` 读(~32,000 个 bind 就炸)、推给浏览器的 6.3MB 载荷、卡片渲染 2 万行。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed