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 笔误
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# 动态调整计算节点 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 仍旧掌控并发调度,逻辑解耦。
|
||||
Reference in New Issue
Block a user