Skip to content
预计 1 分钟右侧目录可快速定位

外部 Tool 不能回滚怎么办:Transactional Boundary 与 Compensation ​

前置:数据库提交和外部副作用如何衔接:Event Log、Outbox 与 Side-effect Consistency
核心问题:外部 Tool 已产生副作用却无法跟数据库一起回滚时,系统怎样选择幂等、对账或补偿而不是假装事务覆盖了它?

调用 30 秒部署 API 时不持数据库 row lock,避免阻塞其他 worker。已创建云资源但后续步骤失败,可执行 delete 作为补偿;如果 create 响应丢失,先 list/query reconcile 再决定。

Transactional Boundary vs External Tool Call ​

数据库事务只能原子覆盖本 DB,无法把 HTTP/邮件/支付纳入同一 ACID transaction。应先提交 intent/execution record,再外部调用并记录结果,或用 outbox/saga;不要在 DB lock 内等待慢 Tool。

正确边界通常是短事务记录 intent/claim→提交→外部调用→短事务记录结果,而不是在事务里等待网络。必要时结合 outbox/saga。

Compensation / Reconciliation 基础 ​

数据库事务只能回滚自己控制的数据,不能把已经发生的外部动作一起“撤销”。

例如:

text
1. 创建云资源成功
2. 本地写 success 前进程 crash
3. resume 后只看到“本地没有成功记录”

这时不能假设“事务回滚了,所以云资源也不存在”。Runtime 需要用 stable operation_id 去外部系统查询实际状态,这叫 reconciliation。

如果外部动作确实需要撤销,例如预订成功后后续流程失败,再调用 cancel_reservation 是 compensation。它是新的业务动作,不是数据库 rollback,因此也可能失败并需要继续对账。

用“创建云资源”走一遍未知结果 ​

Runtime 准备调用远程 create_vm:

text
1. DB: operation(op-42, status=INTENT) commit
2. HTTP create_vm(operation_id=op-42)
3. remote actually creates vm-9
4. response is lost
5. Runtime timeout

此时最危险的动作是直接重试 create_vm。正确策略取决于远端能力:

  • 支持 idempotency key:用同一个 op-42 retry;
  • 支持查询:先 get_by_operation_id(op-42) 做 reconciliation;
  • 两者都不支持:把结果标记为 UNKNOWN,可能需要人工对账。

如果后续整个工作流失败,delete_vm(vm-9) 可以成为 compensation。但 compensation 也可能失败、也可能不是完全逆操作,例如已经发送的邮件无法“撤回到从未发送”。所以 Saga 的本质不是伪造 ACID,而是显式记录部分成功,并为每个业务动作定义可接受的恢复策略。

Failure Modes ​

把跨系统 Exactly-Once 当默认保证 ​

症状: 认为事务/队列能让 DB 与第三方 API 原子提交。

为什么危险: 不确定结果无法处理。

处理: 明确 at-least-once/idempotency/reconciliation,必要时 compensation。

如何验证 ​

Case断言
把跨系统 Exactly-Once 当默认保证第三方成功本地超时后可查询/对账而非盲目 retry。

自测 ​

  1. 付款 Tool 已成功,随后本地 transaction 回滚。为什么数据库 rollback 不能撤销外部世界?
  2. 对于不可逆副作用,compensation 与“直接 retry 原动作”有什么语义区别?什么时候只能进入人工 reconciliation?

Sources ​

AI Engineering · Agent · Harness · Backend Systems