Files
dsh_shenxian/dsh-server-docs/04-调整方案/84-实例内存配额口径统一-消灭388误报.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

7.9 KiB
Raw Blame History

84-实例内存配额口径统一(消灭「388 MiB」误报)

2026-09-13 落地 | 触发:用户问「实例内存大小是不是调整过了,为什么功能设置中还是显示 388 多」 定性:同一事实被复制成两份、只改了一份 —— 属于本项目反复出现的「两处副本必然漂」类缺陷。

一、现象与实测(先取证,再动手)

「dsh 设置 → 功能管理」顶部那条内存条显示 勾选后 388 MiB 并报 ⚠ 将超出上限。

实测真实配额(systemctl show <scope> -p MemoryMax,字节换算):

实例 装载的重型插件 真实 MemoryMax 验算
guest(uid 100002) dsh-univer-office 704643072 = 672 MiB 160 + 512 ✓
admin(uid 114801) dsh-plugin-mcn-suite 402653184 = 384 MiB clamp(160+128)=384(踩下限)✓

V8 堆两边都 = min(256, 配额−96) = 256 MB。⇒ 配额确实早就改过了,且是真生效的。

二、根因:两套模型从未对齐

项 平台侧(决定真实 MemoryMax) 插件前端(决定界面显示)
位置 src/supervisor/orchestrator.ts poc/business-plugins/lib/client.js
基线 BASE_MEM_MB = 160 MEM_BASE_MIB = **285**
dsh-univer-office 512 64
dsh-plugin-mcn-suite 128 39
合成规则 clamp(160+Σ, **384**, 1024) 285 + Σ,并把 384 当硬上限判超限
「上限」 384 只是下限,硬顶是 1024 MEM_LIMIT_MIB = 384(档案 58 时代的写死值副本)
  • 388 的来源:候选池恰好只有这 2 个插件(实调 /api/plugins/business 确认),前端表对两者都有值 ⇒ 全勾 = 285+64+39 = 388。而平台侧同一情形 = 160+512+128 = 800。
  • 误报机制:388 > 384 触发「将超出上限」红色警告;但 384 在平台侧早已只是下限 ⇒ 纯误报。
  • 为什么插件只能自估:/api/dsh/status 不返回任何内存字段(只有 running / instance / watchdog / breaker / url)⇒ 插件无从得知真值。
  • 漂移时点:档案 81 R1 把「写死 384」改成「按插件集合推导」时,只改了编排器;插件那份副本原地未动,此后所有改动(univer 384→512、mcn-suite 128)都没同步。

三、改法(两步,一次做掉)

① 平台侧:把已算好的真值透出来(零新增计算)

文件 改动
src/supervisor/spawner.ts Spawner 接口新增可选方法 quotaInfo?(userId): { memMb; heapMb } | null(照 breakerInfo? 的模式)
src/supervisor/orchestrator.ts 实现 quotaInfo() —— 调同一个 instanceMemMb() / heapMbFor(),读不到返回 null
src/web/routes/dsh.ts /api/dsh/status 增加 quota: app.supervisor.quotaInfo?.(request.user!.id) ?? null

⇒ 因为走的是 spawn 时用的同一函数,不可能再与实际 MemoryMax 对不上。

② 插件侧:改读真值 + 对齐规则 + 修正超限判据

  • 常量与规则逐项对齐编排器(MEM_BASE_MIB 160 / MEM_MIN_MIB 384 / MEM_MAX_MIB 1024 / MEM_HEAP_HEADROOM_MIB 96 / MEM_TABLE{univer:512, mcn:128}),并新增 quotaOf() / rawMiB() / heapMiBFor() 与编排器同构。
  • load() 并行取 /api/dsh/status(失败静默,只影响内存条),把 quota.memMb/heapMb 作为事实行直接显示:实际配额 384 MiB · V8 堆 256 MiB;读不到才退回估算并如实标注。
  • 超限判据改为「原始需求 > 硬顶 1024」 —— 旧版拿 clamp 后的值判,既误报、又在语义上不可能触发(clamp 后永远 ≤ MAX)。
  • 未进成本表的插件不再显示 ≈ 0 MiB 徽章(平台口径「未识别按 0 计」,显示 0 只会制造噪音)。
  • 文案:上限→硬顶、将超出上限→顶到硬顶,overWarn 明确写出「1024 MiB 硬顶」。
  • 版本 0.3.6。

