跳转至

SOP — sync_status 并发化(远程封装上提 + 批量探测)

主题目录:sync_status_concurrency_20261007/|执行日期:2026-10-07|对应挂账项:HANDOFF §3 #19

1. 背景

sync_status(六阶段生命周期的第 2 步,幂等状态巡检)此前有两个累积问题:

  1. 远程封装重复实现:_remote_query / _remote_exec / remote_query / remote_exec / add_change / _remote_sha256 在 9 个部署器文件里各自重复定义,内容高度雷同(仅异常处理与超时默认值略有出入),基类 BaseDeployer 里一个都没有。
  2. 探测逐条串行建连:utils/paramiko_module.exec_linux(host, [cmd]) 每调用一次就新建并关闭一次 SSH 连接。巡检按「逐主机、逐条目」串行下发,slurm 主部署器 6 台 × 约 8 条探测 ≈ 48 次连接;2026-10-07 实测 --dry-run 墙钟 247.7 s,其中相当部分是握手往返,而非命令本身。

ops-execution-efficiency §2 的口径是:要降的是会话数(握手数),不是命令条数 —— 同一主机的多个只读检查应合并成一次会话。

2. 目标

# 目标 判据
1 远程封装统一上提到 BaseDeployer,消除 9 处重复 子类中不再自定义这些方法(scratch_gc_deployer.py 的 remote_query 因基于自有三态 _query 而有意保留)
2 新增 _remote_query_many(plan):按主机分组,每台一次连接串行跑该主机全部命令,主机之间并发 离线自测:分组、顺序映射、异常降级、长度对齐全部通过
3 slurm / health_ensure / user_mgmt 三个部署器的 sync_status 批量化 变更记录逐条一致(语义等价)+ 墙钟下降

3. 前置条件

  • 项目根 /home/rain/Documents/slurm/slurm_ins,Python 虚拟环境 .venv 可用;
  • 到集群 6 台节点的 SSH 凭据(沿用既有 config/slurm_deploy.yaml,权限 600,凭据不入文档);
  • 变更前完成第 7 章的备份。

4. 影响范围

项 内容
变更对象 deployer.py(基类)与 9 个 *_deployer.py,共 10 个文件
改的是什么 只改 sync_status 的探测方式(逐条建连 → 批量并发);判定规则、变更清单生成逻辑、deploy/backup/health_check/rollback 一律不动
是否停服 否。验证全程 --dry-run,只有只读探测
窗口期 无要求
在跑作业 无

5. 分步操作

5.1 备份(高危前置)

改前对 10 个文件做整份快照,落到本主题 backup/slurm_ins/(台账见第 7 章)。

cd /home/rain/Documents/slurm/slurm_ins
mkdir -p sync_status_concurrency_20261007/backup/slurm_ins
STAMP=$(date +%Y%m%d-%H%M%S)
for f in deployer.py slurm_deployer.py submit_node_deployer.py node_env_deployer.py \
         module_env_deployer.py user_mgmt_deployer.py health_ensure_deployer.py \
         scratch_gc_deployer.py local_scratch_deployer.py storage_layout_deployer.py; do
  cp -a "$f" "sync_status_concurrency_20261007/backup/slurm_ins/$f.bak.$STAMP"
done
ls -1 sync_status_concurrency_20261007/backup/slurm_ins/   # 预期 10 个 *.bak.<STAMP>

坑:备份须在任何编辑之前完成;本主题后续两次上机对照都依赖这份旧版快照。命名按 <原名>.bak.<YYYYmmdd-HHmmss>,实际 STAMP = 20261007-041741。

5.2 基类上提远程封装

在 deployer.py 的 BaseDeployer 内新增(实现体:deployer.py 的「远程执行封装」段):

  • _remote_query(host, cmd):只读查询,失败按空串处理(探测型查询不中断流程);
  • _remote_exec(host, cmd, timeout=600, require_zero=True, label=""):变更类执行,dry-run 只记录,非零退出抛 DeployError;
  • remote_query / remote_exec / add_change:无下划线别名,供 *Installer 按宿主方法名调用;
  • _remote_sha256(host, path);
  • _remote_query_many(plan, max_workers=8):新增能力,plan = {host: [cmd1, cmd2, ...]},返回同构 {host: [out1, out2, ...]}。

_remote_query_many 的实现要点:

# 概念等价:每台一次连接串行跑该主机全部命令,主机之间并发(Python 侧实现,非 shell)
# 单主机内:ssh <host> 'cmd1; cmd2; cmd3'      ← 一次会话
# 主机之间:ThreadPoolExecutor(并发上限 = min(8, 主机数))

