Files
dsh_shenxian/dsh-server-docs/04-调整方案/96-实例配额与插件开关解耦.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

82 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 96-实例内存:与插件开关解耦,改为「基础 MIN → 最多 MAX」(2026-09-14 落地)
- 日期:2026-09-14 | 状态:✅ **已上线**(平台后端已重建重启;插件 **0.3.19** 已铺两实例)
- 触发(用户原话,两段):
1. 「**改为 实例内存不要受插件开关影响,只受 min 和 max 值影响**」
2. 「**改成基础 min 最大可以浮动到 max**」
> **TL;DR**
> 平台侧 = cgroup **两个参数**:`MemoryHigh = MIN = 448 MiB`(**基础/软限**,超过即回收·限速)
> + `MemoryMax = MAX = 1024 MiB`(**上界/硬限**,越界 OOM)⇒ 实际占用在两者之间浮动。
> **与插件开关无关**(不再读 profile 的 bundles)。实测两 scope 均 `High=448M / Max=1024M`。
> ⚠️ **勘误(同日,用户两次纠正,都记着)**
> ① 我第一版把它写成「**固定配额 = 512 MiB**(用户裁定)」——**两处都不是用户说的**:
> 用户是在界定**谁可以影响配额**,不是给"固定"背书,更没指定 512(那是我擅自把 MIN 从 448 抬上去的)。
> ② 我第二版又写成「只受 MIN/MAX 决定 ⇒ 一律 448」,同样是我自己加的解读;
> 用户随后明确:**基础是 MIN,最多可以浮动到 MAX**。
> ③ ⚠️ 我还**凭记忆编过一条"之前的实现一直是 D(在 min/max 之间按用量浮动)"** ——
> 用 git 查证后**不成立**:全仓+全历史 `grep MemoryHigh` = 0;今天早上(`1d72e8f` 09-14 04:52)的原文是
> `clamp(BASE 160 + Σ插件表, MIN 384, MAX 1024)`,cgroup 只给 `MemoryMax=<单值>`;更早那版是「**写死 384**」。
> **教训:不确定的历史事实必须 git 取证,别用"应该/一直"作答。**
---
## 一、为什么改(两段历史)
| 时间 | 设计 | 结果 |
|---|---|---|
| 更早 | `MemoryMax` **写死 384** | guest 启用 univer(gateway ≈390 MB)后撞死 ⇒ cgroup OOM kill 死循环 |
| 2026-09-13(档案 81 · R1-④) | 配额 = `基准 + Σ(插件内存预估)`,clamp [MIN, MAX] | 修了 univer 那条,但引入三个软肋 ↓ |
| **2026-09-14(档案 96)** | **基础 MIN → 上浮上界 MAX**(两个 cgroup 参数),**与插件无关** | 本次 |
**档案 81 那版的软肋(实测)**
1. **未知插件按 0 计** ⇒ 装了重插件也不加配额;
2. **表本身偏乐观**:admin 带 mcn 后**峰值 409 MiB**,当时上限 **384** ⇒ **一分钟内被 cgroup OOM 杀 4 次**
(`journalctl -k`:`oom-kill:constraint=CONSTRAINT_MEMCG` + `oom_memcg=/system.slice/dsh-114801-*.scope`),
打出 crash 熔断(`/var/log/dsh-crash-breaker.log` 20:04:53);
3. **语义混乱**:配额随「开关插件」跳动 —— 用户指出「**开关插件只是显示给用户看内存占用的预估,怎么会实际影响实例内存**」。
## 二、改了什么
| 层 | 文件 | 改动 |
|---|---|---|
| 后端(权威) | `src/supervisor/orchestrator.ts` | `instanceMemMb()` → 拆成 **`instanceBaseMb()`**(= MIN,`Math.max(128, …)`)与 **`instanceMaxMb()`**(= max(base, MAX));`sdArgs` 改为 **`-p MemoryHigh=<base>M` + `-p MemoryMax=<max>M`**;**删除** `PLUGIN_MEM_MB` 与 `BASE_MEM_MB`(不再读 profile 的 bundles);`quotaInfo()` 返回 **`{baseMb, memMb, heapMb}`**(`memMb` = 上界);`MIN_MEM_MB = 448` / `MAX_MEM_MB = 1024` 保持用户裁定值 |
| 接口 | `src/supervisor/spawner.ts` | `quotaInfo?()` 的返回类型同步加 `baseMb` |
| 客户端(显示) | `poc/business-plugins/lib/client.js` | `quotaOf()` 兜底值改为**硬顶上界**(进度条 100% 基准与「超限」判据同它);事实行改为 **「基础 448 MiB → 最多 1024 MiB · V8 堆 256」**(新增 i18n `mem.base`);`MEM_TABLE`/`MEM_BASE_MIB` **保留但仅用于预估**;`MEM_MIN_MIB` 注释改为「基础值(`MemoryHigh` 软限)」 |
| 校验 | `scripts/verify-mem-model.mjs` | 反回退断言换成新形态:**必须有 `instanceBaseMb()`/`instanceMaxMb()`**、**cgroup 必须给 `MemoryHigh=base` + `MemoryMax=max`**、两访问器函数体内不得读 bundles、客户端 `quotaOf()` 必须返回上界、`mem.base` 必须存在;MIN/MAX/HEADROOM 两处仍逐值一致 |
| 版本 | `poc/business-plugins/package.json` | 0.3.17 → 0.3.18 → **0.3.19** |
## 三、验证
| 层 | 手段 | 结果 |
|---|---|---|
| 单测 / 构建 | `node --test`、本机 `build`/`typecheck` | ✅ 48 pass / 0 fail;✅ |
| 本机全链 | `npm run verify` | ✅ **EXIT=0 全绿** |
| 服务器 | `bash scripts/ci.sh` | ✅ CI OK(48 pass / 0 fail) |
| 真机接口 | 临时 session(R4)→ `POST /api/dsh/enter` + `GET /api/dsh/status` | ✅ 两用户均 `{"baseMb":448,"memMb":1024,"heapMb":256}`、`running:true` |
| **内核生效值**(最强) | `systemctl show -p MemoryHigh -p MemoryMax dsh-*.scope` | ✅ `dsh-100002-*` 与 `dsh-114801-*` **均 `High=448M / Max=1024M`**,当前用量 254 / 278 MiB |
| 插件版本 | 两 profile 的 `dependencies` | ✅ 均 `business-plugins-0.3.19.tgz` |
| 拼接产物 | 抓浏览器真正拿到的 bundle 跑 `node --check` | ✅ 语法通过(含新代码) |
## 四、⚠️ 代价 / 注意
- **`MemoryHigh` 是"开始回收与限速"的软限**:实例稳态若长期高于 448 MiB,会频繁触发直接回收(性能下降)而不一定报错 ——
所以「基础 448」是否够用,要看 `/var/log/dsh-instance-mem.log` 的**当前/峰值**(当前 254–278 MiB,正常)。
- **装重插件(`dsh-univer-office`,gateway ≈390 MB + 基座 ≈312)会长期超过基础**:此时它会靠上浮到 1024 才能活;
若峰值逼近 1024 仍会 OOM ⇒ 那时该抬的是 **MIN 或 MAX**(用户定),**不再靠插件表**。
- 宿主 1870M:`maxIdleInstances=4` × Max 1024 = 4096M 远超宿主 ⇒ **并发上限仍受宿主约束**(上限非预留,但真跑满会换页)。
## 五、回滚
- 代码:`git revert <本次 commit>`(或把 `sdArgs` 还原成单个 `MemoryMax=<值>`)→ `npm run build` → `systemctl restart dshs`;
插件侧删 `/opt/dsh/artifacts/business-plugins-0.3.19.tgz` 再跑 `ensure-biz-plugins.cjs --all --restart`。
- 服务器备份:`/opt/dsh/backups/pre-97-20260914-222146/`(`orchestrator.ts` / `spawner.ts` / `verify-mem-model.mjs`)。
- 无数据迁移、无 DB 变更。
## 六、文档口径收敛(同批做的)
`BRIEF.md §隔离`、`DEPLOY-本部署.md`(systemd-run 行)、`skills/dsh-instance-diagnose`(配额单元格 + 诊断命令示例)、
`skills/dsh-change-workflow`(配置风险 / cgroup 描述 / 并发上限三处)全部改为「**基础 448 → 上界 1024**(`MemoryHigh`/`MemoryMax`)」。
⚠️ `BRIEF.md` 与 `DEPLOY-本部署.md` **本就有别人未提交的改动**,故**未提交**(与档案 95 §六 同一处理)。