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

数据库提交和外部副作用如何衔接:Event Log、Outbox 与 Side-effect Consistency ​

前置:Transaction;Runtime Facts Persistence;Idempotency
核心问题:数据库状态已经提交但消息还没发出去时怎样避免“状态成功、事件丢失”,Outbox 为什么能缩小这个 crash window?

RUN CREATED、NODE STARTED、TOOL SUCCEEDED 依次追加,current state 可由 reducer 构建。Node 完成事务同时插 outbox node.completed ,publisher 之后送 MQ,即使进程中间 crash 也能重试。

Durable Event Log ​

append-only event log 有利于审计和重建;恢复时从 checkpoint + subsequent events 重放,但外部动作需依据 execution record 避免重复。

Event Log 追加状态变化事实,可用于审计、重建、异步消费;需要稳定 event id、ordering scope 和 schema version。不要随意修改历史事件。

Outbox Pattern 基础 ​

Outbox 解决的是“数据库已经提交,但通知/消息还没发出去”这个 crash window。

text
DB transaction:
  update run state
  insert outbox event
COMMIT

separate dispatcher:
  read outbox
  publish message
  mark sent / retry idempotently

因为业务状态和 outbox row 在同一个 transaction 里,要么都存在,要么都不存在。这样进程在 commit 后立刻 crash,dispatcher 之后仍能补发。它不等于 Exactly-Once,消费者依然要能处理重复投递。

External Side-effect Consistency ​

External Side-effect Consistency 关注数据库事实与邮件、支付、HTTP API 等无法纳入本地 transaction 的副作用如何最终对齐,包括 idempotency、outbox、reconciliation 和 compensation;它是目标问题,不等于某一种 pattern。

Outbox 把哪个 crash window 封住了 ​

假设 Node 完成后既要更新数据库,又要发布 node.completed 到 Queue。危险写法是:

text
UPDATE node → COMMIT
        ↓ crash here
publish message

数据库已经认为完成,但消费者永远收不到事件。反过来先 publish 再 commit,则可能消息已经消费、数据库却回滚。

Outbox 把“业务状态”和“待发送事件”放进同一个本地 transaction:

sql
BEGIN;
UPDATE node_executions SET status='SUCCEEDED' WHERE ...;
INSERT INTO outbox(event_id, topic, payload, status)
VALUES (:event_id, 'node.completed', :payload, 'PENDING');
COMMIT;

独立 dispatcher 之后读取 PENDING row 并投递。即使 publish 成功但 ACK 前 dispatcher crash,也允许再次投递,因此消费者仍需按 event_id 幂等。

于是 Outbox 提供的不是神奇的 cross-system exactly-once,而是一个 durable bridge:本地提交后“这条消息必须最终被尝试发送”这件事不会随着进程死亡而丢失。

Failure Modes ​

DB Commit 与外部 Side Effect 没有 Durable Bridge ​

症状: 写 DB 后直接发消息/API,进程在两步间 crash。

为什么危险: DB 已提交但事件没发,或外部成功本地不知道。

处理: 同事务写业务状态+outbox;dispatcher 幂等投递并记录确认。

如何验证 ​

Case断言
DB Commit 与外部 Side Effect 没有 Durable Bridgecommit/dispatch/ack 各点注入 crash 后最终可对账且不重复。

自测 ​

  1. DB 已提交“要发消息”,进程在真正 publish 前 crash。Outbox 为什么能让这两个动作不再依赖一个不存在的跨系统事务?
  2. 反过来如果外部副作用先成功、DB 后失败,Event Log/Outbox 单独能完全解决吗?还需要什么 identity 或 reconciliation?

Sources ​

AI Engineering · Agent · Harness · Backend Systems