坑(必须留痕):

  • exec_linux 内部用 SSHClient.exec_command,每次调用开新 channel,命令之间不共享 shell 状态;因此把 N 条命令放进一次 exec_linux(host, [c1..cN]) 与原来 N 次独立调用语义等价(已验证,见第 6 章)。
  • connection_pool 有 per-host 锁(_host_locks),主机间并发安全;同一主机在一次批量里只由一个线程处理,不会并发争抢同一连接。
  • 返回结果与入参一一对应:结果条数少于命令数时补空串,避免索引错位。
  • _remote_exec 统一签名后,原 user_mgmt / health_ensure 版本不再原样抛出 paramiko 异常,而是转成 DeployError(两者都被 run() 捕获);scratch / storage 原先默认 timeout=300,统一为 600(仅超时上限放宽,不影响正常路径)。

5.3 子类去重

删除下述重复定义(实现体:各 *_deployer.py),保留子类特有方法(如 _remote_mkdir、_remote_version、_write_remote_file、write_remote_file):

# 各文件删除清单(- 为删除)
# slurm_deployer.py         : - _remote_query, - _remote_exec
# submit_node_deployer.py   : - remote_query, - remote_exec, - add_change(继承 SlurmDeployer)
# node_env_deployer.py      : - _remote_query, - _remote_exec
# module_env_deployer.py    : - _remote_query, - _remote_exec
# user_mgmt_deployer.py     : - _remote_query, - _remote_exec, - _remote_sha256
# health_ensure_deployer.py : - _remote_query, - _remote_exec, - _remote_sha256
# scratch_gc_deployer.py    : - remote_exec, - add_change(保留 _query / remote_query,三态语义特有)
# local_scratch_deployer.py : - remote_query, - remote_exec, - add_change
# storage_layout_deployer.py: - remote_query, - remote_exec, - add_change

坑:scratch_gc_deployer.py 的 _query() 返回 (success, stdout) 三态,其 remote_query 只在命令成功时返回输出 —— 与基类语义不同,有意保留,不要一并删除。

5.4 sync_status 批量化(两阶段:批量探测 + 本地判定)

以 slurm_deployer.py 为例,把「for host: 逐条 query」改为「构造 plan → 一次 _remote_query_many → 本地判定」:

# 改造前(概念):每台每项各建一次连接
for host in <6 台>; do
  ssh "$host" "/usr/local/slurm/sbin/slurmctld --version"      # 1 次
  ssh "$host" "readlink -f /usr/local/slurm"                    # 1 次
  ssh "$host" "systemctl is-active slurmctld slurmdbd slurmd"   # 1 次
  ssh "$host" "systemctl is-active <daemon>"                    # 每 daemon 1 次
  ssh "$host" "cd <install_dir> && ldd ... | grep 'not found'"  # 1 次
done
# 改造后(概念):每台一次连接跑全部命令,6 台并发

三个部署器的批量点:

部署器 批量化内容
slurm_deployer.py 版本 / symlink / 服务概览 / 各 daemon 状态 / 运行库缺失 / slurm.conf 哈希 → 每台 1 次;HA 共享挂载探测 → 控制节点并发
health_ensure_deployer.py 健康检查脚本哈希 + slurm.conf 内容 → 每台 1 次;watchdog 脚本哈希 → 控制节点并发;timer 状态 → 主控 1 次
user_mgmt_deployer.py 5 份文件哈希 + timer enabled/active → 主控 1 次连接(原 7 次)

health_ensure_deployer.py 另新增静态方法 _health_conf_diff_text(content, desired),把「取远端内容」与「比对」拆开,供批量探测复用;原 _health_conf_diff(host, desired) 改为调用它。

坑:slurm_deployer.py 里内联的 libs_probe 命令与 _missing_runtime_libs() 同源,改一处必须同步另一处(代码中已加注释)。slurm.conf 漂移段必须先 _probe_sendmail() 再 render_slurm_conf(),顺序不可调整(mailprog_available 影响渲染结果)。

5.5 离线自测

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python -m py_compile deployer.py slurm_deployer.py submit_node_deployer.py \
  node_env_deployer.py module_env_deployer.py user_mgmt_deployer.py \
  health_ensure_deployer.py scratch_gc_deployer.py local_scratch_deployer.py \
  storage_layout_deployer.py && echo COMPILE_OK

.venv/bin/python sync_status_concurrency_20261007/selftest.py

预期:COMPILE_OK + SELFTEST_OK(8 项断言:分组/并发/顺序、异常降级、长度补空、空 plan 短路、单条查询与别名、_remote_exec 三态、add_change、子类重复定义扫描)。自测用桩函数替换 exec_linux,不连任何主机。

5.6 上机对照验证(改造前 vs 改造后)

cd /home/rain/Documents/slurm/slurm_ins
bash sync_status_concurrency_20261007/verify_baseline.sh

脚本会:把项目文件临时替换为 5.1 的旧版 → 跑 3 个部署器 --dry-run → 恢复新版 → 再跑一遍 → 对比「变更记录」逐条与墙钟,最后兜底恢复为新版。

6. 验证

验证一:语义等价(核心判据)

verify_baseline.sh 输出:

slurm         old       247.7      225       25    200
slurm         new       221.0      225       25    200
  slurm: changes IDENTICAL | wall 247.7s -> 221.0s
