Files
DCTS/docs/dynamic_cpu_slots_design.md
T
fmq cd370d88e7 feat(all): 任务引擎双阶段解耦、僵尸涡旋修复、动态 CPU 配额与前端详情页重构
将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。

引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
  normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
  trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
  force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
  (mark_grid_point_running 仅 pending/queued→running;
  record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照

僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
  活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复

动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
  缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效

科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
  场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)

前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
  pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画

文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
  光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
2026-08-04 23:40:52 +08:00

77 lines
5.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.
# 动态调整计算节点 CPU 核数(并发槽位)设计方案
> **状态:已实现**`effective_max_slots` + 心跳响应下发配额 + `/api/admin/nodes/:node_id/quota` +
> 节点详情管理弹窗)。下文为设计原文,保留作实现参照;实际实现以 `crates/node/src/worker.rs`、
> `crates/server/src/api/admin.rs`、`dashboard/src/components/nodesTable.js` 为准。
## 1. 需求背景
目前 DCTS 的计算节点 (Worker) 在启动时通过本地配置确定最大计算槽位 (`max_slots`),并在心跳和注册时上报给服务端。如果在不重启 Worker 进程的情况下,因为设备资源被其他用户借用等原因,需要临时减少该节点分发的任务数(即缩减 CPU 核数/并发槽位)。
## 2. 现有架构分析
- **Pull 模式**:任务的领用是由 Worker 主动发起 `POST /api/task/claim` 的。Worker 内部根据自身的 `max_slots` 控制并发领用的数量。
- **状态同步**Worker 定期发送 `POST /api/node/heartbeat` 汇报当前的 `active_slots`(正在运行的任务数)和资源负载。
因为是 Pull 模式,如果在服务端强行拒绝 `claim`(例如当节点的活跃任务超过管理员设定的配额时返回空),会导致 Worker 不断进行无效的轮询(Busy Loop),浪费网络和日志资源。因此,最优解是**让 Worker 能够感知到服务端下发的配额上限,并动态调整其本地的并发控制器**。
## 3. 设计方案:基于心跳响应的配额下发机制 (Heartbeat-based Quota Sync)
### 3.1 数据库与服务端改造
1. **DB 结构更新**
在服务端的 `nodes` 表中新增一个可空字段 `admin_max_slots` (INTEGER, Nullable),用于保存管理员强制指定的并发上限。
2. **新增管理 API**
- 接口:`POST /api/admin/nodes/:node_id/quota`
- 权限:Admin 角色
- 请求体:`{"admin_max_slots": 4}`(传 `null` 表示解除限制,恢复节点的物理槽位上限)
3. **心跳响应扩展**
修改 `POST /api/node/heartbeat` 的响应结构,将 `admin_max_slots` 的值透传给 Worker
```json
{
"status": "ok",
"admin_max_slots": 4 // 如果无限制,则为 null
}
```
*注意*:还需在 `crates/common/src/models.rs` 中为 `NodeInfo` 增加 `pub admin_max_slots: Option<i32>` 字段,确保 `/api/admin/nodes` 返回配额数据供 Dashboard 渲染。
### 3.2 Worker 节点改造
1. **解析心跳响应**
目前 Worker 的心跳线程(`crates/node/src/worker.rs`)忽略了响应体。需要在 `crates/common/src/models.rs` 中定义 `NodeHeartbeatResponse` 结构体,并在 Worker 中反序列化,提取 `admin_max_slots`。
2. **状态共享**
Worker 并非使用 `Semaphore` 控制并发,而是依赖 `Arc<AtomicI32>` (`active_slots`) 计数。我们需要在 `NodeWorker` 的状态中新增一个 `effective_max_slots: Arc<AtomicUsize>`,以便心跳线程和领用线程共享配额信息。
3. **动态调整并发控制逻辑**
- 心跳线程在收到心跳响应后,计算生效配额并更新:
```rust
let target_slots = match admin_max_slots {
Some(admin_limit) => std::cmp::min(admin_limit as usize, physical_max_slots),
None => physical_max_slots
};
self.effective_max_slots.store(target_slots, Ordering::Release);
```
- 任务领用(Claim)主循环的条件相应修改为:
```rust
let active = self.active_slots.load(Ordering::Acquire);
if (active as usize) < self.effective_max_slots.load(Ordering::Acquire) {
// ... 领用任务 ...
} else {
tokio::time::sleep(Duration::from_secs(2)).await;
}
```
- **平滑降级**:如果缩减时当前正在运行的槽位大于 `target_slots`,上述 `if` 判断会直接失败,Worker 自然进入 `sleep` 分支待命。无需强行中断物理计算任务,等现有任务结束后,数量便会自动回落到新配额以下,实现安全平滑降级(即便配额设为 0,也会安全地暂停接收新任务)。
### 3.3 前端控制台 (Dashboard) 改造
1. **保持表格原状**
外部节点列表的大表维持原状(“CPU 槽位”列仍然显示 `占用 / 最大`,不作修改,保持界面整洁)。
2. **重构操作列交互(收敛至统一弹窗)**:
- 将“管理操作”列中原有的“启用/停用”、“重发 Token”等多个离散按钮,统一替换为一个**三个点 (More Options) 的 SVG 图标按钮**。
- 点击该按钮后,利用系统中现有的弹窗样式 (如 `.modal-card`) 弹出一个**节点详情与管理 Modal**。
3. **节点详情管理弹窗内容**
- **基础信息展示**:显示节点的详细状态、心跳时间、资源使用率等。
- **槽位占用情况展示**:如果没有配额限制,显示格式为 `16/16`(占用/最大);如果有配额限制,显示格式为 `12/12/16`(占用/配额/最大),其中“最大”代表节点 `.env` 中 `DCTS_MAX_SLOTS` 设置的运行使用上限。
- **修改配额 (CPU 核数)**:提供表单供输入新的配额数字,调用 `/api/admin/nodes/:node_id/quota` 保存;或提供“清除配额”恢复运行使用的最大核数上限。
- **节点调度控制**:提供“暂停(停用)/启动(启用)节点”的操作按钮。
- **安全管理**:提供“重新颁发 Token”的操作按钮。
## 4. 方案优势
1. **平滑降级**:不中断正在运行的 TLUSTY 任务,实现优雅缩容。
2. **网络高效**:利用现有的高频心跳链路顺带下发配置,不增加额外的 RPC 接口,且避免了服务端阻断 claim 带来的无效轮询。
3. **职责清晰**Server 依然是状态中心,Worker 仍旧掌控并发调度,逻辑解耦。