- 12 Oct, 2026 2 commits
- 10 Oct, 2026 12 commits
- 09 Oct, 2026 8 commits
- 08 Oct, 2026 4 commits
- 06 Oct, 2026 2 commits
- 21 Sep, 2026 2 commits
-
-
luoqi committed
-
FRIDAY 宿主接入测试机后,点「确认分配」必 500: POST /pac/v1/plans/assignments → 42846 cannot cast type integer[] to timestamp with time zone[] at plan-assignment.service.ts:392 ($transaction 内) 机理:Prisma 从**值**推断数组元素类型。`aDedicatedCsLastVisit`(该患者专属客服的 最近到诊时间)整个数组非空且全是 null 时推不出来,退化成 integer[],撞上显式 ::timestamptz[]。
⚠ ️ 空数组 [] 反而安全(走不到转换),**只有「非空且全 null」才炸**。 为什么瑞尔一直没事:那边有回访历史,多数患者有专属客服 → 数组里总有非 null。 FRIDAY 是新宿主、无回访历史 → 整批全 null。 ⇒ **这不是 FRIDAY 特有的**,瑞尔只要碰上「这批人恰好都没有专属客服」一样炸。 修法:参数以 ISO 文本传,再整体 ::text[]::timestamptz[]。三种形态(全 null / 混合 / 全有值)都在测试机实测通过。不动 t(...) 的列名与顺序 —— 九个数组的位置 对应关系很脆,那里原本就有一条警告。 实测哪些目标类型会塌(2026-09-21 测试机): ::timestamptz[]❌ ::uuid[]❌ ::text[]✅ ::double precision[]✅ ::int[]✅ ⇒ 同段的 aDedicatedCs(::text[])、aSourceConfidence(::double precision[]) 虽然也可空但**没病**,没有顺手改 —— 测试里专门锁了这一点。 测试:新增仓库级守卫 —— 凡 ${x}::timestamptz[] / ::uuid[] 且 x 声明含 `| null` 即判失败,覆盖全 src 而不只是这一处。5 项变异全部被咬红,其中一项 (toContain('toISOString()'))第一版没牙、被变异测试当场抓到后已收紧为只锁那一行。 全量 1979 passed。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>luoqi committed
-
- 05 Sep, 2026 10 commits
-
-
量错了指标。mem_limit 管的是 cgroup 的 memory.current,而我拿 `docker stats` 和进程 rss 当依据,三者差 1.6 倍: cgroup memory.peak = 4760 MiB ← mem_limit 真正管的数(58% of 8192) docker stats 采样 = 3.53 GiB ← 采样,峰值漏在间隔里(我 10 分钟一采) 进程 rss 峰 = 2936 MB ← 不含 page cache 差额是 page cache + 堆外分配。8g 仍然站得住(且 4760 里相当部分是可回收的 page cache —— 跑完后 file 段只剩 2 MiB),但余量是 1.7 倍不是 2.8 倍。 正确量法写进注释:`cat /sys/fs/cgroup/memory.peak`,并注明高水位随容器重建清零。 同时这个数推翻了「堆上限 6144 ⇒ V8 一定先抛」的说法:cgroup 高水位比堆峰值 多出约 2.2G,堆若真涨到 6144,总量会逼近 8192,谁先触发取决于当时可回收的 page cache 有多少。改成如实写"大概率而非硬保证",并给出想要硬保证时该取的 值(5120)和它的代价。6144 保持不变。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
luoqi committed
-
加了 mem_limit(8g)后,堆上限还留在 8192 会形成一个坏顺序:RSS 恒大于堆 (实测 rss=2873MB 时 heapTotal=2309MB,堆外常驻约 560MB),两者相等时 **一定是先撞 cgroup 被 SIGKILL —— exit 137,零日志零堆栈**,而不是 V8 抛 `Reached heap limit`(带 JS 堆栈,能直接定位是哪段吃的)。 等于把唯一一次能拿到现场的机会浪费掉,而 09-04 整件事就是败在没有现场。 6144 的依据是生产实测,不是拍脑袋 —— 09-05 10:15 那轮(分批改造上线后第一轮 干净数据,550,481 召回池):峰值 heapUsed=2437MB(批 15),之后单调降到 402MB; 峰值 rss=2873MB。6144 相对峰值留 2.5 倍;最坏 RSS ≈ 6144+600 ≈ 6.7G < 8G ⇒ V8 先抛。 不影响 CLI:命令行 --max-old-space-size 覆盖 NODE_OPTIONS,已实测 (NODE_OPTIONS=6144 + 命令行 8192 → heap_size_limit 8240MB)。 顺带改准两处已经和事实对不上的注释: · 「观测峰值 6.03G,留 33% 余量」是分批改造**之前**的旧数,实测只用到 36%(2.8 倍余量); 写明以后调这个数必须重新量,别再拿 6.03G 当依据。 · 「与 8192 相等是刻意的过渡态」—— 过渡态已结束,改成说明为什么堆上限必须低于 mem_limit。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
luoqi committed
-
luoqi committed
-
补摄 / recompute-plans 一直是 `docker exec pac-pac-service-1 node …`, 同一容器 = 同一 cgroup。加限制前服务 4G + CLI 4.9G 在 15G 宿主上撑得住; 加限制后两者之和过 8G,内核会在 cgroup 内部挑一个杀 —— 可能杀的是服务。 规则本来就写着"别并发跑全量 CLI",但违反的代价从「也许没事」变成「必然被杀」, 必须写在限制旁边而不是别处。 给出的替代配方(单开带自己上限的容器)已在测试机实跑验证: 新容器 memory.max 是自己的值、连库正常、服务那 8G 分母不受影响。 同时改掉两处会让人照着做不通的细节 —— CLI 是 dist/cli/<名字>.cli.js, 以及 DATABASE_URL 在托管/自带 DB 两种形态下的差别。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
luoqi committed
-
luoqi committed
-
外部探针解决了「pac-service 死了没人知道」,但它自己也会死:测试机宕、cron 被删、 脚本改坏、/etc/pac-health-watch.env 丢失 —— 这些情况下探针**静静地停掉**, 而所有人以为它还看着。这是 09-04 的同款失败换个位置又出现一次。 每天固定时刻(默认 9 点,WATCH_HEARTBEAT_HOUR=-1 关)推一条「我还活着 + 过去一个周期探了几次/失败几次」。刻意排在生产每日健康报告(09:07)旁边: 两条都到 = 两侧都活;只到一条 = 立刻知道断的是哪一侧。 两个实现细节: · 心跳判据是「今天还没推过 且 已到点」,不是「分钟正好相等」—— cron 可能因负载/重启错过某一格,按分钟判会整天不推,而漏推 = 误报"探针死了"。 · 状态文件的每个数值字段都做正则校验。它是被 source 的,写坏一次(磁盘满/并发) 会让后面的算术直接语法错退出 → 探针静默死亡,正是本脚本要防的那类失败。 已用"塞垃圾进状态文件"的用例验证能自愈。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed -
现状:sync-incremental.scheduler 不注入 AlertService。轮次失败只 logger.error 就吞掉(`:38` 注释「跑失败:log ERROR 不抛」),唯一出口是次日 09:07 的每日健康 报告 —— 最坏晚 24 小时。09-04 生产宕 11.8 小时全程零告警,有这一份原因。 而刚加的 compose mem_limit(8g)会把下面这条路径变成常见路径: 容器被 SIGKILL → 轮次失败 → HTTP 30 秒内恢复 → 外部探针一路绿 即**系统在稳定地失败,而所有指示灯是绿的**。不补这三条,新加的兜底反而 制造了一种更隐蔽的故障。 三条信号: 1. 回收到僵尸锁 → warning。这是「上一轮没正常终结」的**唯一确定性信号**, 比"轮次失败"更早更准:失败可能只是网络抖动,留下僵尸锁则意味着进程被 外力打断、没走到 finally。正文直接给 CONSTRAINT_MEMCG 的查法,并写明 别看 docker inspect 的 ExitCode(restart:always 下那是重启后那条命)。 2. 轮次抛异常 → critical,带错误原文。 3. 套圈跳过 → warning。这是 08-29 雪崩的前兆、也是 09-04 的中间态 —— 当时 11.8 小时里每轮都在这里 return,防套圈闸救了系统却**吞掉了症状**。 告警统一走 alertSafe:推送失败绝不能反过来打断同步。 测试:5 个新用例,并做了变异测试 —— 逐条拆掉告警 / 拆掉 try/catch, 四次变异全部被咬红,不是走过场的绿。全量 1975 passed。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luoqi committed
-