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 一律写「远程服务器」。
82 lines
6.7 KiB
Markdown
82 lines
6.7 KiB
Markdown
# 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 §六 同一处理)。
|