SOP — sync_status 并发化(远程封装上提 + 批量探测)¶
主题目录:
sync_status_concurrency_20261007/|执行日期:2026-10-07|对应挂账项:HANDOFF §3 #19
1. 背景¶
sync_status(六阶段生命周期的第 2 步,幂等状态巡检)此前有两个累积问题:
- 远程封装重复实现:
_remote_query/_remote_exec/remote_query/remote_exec/add_change/_remote_sha256在 9 个部署器文件里各自重复定义,内容高度雷同(仅异常处理与超时默认值略有出入),基类BaseDeployer里一个都没有。 - 探测逐条串行建连:
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同步,请勿在站点侧修改)