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

5.7 KiB
Raw Blame History

动态调整计算节点 CPU 核数(并发槽位)设计方案

状态:已实现effective_max_slots + 心跳响应下发配额 + /api/admin/nodes/:node_id/quota + 节点详情管理弹窗)。下文为设计原文,保留作实现参照;实际实现以 crates/node/src/worker.rscrates/server/src/api/admin.rsdashboard/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
    {
      "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. 动态调整并发控制逻辑
    • 心跳线程在收到心跳响应后,计算生效配额并更新:
      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)主循环的条件相应修改为:
      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(占用/配额/最大),其中“最大”代表节点 .envDCTS_MAX_SLOTS 设置的运行使用上限。
    • 修改配额 (CPU 核数):提供表单供输入新的配额数字,调用 /api/admin/nodes/:node_id/quota 保存;或提供“清除配额”恢复运行使用的最大核数上限。
    • 节点调度控制:提供“暂停(停用)/启动(启用)节点”的操作按钮。
    • 安全管理:提供“重新颁发 Token”的操作按钮。

4. 方案优势

  1. 平滑降级:不中断正在运行的 TLUSTY 任务,实现优雅缩容。
  2. 网络高效:利用现有的高频心跳链路顺带下发配置,不增加额外的 RPC 接口,且避免了服务端阻断 claim 带来的无效轮询。
  3. 职责清晰Server 依然是状态中心,Worker 仍旧掌控并发调度,逻辑解耦。