意外中断与自动续跑
工作流跑到一半被打断怎么办?配好 recovery 与 idempotent,跑断点续传、避免重复副作用。
"一次没跑完怎么办"是生产级工作流必须回答的问题。Braidrun 提供两种处理方案:
- 默认:安全中断 — 把 execution 标为 INTERRUPTED,等你看清楚再手动从中间某一步"重试"。
- 可选:自动续跑 — 你显式声明"这条工作流可以续跑 + 哪些步骤幂等"后,平台自动从中断点继续。
中断到底指什么
两类:
- 平台侧 —— 服务重启、实例被替换、宿主机故障。这些我们努力避免但不能 100%。
- 你侧 —— 你主动"取消"某次 execution(停在 CANCELLED,不是 INTERRUPTED)。
只有前一类才会进入"自动续跑"流程,CANCELLED 的 execution 不会自动跑。
默认行为:安全中断
发生平台侧中断时,平台:
- 把该 execution 状态标为 INTERRUPTED;
- 保留所有已完成 step 的结果、产物、日志;
- 你收到一条站内通知(也会按你的通知配置邮件 / Slack);
- 你去执行详情页,可以"从失败那一步重试"—— 平台复用前面 step 的结果。
这种默认对所有工作流都可用,不需要配置。适合:
- 工作流含对外副作用(发邮件 / 扣款 / 改数据库)
- 生产还不确定"重启续跑是否安全"时
可选:自动续跑
如果你的工作流是纯数据管道(只读 API + 生成报表),可以开启自动续跑 —— 中断发生后平台自动从中断点继续跑,不需要你手动点。
启用的两层 opt-in
要让自动续跑生效,你需要在 YAML 里两层都声明:
- workflow 级: 声明
recovery.autoResumeOnRestart: true - step 级: 安全重跑的步骤标
idempotent: true
完整示例
yaml
name: daily-asa-digest
version: "1.0.0"
recovery:
autoResumeOnRestart: true
policy: RESUME_FROM_LAST_INCOMPLETE
maxAutoResumeAttempts: 3
steps:
- id: fetch_asa_data
type: sub_workflow
module: dingyue-module-asa-fetch
idempotent: true # 只读拉取,可重跑
- id: build_excel
type: sub_workflow
module: dingyue-module-excel-report
idempotent: true # 生成文件到产物目录,可重跑
- id: notify_team
type: sub_workflow
module: dingyue-module-slack-deliver
# 不写 idempotent —— 默认 false,发消息不可重跑三种 recovery.policy
| policy | 含义 | 适合场景 |
|---|---|---|
ABORT | 不自动恢复,等于回到默认行为 | 测试阶段 |
RESUME_FROM_LAST_INCOMPLETE | 默认。从第一个非 COMPLETED 的 step 开始重跑。 | 数据管道、只读 API 聚合、纯计算 |
RESUME_FROM_START | 整条 workflow 从头开始重跑 | 数据源可能在中断期间变化、需要刷新的场景 |
idempotent 怎么标
判断一个 step 是否 idempotent 的黄金标准:
"同样的输入跑两次,会不会产生副作用?"
如果会(对外发消息、扣款、写订单),就不要加 idempotent。
| Step 性质 | idempotent |
|---|---|
| 只读 API 查询(拉报表、列 App) | ✓ true |
| 纯计算 / 数据转换 | ✓ true |
| 生成 Markdown / Excel 到产物目录 | ✓ true(重跑前旧产物会被清理) |
| LLM 推理(无副作用工具) | ✓ true(会重新计 tokens,但语义安全) |
| 发邮件 / Telegram / Slack / 飞书 | ✗ false |
| 写入外部 DB / 改广告出价 / 下单 / 扣款 | ✗ false |
| Webhook 出站调用 | ✗ false(除非对方 endpoint 明确去重) |
| manual_approval | ✗ false(续跑会再 ping 一次审批人,体验差) |
默认值是 false —— 安全为先
不写 idempotent 默认是 false。RESUME_FROM_LAST_INCOMPLETE 遇到非幂等 step 会降级为 ABORT —— 不自动跑,留给你手动。
自动续跑不会发生的几种情况
即使你配好了 recovery,以下几种 execution 仍然不会自动续跑:
- 触发源是 Webhook —— Webhook payload 可能 at-most-once,平台不知道上游是否会 retry。
- 触发源是 Scheduler —— scheduler 会按 cron 下一轮自行 re-fire。
- 当前停在 manual_approval —— 等审批意味着本来就是阻塞状态。
- workflow 定义文件在中断期间被修改 —— 平台检测到 hash mismatch,不确定新版是否兼容。
手动"从某一步重试"(随时可用)
不管工作流有没有配 recovery,执行详情页始终提供"从失败步骤重试"按钮:
- 打开一条 INTERRUPTED / FAILED 的 execution;
- 点失败 / 中断的 step 旁的「从这一步重试」;
- 前面已 COMPLETED 的 step 结果会被复用;这一步开始往下重新跑。
即使这一步不是 idempotent,你也可以手动重试 —— 因为这时是你作为人有意识地决定"值得重跑"。
续跑限额
防止系统把同一条 execution 来回重启"死循环":每条 execution 最多自动续跑 3 次。超过仍然失败就停下来等你处理。你可以在 recovery.maxAutoResumeAttempts 里覆盖(1-20)。
审计
每次自动续跑都会记审计事件:
execution.auto_resume— 成功续跑execution.auto_resume.skipped— 按规则不应该续跑,带 reason
Admin 可以在审计日志里按这两个 event_type 过滤查看。
实战建议
- 纯数据管道(ASA 日报、RSS 摘要):recovery + idempotent 全开,安心让它自动续跑。
- 含外部副作用的(扣款、发消息、下单):不要开 auto resume。中断了人工决策。
- 关键副作用步骤前加 manual_approval —— 审批本身是中断续跑的"天然阻塞点"。