health_ensure old         6.5        3        0      3
health_ensure new         5.7        3        0      3
  health_ensure: changes IDENTICAL | wall 6.5s -> 5.7s
user_mgmt     old         9.7        6        0      6
user_mgmt     new         8.2        6        0      6
  user_mgmt: changes IDENTICAL | wall 9.7s -> 8.2s

三个部署器「变更记录」逐条完全一致(含总记录数、待执行数、幂等跳过数),证明改造未改变任何判定结果;slurm 主部署器墙钟 247.7 s → 221.0 s(-26.7 s,-10.8%)。

说明:slurm 的 25 项「待执行」是改造前就存在的基线(旧版同样 25 项),与本变更无关,非本次引入。

验证二:离线自测 8 项全过(见 5.5)。

失败判断:任一处 changes DIFFERENT! 或自测断言失败 → 停止,按第 7 章回滚。

7. 回滚

  • 备份位置:sync_status_concurrency_20261007/backup/slurm_ins/,台账见 sync_status_concurrency_20261007/backup/README.md。
  • 回滚步骤(把 10 个文件整份还原为改前快照):
cd /home/rain/Documents/slurm/slurm_ins
BAK=sync_status_concurrency_20261007/backup/slurm_ins
for p in "$BAK"/*.bak.*; do
  cp -a "$p" "$(basename "$p" | sed -E 's/\.bak\.[0-9]{8}-[0-9]{6}$//')"
done
  • 回滚后验证:重跑 verify_baseline.sh(此时两侧都是旧版,应报 IDENTICAL),或直接 --dry-run 确认变更清单与基线一致。
  • 回滚触发条件:任一部署器 changes DIFFERENT!;py_compile 或 selftest.py 失败;dry-run 出现新增的待执行项。

8. 风险说明

风险 缓解
批量化改变判定语义 → 误报/漏报变更 两阶段改造保持判断表达式原样;上机对照逐条比对变更记录,已证实一致
单主机一次连接跑多条命令引入状态耦合 exec_command 每次开新 channel,命令间不共享 shell;已核对 paramiko_module.run_cmd 实现
主机并发触发 sshd MaxStartups 被拒 并发上限 min(8, 主机数);本集群仅 6 台
统一 _remote_exec 后异常类型变化 user_mgmt / health_ensure 由原始异常转为 DeployError,两者均被 run() 捕获;已在 5.2 留痕
子类误删造成方法缺失 编译 + 自测 + 上机 dry-run 三重验证;scratch 的三态 remote_query 已识别并保留

附录 A 交付物清单

文件 说明
SOP.md 本文件
selftest.py 离线自测(8 项断言,桩函数替代 exec_linux)
verify_baseline.sh 改造前/后 dry-run 对照脚本
backup/slurm_ins/ 10 个改造前文件快照
backup/README.md 备份台账

附录 B 后续项(2026-10-07 复核;2026-10-08 修正)

下表原列「尚未批量化」,已于同日全部完成(仅限下表所列 6 个部署器);本清单发出后未随代码更新,一度与实现体不符 (2026-10-07 复核时曾据此误判,见 ../submit_node_batch_20261007/SOP.md 第 1 章)。现状逐条核实:

部署器 现状(file:line 复核)
submit_node_deployer.py _sync_munge / _sync_hosts_entries / _sync_tier1 已批量(_run_per_host,L329/349/374);_sync_host 于主题 ../submit_node_batch_20261007/ 完成(每台一条 shell + 回填探测缓存)
storage_layout_deployer.py probe_mount / probe_tier0 已批量(sync_status 内 _run_per_host,L587);组目录判定 10-08 收尾(改复用 probe_group_dirs,去 N 条)
scratch_gc_deployer.py 当时仅做到主机间并发(L243);主机内仍逐条、_query 未接缓存 → 10-08 改为每台一条 shell
local_scratch_deployer.py probe_device 已批量(L515)
node_env_deployer.py / module_env_deployer.py installer.sync 已用 _kv_script + _run_per_host(L163/L398、L192/L361)

一句话现状:9 条探测路径全部已批量——但本清单只覆盖当时复核的 6 个部署器。

⚠️ 2026-10-08 更正(过度声称):上句不成立。复核漏了 3 处: scratch_gc_deployer(主机内逐条、且自有 _query 未接缓存)、 storage_layout_deployer::_probe_node 的组目录循环、user_mgmt/health_ensure 的 pre_check。 已于主题 ../probe_batch_residual_20261008/ 收尾(命令数 -21.7%…-77.2%,变更清单逐条一致)。 子命令实测基线见 ../submit_node_batch_20261007/SOP.md 第 6 章与新主题附录。


来源:/home/rain/Documents/slurm/slurm_ins/sync_status_concurrency_20261007/SOP.md | 最后更新:2026-10-08 01:43(由 build/sync.py 同步,请勿在站点侧修改)