③ 钉死(关键):新增 scripts/verify-mem-model.mjs

根因是「同一事实两处副本」,所以修法必须包含「让它不可能再漂」。 脚本解析编排器与插件的常量/成本表/clamp 算式/接线点,逐项交叉断言,并接进 npm run verify: 常量不一致、成本表任一键不一致、clamp 算式不同构、quotaInfo 接口/实现/路由任一环缺失、插件不再读 status —— 任一命中即构建失败。

反向验证(已做):把插件的 MEM_BASE_MIB 改回旧值 285 ⇒ 脚本报 ✗ 常量一致 · 基线 BASE -> 编排器 160 / 客户端 285、退出码 1;还原后全绿。 (说明这道闸门真的会拦,而不是摆设。)

四、验证记录

层 证据
编译 npm run build(tsc)零报错
单测 npm run verify 全绿 —— 含新增的 15 项模型交叉断言
平台实装 服务器 /opt/dshs/lib/supervisor/orchestrator.js、lib/web/routes/dsh.js md5 与本机编译产物一致(差异已核:orchestrator 正好 19 行 = 我的改动;dsh.js 3 行 + 1 行他人旧的注释)
接口 GET /api/dsh/status → quota: {"memMb":384,"heapMb":256},与实测 MemoryMax=402653184 吻合
插件实装 admin/guest 两 profile 均 0.3.6;lib/client.js md5 86bc8a46… 与本地产物一致;旧符号 MEM_LIMIT_MIB 0 处
端到端 冷启动取下发 bundle(rev=a8eb3ecf1edb,4.30 MB):MEM_BASE_MIB=160、MEM_MAX_MIB=1024、univer 成本 512、读 status 6 处、旧符号 0 处

修复后界面应有的样子(admin 为例):当前 384 MiB → 勾选后 800 MiB + 实际配额 384 MiB · V8 堆 256 MiB, 状态 余量充足(800 < 1024)—— 388 与红色误报都不再出现。

五、事故/踩坑记录

  1. 并发撞车(本次最该记住的一条):poc/business-plugins/lib/client.js 在我作业期间被并行会话连续改了两轮(0.3.4「系统管理改为门户 6 功能页」、0.3.5「插件管理列表高度」,0.3.5 已部署)。因为每次 Edit 前都重读了文件,我的改动是叠在它们之上、没有覆盖;但版本号我以为是 0.3.3、实际已是 0.3.5 ⇒ 我第一把 npm pack 打出了「内容是别人的 0.3.5 + 我的修改、版本号却写 0.3.5」的同名假产物(已删除),改判为 0.3.6。 ⇒ 纪律:动手前先 git status + 查最新 tgz 版本号;版本号取「当前值 +1」,不要用记忆里的值。
  2. 未提交区已被多方污染:该仓 git status 同时有他人未提交的 0.3.4/0.3.5(已上线但未 commit)。 ⇒ 本次只提交我自己的文件,poc/business-plugins/lib/client.js 与其 package.json 保持未提交、留给他人的批次一起提交(避免把别人在途改动「顺手上锁」)。
  3. server 侧 dsh.js 比仓库旧一行注释(/desktop.html→/portal.html)—— 说明服务器编译产物整体略滞后于仓库;本次顺手带上,无行为影响。

六、回滚

  • 平台:/opt/dshs/.bak-20260913-memmodel/{orchestrator.js,dsh.js} 拷回原位 → systemctl restart dshs.service
  • 插件:/opt/dsh/artifacts/ 保留历代 tgz(0.3.5 仍可取)→ node /opt/dshs/scripts/ensure-biz-plugins.cjs --all --restart 指定旧版
  • 校验脚本:删掉 package.json verify 链末尾的 && node scripts/verify-mem-model.mjs 即可(脚本本身独立,不影响运行)

七、后续(未做,非阻塞)

  • 若成本表再加插件:只改编排器 + 跑 verify 会失败 ⇒ 逼你同步插件侧(这就是设计意图)。若将来希望彻底「只维护一处」,可考虑由 /api/dsh/status 或新增只读接口下发成本表本身,插件不再内置副本。
  • business-plugins 前端那条内存条现在展示的是配额(cgroup 上限)而非实时占用;若要显示真实 RSS,需要平台侧新增采样接口(当前未做,文案不声称是实时值)。