docker-compose.prod.yml
5.79 KB
-
fix: 自带 PG 的 /dev/shm 提到 1g + 画像版本号并发竞态(测试服全量回填暴露的两个问题) · 2a02d9d1
昨夜测试服跑优化后的全量回填(35.7 万患者,2.2 小时跑完、2,694 人/分)暴露两个**既有**问题, 都不是优化本身造成的。 ## ① /dev/shm 64MB → plan 全量重算当场崩 ERROR: could not resize shared memory segment "/PostgreSQL.xxx" to 12615680 bytes: No space left on device Docker 给 /dev/shm 的默认只有 64MB(inspect 确认 ShmSize=67108864),而 PG 的并行查询 worker 用共享内存段交换中间结果。召回的 selectHits 把 **10 个子场景 Promise.all 并行跑**, 每个都是 2500 万 patient_facts 上带 LATERAL 的重查询、各自还起 parallel worker, 并发的段远超 64MB。 崩在 selectHits → runSubScenario,那处 Promise.all 本次一个字没动 —— 与 worker pool 无关。 compose 里给 postgres 加 `shm_size: 1g`(PG 容器常规配法)。⚠ ️ 生产用阿里云托管 RDS、不启本服务,不受此问题影响,也不受本改动影响。 ## ② CLI 与 stale-scan cron 抢版本号 → Unique constraint 全量回填跨过了 02:00 的 `PAC_STALE_SCAN_CRON`,两个**独立进程**同时给同一批患者建新版本: 各自在事务外读到同一个 latest.version、算出同一个 nextVersion → `Unique constraint failed on the fields: (patient_id, version)`。 实测那一轮 CLI 挂 688 个、**定时任务自己挂 1,604 个**(它伤得更重)。 BullMQ processor 内部按 patient_id 串行,挡得住自己人,挡不住外面另起的 CLI 进程。 上一轮旧代码没撞,只是因为它在 2 点前就被我杀了 —— 运气,不是设计。 修法:两条写路径进事务先拿**按患者的 advisory 事务锁** (`pg_advisory_xact_lock(hashtext(patientId))`,随事务自动释放、跨进程有效); 建版本那支再**在锁内重读**最高版本 —— 迟到者看到已经变大的版本号,顺序叠上去。 就地刷新那支也拿同一把锁,否则可能写到刚被并发写者 supersede 的版本上。 ## 验证 本地真库对照:取同 300 个患者、删掉其画像(逼两个进程都算出 version=1)、两进程并发 --force 旧代码 A: success 18 failed 216 | B: success 282 failed 18 → **234 次 unique 冲突** 新代码 A: success 186 failed 0 | B: success 300 failed 0 → **0 次冲突** 498 单测通过(36 suites),tsc 干净。persona-watermark.spec 新增 4 例锁住: 建版本前必拿锁 / 版本号锁内重读 / 就地刷新那支也拿锁 / noop 不白拿锁。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed