microsoft/pg_durable

GitHub: microsoft/pg_durable

pg_durable 是微软推出的 PostgreSQL 扩展,在数据库内部实现持久化执行,通过 SQL 定义工作流并逐步设置检查点,确保长时间运行的后台任务在崩溃或重启后可自动恢复。

Stars: 1987 | Forks: 49

pg_durable logo [官网](https://microsoft.github.io/pg_durable/) · [文档](docs/) · [快速示例](#quick-example) · [GitHub](https://github.com/microsoft/pg_durable) [![许可证](https://img.shields.io/badge/license-PostgreSQL%20License-3d86c6.svg)](LICENSE.txt) [![PostgreSQL 17 & 18](https://img.shields.io/badge/PostgreSQL-17%20%26%2018-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/) ## PostgreSQL 内部的持久化执行
为那些已经将状态保存在 Postgres 中,并希望不再通过拼凑 cron jobs、workers、queues 和状态表来确保后台工作可靠性的团队,提供长时间运行且容错的 SQL 函数。只需在 SQL 中定义 workflow,让 pg_durable 为每个步骤设置 checkpoint,即可在崩溃、重启或步骤失败后恢复执行。 持久化执行现已成为标准的行业模式,pg_durable 将其引入 Postgres 内部,无需额外的服务基础设施。这是我们实现“将计算拉近数据”使命的一部分。 ## 这适合我吗? ### 适用人群 - 希望 workflow 与其处理的数据相邻的后端工程师和数据工程师。 - 自动化 runbook 的 DBA 和 SRE,这些 runbook 必须能够在重启后保留状态,并可通过 SQL 进行审计。 - 构建数据或 AI pipeline 且需要针对每行、文档或批次进行持久化执行的团队。 ### 核心理念 pg_durable 函数是一个由 SQL 步骤组成的图,PostgreSQL 会在执行过程中为其逐步设置 checkpoint。如果数据库崩溃、重启或某个步骤失败,执行将从上一个持久化的 checkpoint 恢复,而无需您手动重构状态。
A durable function fans out into three parallel queries — count users, count orders, sum revenue — that join into a dashboard step
### 适用的工作负载 - Vector embedding pipeline:分块、调用 embedding API,并 upsert 到 `pgvector`。 - Ingest pipeline:暂存、去重、转换并发布大批量数据。 - 定期维护:检测 bloat、通知、等待批准,然后执行下一步操作。 - Fan-out 聚合:并行运行独立查询,然后 join 结果。 - External API workflow:从 SQL 进行数据充实、分类以及 webhook 式调用。 ### 您目前可能采取的替代方案 - 使用 `pg_cron` 加上一个 jobs 表、状态列、重试计数器和一个轮询 worker。 - 使用外部 orchestrator(如 Airflow、Temporal、Step Functions 或 Argo)回调 Postgres。 - 使用 queue 加上 workers 以及一个单独的状态表来协调重试和部分完成。 - 使用一个 `plpgsql` 存储过程,它原本运行良好,直到崩溃或长时间运行的事务迫使您重新开始。 ### 解决的痛点 - 长时间运行的任务中途重启意味着要重新运行已经成功的工作。 - 一行数据或一次 API 调用失败就会导致手动清理和不确定的重放。 - 长事务持有锁、增加 WAL,并使批处理作业在更大规模时变得脆弱。 - 应用层的并行工作为部分失败 bug 和状态漂移创造了更多可能。 - Workflow 逻辑最终散落在 SQL、workers、queues、仪表板和状态表中。 ### 架构的变化 - Workflow 定义迁移到 SQL 中,并以 `df.start(...)` 开始。 - 重试状态、进度跟踪和 checkpoint 机制转移到了 Postgres 中,而不是定制的应用代码。 - 部分应用层的 workers、queue 消费者或调度器粘合代码可以完全移除。 - 运维可见性来自于 Postgres 表(例如 `df.instances`),并使用与您的数据相同的身份验证和备份模型。 ### 何时不适合使用 - 该作业已经是一个简单的 `INSERT ... SELECT` 或普通的 SQL 语句。 - 您需要的是亚毫秒级的同步请求处理,而不是持久的后台执行。 - 您无法在 Postgres 环境中安装扩展或运行后台 worker。 - Workflow 主要存在于 Postgres 之外,并跨越多个异构系统。 - 您需要的任意应用逻辑无法整洁地映射为 SQL 步骤、分支、循环或 HTTP 调用。 ### 工作原理 1. 使用诸如 `~>` 和 `|=>` 之类的可组合运算符在 SQL 中定义 workflow。 2. 使用 `df.start()` 启动它并获取一个 instance ID。 3. 让 runtime 在执行每个步骤时持久化地设置 checkpoint。 4. 在 workflow 运行期间或完成后,从 PostgreSQL 查询状态和结果。 ### 限制 该模型有意设计为 SQL 形态。如果某个步骤需要任意代码、非 HTTP SDK 或丰富的内存控制流,您可能需要将该逻辑封装在 SQL 函数中,通过 HTTP endpoint 暴露给 `df.http()`,或者针对系统的该部分使用通用 orchestrator。 ## 功能 - **持久化** — 函数状态持久化到 PostgreSQL 中。可在崩溃、重启和故障转移后存活。 - **SQL 原生** — 使用可组合的运算符在 SQL 中定义函数。 - **感知数据库** — 为调度、条件和并行执行提供一等基元。 - **零基础设施** — 作为 PostgreSQL 扩展运行。不需要 Redis、Temporal 或任何外部服务。 ## 快速示例 ``` -- A durable function that processes data in steps 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-_-1_.deb`,并会将扩展库、控制文件和 SQL 升级文件安装到匹配的 PostgreSQL 安装目录中。 标记的发布版本还会将适用于 PostgreSQL 17 和 18 的可直接运行的 Docker 镜像(`linux/amd64`)发布到 GitHub Container Registry:`ghcr.io/microsoft/pg_durable`。该镜像在官方 `postgres` 镜像的基础上安装了已发布的 Debian 软件包。每个发布会发布不可变的 `X.Y.Z-pg` 和 `vX.Y.Z-pg` 标签(例如 `0.2.2-pg17`、`0.2.2-pg18`);最高稳定版还会额外更新浮动标签 `pg`,而默认的主版本(`pg17`)还会更新 `latest`。PG 主版本是每个标签的一部分,因此可以并行发布多个 PostgreSQL 版本。请在 浏览所有已发布的镜像和标签。 运行已发布的镜像 — PostgreSQL 17 和 18 可以在不同的主机端口上并行运行: ``` # PostgreSQL 17(`latest` 标签也指向最新的 PG17 版本) docker run -d --name pg_durable_pg17 \ -p 5432:5432 \ -e POSTGRES_PASSWORD=secret \ ghcr.io/microsoft/pg_durable:pg17 # PostgreSQL 18(在不同 host port 上与 PG17 并行运行) docker run -d --name pg_durable_pg18 \ -p 5433:5432 \ -e POSTGRES_PASSWORD=secret \ ghcr.io/microsoft/pg_durable:pg18 # 使用 psql 连接(PG17 在 5432 上,PG18 在 5433 上) psql "postgresql://postgres:secret@localhost:5432/postgres" psql "postgresql://postgres:secret@localhost:5433/postgres" ``` 该扩展会在首次初始化时预加载并创建在 `postgres` 数据库中。系统会忽略 `POSTGRES_DB` — pg_durable 始终安装到 `postgres` 中,因此该扩展和后台 worker 绝不会以不同的数据库为目标。为了实现可重现的部署,请指定不可变的 `X.Y.Z-pg` 标签(例如 `0.2.2-pg17`),而不是浮动的 `pg`/`latest` 标签;不可变标签一旦发布就永远不会被覆盖。 安装软件包后,将 `pg_durable` 添加到 `shared_preload_libraries` 中,重启 PostgreSQL,并在配置好的 pg_durable 数据库中创建该扩展: ``` CREATE EXTENSION pg_durable; ``` 默认的 pg_durable 数据库是 `postgres`;有关后台 worker 配置和权限设置,请参阅[用户指南](USER_GUIDE.md)。 每个发布版本还会提供用于从源码构建的源码归档文件,以及用于验证已下载资产的 `SHA256SUMS` 文件。 ## 开发安装 ### 前置条件 - PostgreSQL 17 或 18 - Rust (nightly) - [cargo-pgrx](https://github.com/pgcentralfoundation/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,并安装 extension # 这需要一段时间 - 先去忙别的吧 ./scripts/pg-start.sh # 连接到本地 pgrx PostgreSQL 实例 ~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres ``` `pg-start.sh` 会使用 `postgres` 超级用户为新的本地数据目录进行引导,并创建一个与当前操作系统用户相匹配的超级用户角色,因此默认的本地 `psql` 用法将继续有效。如果您想显式强制使用规范的引导角色,请使用 `-U postgres`。 #### Docker 要运行预构建的已发布镜像,请参阅[软件包](#packages)部分。对于本地开发和测试,请从源码构建并运行: ``` # 编译和测试(源 Dockerfile — 编译 extension) ./scripts/test-e2e-docker.sh --rebuild # 可选:部署到 ACR(用于内置 pg_durable 的自定义 PG17 镜像) ./scripts/deploy-acr.sh ``` ## 多用户设置 `CREATE EXTENSION pg_durable` **不会**向 `PUBLIC` 授予任何权限。安装扩展后,管理员必须向应用角色显式授予访问权限。行级安全 (RLS) 确保每个用户只能查看和管理自己的持久化函数实例和节点。 **向应用角色授予权限:** ``` -- Grant to specific roles after CREATE EXTENSION SELECT df.grant_usage('app_role'); ``` 或者,创建一个间接角色并将应用角色添加为其成员: ``` -- Create a shared role for pg_durable access CREATE ROLE pg_durable_user NOLOGIN; SELECT df.grant_usage('pg_durable_user'); -- Grant membership to application roles GRANT pg_durable_user TO app_backend, etl_service; ``` **关键点:** - 后台 worker 角色(`pg_durable.worker_role` GUC,默认为:`postgres`)**必须是超级用户** — 它会绕过 RLS 来管理所有用户的实例 - 用户在 `df.instances` / `df.nodes` 上拥有 `SELECT` + `INSERT` 权限,并在 instances 上拥有列级别的 `UPDATE (status, updated_at)` 权限以供 `df.cancel()` 使用 - 用户无法修改身份标识列 (`submitted_by`) - **`df.vars` 使用按用户划分的作用域** — 每个用户通过 `owner` 列和 RLS 拥有自己的变量 namespace。超级用户会绕过 RLS,但 DSL 函数仍会通过显式过滤器将作用域限制为调用用户。避免以纯文本形式存储机密信息 ## 持续集成 所有 Pull Request 在合并前必须通过以下检查: 1. **格式检查** — `cargo fmt --check` 2. **Clippy 和测试** — `cargo clippy`、单元测试 (`cargo pgrx test pg17`)、pg_regress 测试和 E2E 测试 CI workflow 定义在 [.github/workflows/ci.yml](.github/workflows/ci.yml) 中。它使用 pgrx 来下载和管理 PostgreSQL。 ## 测试 pg_durable 有两个测试套件: ### pg_regress 测试(标准 PostgreSQL 回归测试) 使用 PostgreSQL 的标准测试框架,对核心 DSL 功能进行快速、确定性的测试。 测试 SQL 位于 `sql/` 中,预期输出位于 `expected/` 中,PGXS 在根目录的 `Makefile` 中进行配置。 ``` make test-regress # full reset + run make installcheck # run only (PostgreSQL must already be running) ``` ### E2E 测试(全面场景测试) 使用 pgrx PostgreSQL 进行复杂的本地集成测试: ``` ./scripts/test-e2e-local.sh # All local SQL E2E tests, including special restart/config phases ./scripts/test-e2e-local.sh 04_parallel # Specific test ./scripts/test-e2e-local.sh --default-build-phases # Only the default-build phase group ``` 详情请参阅 [tests/e2e/](tests/e2e/)。 ## 文档 - [用户指南](USER_GUIDE.md) — 包含示例的完整使用指南 - [MVP 指南](docs/pg_durable_mvp.md) — 实现细节和内部机制 - [示例](examples/README.md) — 示例规范和冒烟测试指南 ## 架构 pg_durable 是一个 PostgreSQL 扩展(使用 [pgrx](https://github.com/pgcentralfoundation/pgrx) 构建)— 一切都在 PostgreSQL 服务器内部运行,无需外部服务。该扩展暴露了一个用于构建函数图的 SQL DSL,并注册了一个后台 worker,该 worker 在两个底层 Rust 库之上持久地执行它们: - [duroxide](https://github.com/microsoft/duroxide) — 一个提供编排 runtime(确定性重放、checkpoint、子编排、计时器)的持久化任务框架。 - [duroxide-pg](https://github.com/microsoft/duroxide-pg) — duroxide 的基于 PostgreSQL 的状态提供者。它将 runtime 状态(实例、历史记录、工作队列)持久化保存在扩展拥有的专用 `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 — 当您更倾向于使用 SQL 编写时,pg_durable 就是您在这两者之上构建的结果。 ## 状态 **预览版** - 此项目目前处于预览阶段。 ## 支持 使用 GitHub Issues 提交 bug 报告和功能请求。请勿通过公开的 GitHub issues 报告安全漏洞;请按照 [SECURITY.md](SECURITY.md) 中的说明进行操作。 ## 行为准则 本项目已采纳[微软开源行为准则](https://opensource.microsoft.com/codeofconduct/)。了解更多信息,请参阅[行为准则常见问题](https://opensource.microsoft.com/codeofconduct/faq/),如有任何问题或意见,请联系 [opencode@microsoft.com](mailto:opencode@microsoft.com)。 ## 安全 微软非常我们的软件产品和服务的安全性。请勿通过公开的 GitHub issues 报告安全漏洞。有关安全报告说明,请参阅 [SECURITY.md](SECURITY.md)。 ## 隐私与遥测 pg_durable 不会向微软发送遥测数据。 ## 商标 本项目可能包含项目、产品或服务的商标或徽标。授权使用微软商标或徽标受[微软商标和品牌指南](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general)约束并必须遵循该指南。在本项目的修改版本中使用微软商标或徽标不得引起混淆或暗示微软的赞助。任何使用第三方商标或徽标的行为均受该第三方政策约束。 ## 许可证 PostgreSQL License
标签:AI数据管道, PostgreSQL扩展, 可视化界面, 后端开发, 容错机制, 工作流引擎, 持久化执行, 数据库, 测试用例, 请求拦截