只用 Postgres:重新思考持久工作流执行
Durable workflows 是构建可靠程序的强大工具。通过定期将进度检查点写入数据库,程序可以在崩溃或故障后从上一次完成的步骤重新加载——就像在电子游戏中保存进度一样。传统上,这通过 外部编排 实现,其中一个中心服务器(如 Temporal、Airflow 或 AWS Step Functions)协调步骤并管理状态。
然而,越来越多的观点认为外部编排本质上过于复杂。如果持久工作流的核心目的只是将状态检查点写入数据库,为什么还要引入单独的编排服务器?通过直接使用数据库本身作为编排器,开发者可以简化技术栈,降低故障点,并利用已有的数据库专业知识。
基于 Postgres 的执行架构
在基于 Postgres 的系统中,应用服务器直接与数据库通信。无需中心编排器分发任务,流程如下:
- 提交:客户端在 Postgres 工作流表中创建一条记录。
- 执行:应用服务器轮询该表以出队并执行工作流。
- 检查点:服务器在执行每一步时,将输出直接写入 Postgres 作为检查点。
- 恢复:如果服务器崩溃,另一台服务器可以从最后的检查点恢复工作流。
该设计消除了对中心协调者的需求。服务器使用锁机制(例如 SKIP LOCKED)协作出队工作流,以确保每个工作流仅由一个工作者处理。如果多个工作者同时尝试执行同一工作流,数据库完整性约束会防止重复工作。
数据库中心方法的关键优势
可扩展性与可用性
通过将编排转移到数据库,系统的可扩展性直接与数据库性能挂钩。Postgres 可以垂直扩展以处理每秒数万条工作流,水平扩展则可通过分片或类似 CockroachDB 的分布式版本实现。由于工作者是可互换的,只要数据库在线,系统即可保持可用。
内置可观测性
最显著的优势之一是工作流状态存储在关系表中。这意味着可观测性可以通过 SQL “免费”获得。复杂的分析查询——例如查找上个月所有失败的工作流——可以声明式执行,无需专门的可观测性工具或键值存储扫描。
降低安全攻击面
外部编排器会引入新的故障点和安全风险,因为它们通常处理敏感的应用数据。使用 Postgres 可以复用已有的关键基础设施。如果你的应用已经依赖 Postgres,添加持久执行并不会引入需要加固、审计或保护的新基础设施。
社区观点与权衡
尽管这些好处令人信服,开发者社区在放弃专用编排器时仍指出了若干关键考虑因素。
“自行构建 vs 购买”复杂性陷阱
一些工程师警告说,虽然最初的设置很简单,但复杂性最终会从编排器迁移到应用逻辑。随着系统的增长,你可能会发现自己手动实现专用引擎开箱即用的功能:
一旦你需要重试、退避、超时、取消、版本控制、可视化、任务路由、速率限制、租约、心跳、卡住的工作者检测、重放/调试语义、工作流迁移、分叉/汇聚、长定时器、审计日志以及运维工具,“只用数据库”的说法就会变成“构建一个糟糕的工作流引擎副本加上一堆工作者”。
正确性与幂等性
关于数据正确性存在激烈争论。一些人认为,若未以极其谨慎的方式实现,简单的持久工作流伪代码在崩溃时可能导致数据损坏。常见的替代方案是将工作流构建为 幂等操作,系统可以从头重新启动并跳过已完成的步骤。
大规模性能
虽然 Postgres 功能多样,但有用户指出,在云端运行高度持久的 Postgres 实例成本较高。另一些人建议,对于极端规模,基于磁盘日志的架构可能比传统的数据库支撑系统更具成本效益。
替代实现
除了 DBOS,其他多种工具和模式也实现了类似的理念:
- Absurd:针对 Postgres 的最小化持久工作流实现。
- Oban:一个流行的 Elixir 作业处理库,使用 Postgres。
- Custom Rust Implementations:多个团队构建了最小化的 Rust crate,以在 Postgres 中处理持久 actor 和有限状态机(FSM)。
- SQLite:针对较小工作负载,一些开发者已将这些概念移植到 SQLite,使用简单的轮询循环而非
LISTEN/NOTIFY。
结论
将编排迁移到数据库对于已经深度使用 Postgres 生态的团队而言是务实的选择。它降低了架构开销并简化了可观测性。然而,这一权衡意味着所有权的转移:你用单独编排器的运维复杂性换取了在开发层面确保正确性并手动实现高级工作流特性的复杂性。对许多团队而言,能够将所有数据集中在同一位置并使用 SQL 进行监控,使得这种取舍值得。
SUMMARY: 探讨为何将 Postgres 用作持久工作流编排器可以通过去除中心编排器并利用原生数据库能力实现可扩展性和可观测性,从而简化架构。
TITLE: 只用 Postgres:重新思考持久工作流执行