# Runbook:僵尸任务涡旋修复上线(2026-08-02) ## 背景 2026-08-01 晚重启工作流后发生重复派发涡旋(单点最高被算 101 次、wave3-9 积压饿死 6+ 小时)与收敛点被迟到失败报告翻黑(converged 净减少)。根因与修复见本次代码变更(三条不变量:每点至多一个活任务;converged 吸收态;stop/重启不产生僵尸)。 **当前生产状态(14:24 实时)**:工作流已暂停、槽位已排空、converged 4621 / failed 1139 / pending 3456。本 runbook 从"部署新版"开始。 ## 第 1 步:部署新版服务端 ```bash cd /home/fmq/program/tlusty/tl208-s54/dcts # 本机(代码所在) ./scripts/deploy.sh -e remote -b compose -r server ``` 部署期间工作流保持 paused,无影响。部署完成后确认容器正常: ```bash ssh fmq@100.66.1.2 'cd /vol1/1000/program/dcts && docker compose ps server' curl -s "http://100.66.1.2:8090/api/status" -H "Authorization: Bearer " | head -c 300 ``` ## 第 2 步:存量清理(维护窗口,约 2 分钟) 在 100.66.1.2 上执行。建议停容器避免 WAL 锁争用(节点心跳会短暂失败并自动恢复): ```bash ssh fmq@100.66.1.2 cd /vol1/1000/program/dcts docker compose stop server # 确认队列库路径(compose 默认为 data/dcts_queue.db) ls -la data/dcts.db data/dcts_queue.db # 备份 cp data/dcts.db data/dcts.db.bak.$(date +%s) cp data/dcts_queue.db data/dcts_queue.db.bak.$(date +%s) # 清理 + 修复(先审计后执行) sqlite3 data/dcts.db <<'SQL' ATTACH 'file:data/dcts_queue.db?mode=ro' AS q; -- 审计 1:将删除的僵尸行数(预期数千条;= 无队列凭证的 pending 行) SELECT COUNT(*) AS zombies FROM main.tasks WHERE status='pending' AND workflow_name='sdB_cno' AND task_id NOT IN (SELECT task_id FROM q.task_queue); -- 审计 2:被翻黑的收敛点数(success_method 非空的 failed 点) SELECT COUNT(*) AS flipped FROM grid_points WHERE workflow_name='sdB_cno' AND status='failed' AND success_method IS NOT NULL; -- 审计 3:从未真正获得种子救援的 failed 点数 SELECT COUNT(*) AS never_rescued FROM grid_points WHERE workflow_name='sdB_cno' AND status='failed' AND success_method IS NULL AND NOT EXISTS (SELECT 1 FROM tasks t WHERE t.point_name=grid_points.name AND t.workflow_name='sdB_cno' AND t.task_type='seed_step'); -- 执行 1:清僵尸(claimed 在途行自动保留,NOT IN 子查询不过滤 status) DELETE FROM main.tasks WHERE status='pending' AND workflow_name='sdB_cno' AND task_id NOT IN (SELECT task_id FROM q.task_queue); -- 执行 2:修复被翻黑的收敛点 UPDATE grid_points SET status='converged' WHERE workflow_name='sdB_cno' AND status='failed' AND success_method IS NOT NULL; -- 执行 3(可选):复活从未获救援的 failed 点,走正常 cold_run → 种子回退路径 UPDATE grid_points SET status='pending' WHERE workflow_name='sdB_cno' AND status='failed' AND success_method IS NULL AND NOT EXISTS (SELECT 1 FROM tasks t WHERE t.point_name=grid_points.name AND t.workflow_name=grid_points.workflow_name AND t.task_type='seed_step'); SQL docker compose up -d server ``` > 注意:执行 3 会把约百个点打回 pending,重启后它们会先跑一轮 cold_run 再触发种子回退——这是预期行为。 ## 第 3 步:启动工作流 ```bash curl -s -X POST "http://100.66.1.2:8090/api/workflows/sdB_cno/start" \ -H "Authorization: Bearer " ``` 启动时 `initialize_grid` 卫生逻辑会再清一遍残留(与第 2 步不冲突),随后 3456 个 pending 点按 wave 升序正常派发。 ## 第 4 步:验证(启动后 1 小时内) ```bash # 1. 无重复派发:最近 1 小时每个点应至多 1 个任务(预期空结果) ssh fmq@100.66.1.2 "sqlite3 /vol1/1000/program/dcts/data/dcts.db \ \"SELECT point_name, COUNT(*) n FROM tasks WHERE created_at > datetime('now','-1 hour') GROUP BY point_name HAVING n > 1;\"" # 2. converged 单调不减、queued 以 ~100/h 下降(每 10 分钟看一次) watch -n 600 "curl -s http://100.66.1.2:8090/api/status -H 'Authorization: Bearer ' | head -c 200" # 3. 历史涡旋点不再产生新任务 ssh fmq@100.66.1.2 "sqlite3 /vol1/1000/program/dcts/data/dcts.db \ \"SELECT COUNT(*) FROM tasks WHERE point_name='t55000_g5.0_he2_c-4_n-3_o-4' AND created_at > datetime('now','-1 hour');\"" # 4. 观察项(非承诺):快照 running 计数是否恢复 >0。若节点满载但 running 恒 0, # 属独立显示问题,查 server 日志中 claim 的 "跳过 running 标记" info 行定位。 ``` ## 回滚预案 若新版出现异常:`docker compose stop server` → 恢复备份(`cp data/dcts.db.bak. data/dcts.db`,队列库同理)→ 部署旧镜像 → up。清理 SQL 的逆操作不需要(僵尸行清除与翻黑修复都是单向正确方向)。