数据库任务总在重试?SqlFlow 用持久化步骤把长流程变成可恢复工作流
很多后台任务的真正难点,不是把代码跑起来,而是处理“跑到一半”这件事:支付接口已经调用,进程却在写入结果前崩溃;流程需要等待人工审批,但工作线程不能一直占着;定时任务越来越多,数据库队列又被高频轮询拖慢。SqlFlow 是一个基于 PostgreSQL 或 SQL Server 的持久化工作流系统,尝试用数据库保存状态,用信号唤醒工作进程,从而避免把恢复逻辑全部塞进应用内存。
它解决的不是普通队列问题
SqlFlow 把流程拆成三个重要概念:Workflow 是开发者编写的业务逻辑,Task 是一次具体执行,Step 则是持久化边界。一个容易失败的操作包在 Step 中后,返回结果会写入数据库;如果服务器在之后崩溃,任务恢复时可以跳过已经完成的 Step,而不是再次调用外部服务。
这对 AI Agent 也有实际意义。一次 Agent 任务可能包括调用模型、查询数据库、等待人工确认和提交最终结果。把整个流程当成一次不可分割的函数,任何中断都会让系统难以判断哪些副作用已经发生。持久化步骤则提供了更清晰的恢复点。
存储和唤醒是两条不同的路径
SqlFlow 的设计值得注意的地方,是把数据库存储和 signaling(唤醒信号)分开。数据库是任务状态的事实来源;信号只负责告诉空闲 worker“可能有新工作了”,不承载任务数据。即使信号因网络问题丢失,任务本身仍然留在数据库中,可以由安全的兜底检查重新发现。
在 PostgreSQL 中,它使用 LISTEN / NOTIFY;在 SQL Server 中使用 Service Broker;需要跨节点扩展时,还可以使用 NATS JetStream 做信号层。这样做比让每个 worker 不停执行 SELECT 更节省数据库资源:没有工作时,worker 可以休眠,有新任务时再被唤醒并通过事务领取任务。
一个可恢复的业务流程
README 给出的 C# 示例使用 Step 保存支付调用结果,再用 AwaitEvent 等待人工审批:
public async Task<Result> ExecuteAsync(TaskContext ctx, Order order)
{
var payment = await ctx.Step("charge-card", async () =>
await _stripe.ChargeAsync(order.Amount));
if (!payment.Success) return new Result { Failed = true };
var approval = await ctx.AwaitEvent<HumanApproval>(
"manager-approval", "wait-for-human");
if (approval.Approved)
{
await ctx.Step("ship-product", async () =>
await _fulfillment.ShipAsync(order.Id));
}
return new Result { Success = true };
}
这里有两个关键点。第一,支付调用的结果已经持久化,进程在后续阶段崩溃时,恢复流程不必盲目重复扣款。第二,AwaitEvent 把等待状态保存下来并释放 worker;审批事件到达后,流程才继续执行。它比在内存中写一个永久 while 循环更适合长时间等待。
当然,Step 并不能自动解决外部系统的幂等性。支付、发货或发送邮件等操作,仍应使用业务幂等键,并在服务端确认重复请求的处理策略。持久化状态降低了重复执行风险,但不等于副作用天然可回滚。
并发、重试和定时任务
SqlFlow 的 worker 通过数据库事务领取任务,并用并发上限限制同时执行的数量。假设某个 worker 的 Concurrency 配置为 5,即使瞬间产生 1000 个任务,也只会领取可处理的一小批,其余任务留在数据库中等待。任务失败后可以进入重试状态,下一次执行时间和指数退避由持久化状态驱动。
项目同时支持 PostgreSQL 和 SQL Server 的建表脚本,并提供 .NET、Java、Python 和 Go SDK。仓库还包含 Management API 和 Control Panel,用于查看系统健康状况、处理中的任务、慢任务以及具体执行记录。对于已经依赖关系型数据库的团队,这种方案的迁移成本可能比引入一个独立工作流集群更低;但它仍然需要认真评估数据库容量、锁竞争和任务保留策略。
如何开始评估
SqlFlow 的仓库提供四种语言教程。建议先做一个不会产生真实副作用的实验:创建数据库 schema,运行一个包含两个 Step 的任务,在第二步主动让进程退出,然后观察第一次 Step 的结果是否能被恢复流程复用。之后再加入 AwaitEvent,模拟人工审批或外部 webhook。
评估时至少检查四件事:任务状态是否是数据库中的唯一事实来源;信号丢失后是否仍能恢复;重试是否会造成外部副作用重复;并发设置是否符合数据库连接池和下游 API 限流。对 AI Agent 而言,还应把模型调用、工具写操作和人工审批分别设为清晰的边界,避免“整段 Agent 对话”成为不可恢复的大事务。
我的判断
SqlFlow 的价值不在于把数据库包装成又一个消息队列,而在于提供了一种相对朴素的恢复模型:数据库保存状态,Step 保存结果,事件负责暂停与继续,信号只负责唤醒。它适合需要长时间运行、可重试、可人工介入的业务流程,也适合希望把 AI Agent 放进现有数据库基础设施的团队。
它不是 Temporal 或成熟编排平台的直接替代品,生产使用前仍要验证 SDK 完整度、迁移脚本、故障恢复和监控能力。不过对于从单体应用开始、希望先解决“任务跑一半怎么办”的团队,SqlFlow 提供了一个值得动手验证的轻量路径。