PostgreSQL 内部的持久化执行框架
Microsoft 开源的 pg_durable 是一个 PostgreSQL 扩展,为那些已经将状态保存在 Postgres 中、并希望摆脱拼凑 cron 任务、工作进程、队列和状态表来让后台工作变得可靠的团队,提供了长时间运行、容错的 SQL 函数支持。用 SQL 定义工作流,让 pg_durable 在每个步骤设置检查点(checkpoint),在崩溃、重启或步骤失败后自动恢复执行。
持久化执行(durable execution)如今已是行业标准模式,pg_durable 将其引入 Postgres 内部,无需额外的基础设施。这是我们推动"计算靠近数据"使命的一部分。
立即在 Azure HorizonDB 中体验 pg_durable——Microsoft 全新的 PostgreSQL 云服务,专为性能而设计,内置 pg_durable。
这适合我吗?
适用人群
- 后端和数据工程师——希望工作流与其操作的数据共存。
- DBA 和 SRE——需要自动化 runbook,这些 runbook 必须能在重启后存活且可在 SQL 中审计。
- 构建数据或 AI 管道的团队——需要按行、按文档或按批次进行持久化执行。
核心理念
pg_durable 函数是一个由 SQL 步骤组成的图(graph),PostgreSQL 在执行过程中逐步执行并设置检查点。如果数据库崩溃、重启或某一步骤失败,执行将从最后一个持久的检查点恢复,而无需手动重建状态。
[示意图:一个持久化函数扇出为三个并行查询——统计用户数、统计订单数、汇总收入——然后合并到仪表盘步骤中]
适用场景
- 向量嵌入管道(Vector embedding pipeline):分块、调用 embedding API、写入
pgvector。 - 数据摄入管道(Ingest pipeline):暂存、去重、转换、发布大批量数据。
- 定时维护:检测膨胀、通知、等待批准、执行后续操作。
- 扇出聚合(Fan-out aggregation):并行执行独立查询,然后合并结果。
- 外部 API 工作流:从 SQL 触发的数据增强、分类和 webhook 风格调用。
你现在可能正在用的替代方案
pg_cron加一张任务表、状态列、重试计数器和轮询工作进程。- 外部编排器(如 Airflow、Temporal、Step Functions 或 Argo)回调 Postgres。
- 消息队列加工作进程加独立状态表来协调重试和部分完成。
- 一个
plpgsql存储过程,在崩溃或长时间运行的事务迫使你重新开始之前还能工作。
它解决的痛点
- 长时间任务中途重启意味着重跑已经完成的工作。
- 一行数据失败或一次 API 调用失败就导致手动清理和不确定的重放。
- 长事务持有锁、增长 WAL,使批处理任务在较大规模下变得脆弱。
- 应用层的并行处理为部分失败和状态漂移创造了更多机会。
- 工作流逻辑分散在 SQL、工作进程、队列、仪表盘和状态表中。
你的架构会如何变化
- 工作流定义迁移到 SQL 中,以
df.start(...)开始。 - 重试状态、进度跟踪和检查点迁移到 Postgres 中,而非定制应用代码。
- 部分应用层工作进程、队列消费者或调度胶水代码可以完全消失。
- 运维可见性来自
df.instances等 Postgres 表,使用与数据相同的认证和备份模型。
何时不应使用
- 任务已经是一个简单的
INSERT ... SELECT或一条普通 SQL 语句。 - 你需要亚毫秒级的同步请求处理,而非持久的后台执行。
- 你无法在 Postgres 环境中安装扩展或运行后台工作进程。
- 工作流大部分在 Postgres 之外运行,跨越多个异构系统。
- 你需要任意应用逻辑,不能清晰地映射到 SQL 步骤、分支、循环或 HTTP 调用。
工作原理
- 使用可组合的操作符(如
~>和|=>)在 SQL 中定义工作流。 - 用
df.start()启动,获取一个实例 ID。 - 让运行时在每个步骤之间通过检查点持久化执行。
- 在 PostgreSQL 中查询工作流的状态和结果——无论运行中还是已完成。
局限性
该模型有意设计为 SQL 形态。如果某一步骤需要任意代码、非 HTTP 的 SDK、或丰富的内存控制流,你可能需要将该逻辑封装在 SQL 函数中,通过 HTTP 端点暴露给 df.http(),或为该部分系统使用通用编排器。
功能特性
- 持久化(Durable)——函数状态持久化到 PostgreSQL。在崩溃、重启和故障转移中存活。
- SQL 原生(SQL-native)——使用可组合操作符在 SQL 中定义函数。
- 数据库感知(Database-aware)——调度、条件和并行执行的一流原语。
- 零基础设施(Zero infrastructure)——作为 PostgreSQL 扩展运行。无需 Redis、Temporal 或外部服务。
快速示例
-- 一个分步骤处理数据的持久化函数
SELECT df.start(
''SELECT id FROM documents WHERE processed = false LIMIT 100'' |=> ''batch''
~> ''UPDATE documents SET processed = true WHERE id = ANY($batch)''
);
软件包
标记版本从 GitHub 发布资产发布适用于 PostgreSQL 17 和 18(amd64)的 Debian 软件包。软件包命名为 pg-durable-postgresql-<PG 主版本>_<pg_durable 版本>-1_<架构>.deb,将扩展库、控制文件和 SQL 升级文件安装到对应的 PostgreSQL 安装目录中。
安装软件包后,将 pg_durable 添加到 shared_preload_libraries,重启 PostgreSQL,然后在配置的 pg_durable 数据库中创建扩展:
CREATE EXTENSION pg_durable;
默认的 pg_durable 数据库是 postgres;后台工作进程配置和权限设置请参阅用户指南。
发布资产中还包括用于从源码构建的源代码压缩包。
开发安装
前置条件
- PostgreSQL 17 或 18
- Rust(nightly)
- cargo-pgrx 0.16.1
GitHub Codespace
主分支预构建安装 PostgreSQL 17,构建 pg_durable,并在 ~/.pgrx 下准备一个已安装扩展的本地集群。PostgreSQL 默认不运行,因此开始工作时需要启动它。
# 启动 PostgreSQL
./scripts/pg-start.sh
# 连接
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres
在没有准备好的预构建的分支上,运行 pg-start.sh——它会在首次运行时构建并安装扩展(预计需要几分钟):
./scripts/pg-start.sh
其他环境
本地和 Dev Container
VS Code Dev Container(.devcontainer/)预装了 Rust、cargo-pgrx 和 PostgreSQL 17。对于裸机本地机器,请先按照 .devcontainer/onCreateCommand.sh 中的步骤安装工具链。
# 构建、初始化 PostgreSQL 并安装扩展
# 这需要一些时间——去做点别的事情
./scripts/pg-start.sh
# 连接到本地 pgrx PostgreSQL 实例
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres
pg-start.sh 为新的本地数据目录引导一个 postgres 超级用户,同时还为当前 OS 用户创建一个匹配的超级用户角色,因此默认的本地 psql 用法可以继续工作。如果想强制使用规范的引导角色,可以加 -U postgres。
Docker
# 构建并测试
./scripts/test-e2e-docker.sh --rebuild
# 可选:部署到 ACR(用于自定义集成了 pg_durable 的 PG17 镜像)
./scripts/deploy-acr.sh
多用户设置
CREATE EXTENSION pg_durable 不会向 PUBLIC 授予任何权限。安装扩展后,管理员必须显式授予应用角色访问权限。行级安全(Row-level Security, RLS)确保每个用户只能查看和管理自己的持久化函数实例和节点。
向应用角色授予权限:
-- 在 CREATE EXTENSION 后向特定角色授予权限
SELECT df.grant_usage(''app_role'');
或者,创建一个间接角色并将成员资格授予应用角色:
-- 创建用于 pg_durable 访问的共享角色
CREATE ROLE pg_durable_user NOLOGIN;
SELECT df.grant_usage(''pg_durable_user'');
-- 向应用角色授予成员资格
GRANT pg_durable_user TO app_backend, etl_service;
完整的授权列表、撤销访问权限以及加固升级安装的详细信息,请参阅用户指南——权限授予部分。
注意:
GRANT EXECUTE ON ALL FUNCTIONS仅在授权时存在的函数上生效。使用ALTER EXTENSION pg_durable UPDATE升级 pg_durable 后,重新运行df.grant_usage(''role'')(或重新执行手动授权),以便新函数可被访问。
关键要点:
- 后台工作进程角色(
pg_durable.worker_roleGUC,默认为postgres)必须是超级用户——它绕过 RLS 来管理所有用户的实例。 - 用户获得
df.instances/df.nodes的SELECT+INSERT权限,以及实例上用于df.cancel()的列级UPDATE (status, updated_at)权限。 - 标识列(
submitted_by)用户不能修改。 df.vars使用每个用户的作用域——每个用户通过owner列和 RLS 拥有自己的变量命名空间。超级用户绕过 RLS,但 DSL 函数仍然通过显式过滤器限定到调用用户。避免以明文存储密钥。
持续集成
所有拉取请求在合并前必须通过以下检查:
- 格式化检查——
cargo fmt --check - Clippy 和测试——
cargo clippy、单元测试(cargo pgrx test pg17)、pg_regress 测试和 E2E 测试。
CI 工作流定义在 .github/workflows/ci.yml,使用 pgrx 下载和管理 PostgreSQL。
测试
pg_durable 有两个测试套件:
pg_regress 测试(标准 PostgreSQL 回归测试)
快速、确定性的测试,用于核心 DSL 功能,使用 PostgreSQL 的标准测试框架。测试 SQL 位于 sql/,预期输出在 expected/,PGXS 配置在根目录 Makefile 中。
make test-regress # 完全重置并运行
make installcheck # 仅运行(PostgreSQL 必须已在运行)
E2E 测试(综合场景测试)
使用 pgrx PostgreSQL 的复杂本地集成测试:
./scripts/test-e2e-local.sh # 所有本地 SQL E2E 测试,包括特殊的重启/配置阶段
./scripts/test-e2e-local.sh 04_parallel # 特定测试
./scripts/test-e2e-local.sh --default-build-phases # 仅 default-build 阶段组
详情请参阅 tests/e2e/。
文档
架构
pg_durable 是一个 PostgreSQL 扩展(使用 pgrx 构建)——所有内容都在 PostgreSQL 服务器内部运行,无需外部服务。该扩展提供了一个用于构建函数图的 SQL DSL,并注册了一个后台工作进程,在以下两个底层 Rust 库之上持久化地执行函数:
- duroxide——一个持久化任务框架,提供编排运行时(确定性重放、检查点、子编排、定时器)。
- duroxide-pg——为 duroxide 提供 PostgreSQL 后端的持久化状态提供者。它在扩展拥有的专用
duroxide.*schema 中持久化运行时状态(实例、历史记录、工作队列)。
┌────────────────────────────────────────────────────────────────────┐
│ PostgreSQL │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ pg_durable extension (pgrx) │ │
│ │ │ │
│ │ SQL DSL ''sql'' |=> ''name'' ~> ''sql2'' │ │
│ │ df.if() | df.join() | df.loop() │ │
│ │ │ │
│ │ Background worker (hosts the duroxide runtime in-process) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ duroxide (orchestration runtime) │ │ │
│ │ │ ┌──────────────────────────────────────────────────┐ │ │ │
│ │ │ │ duroxide-pg (PostgreSQL state provider) │ │ │ │
│ │ │ └──────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Schemas │ │
│ │ df.* DSL graphs (nodes, instances, vars) │ │
│ │ duroxide.* runtime state (owned by duroxide-pg) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
如果你希望用 Rust、Python 或 Node 编写持久化函数,同时仍将状态持久化在 PostgreSQL 中,可以直接从宿主语言使用 duroxide 和 duroxide-pg——pg_durable 是当你更倾向于用 SQL 编写时,在这对库之上构建的产物。
状态
预览版(Preview)——该项目当前处于预览阶段。
支持
使用 GitHub Issues 提交错误报告和功能请求。不要通过公开的 GitHub Issues 报告安全漏洞;请按照 SECURITY.md 中的说明操作。
行为准则
本项目已采用 Microsoft 开源行为准则。如需更多信息,请参阅行为准则常见问题解答,或通过 opencode@microsoft.com 联系。
安全
Microsoft 非常重视其软件产品和服务的安全性。请勿通过公开的 GitHub Issues 报告安全漏洞。安全报告说明请参阅 SECURITY.md。
隐私与遥测
pg_durable 不会向 Microsoft 发送遥测数据。
商标
本项目可能包含项目、产品或服务的商标或标识。Microsoft 商标或标识的授权使用须遵守并遵循 Microsoft 商标与品牌指南。在本项目的修改版本中使用 Microsoft 商标或标识不得引起混淆或暗示 Microsoft 的赞助。第三方商标或标识的使用须遵守第三方相关政策。
许可证
PostgreSQL License(PostgreSQL 许可证)