Hotdry.

Article

pg_durable 确定性重放执行机制:幂等重放与状态一致性工程实践

解析 pg_durable 的确定性重放架构,涵盖执行图持久化、activity 级 checkpoint、幂等重放语义及生产环境配置要点。

2026-06-06systems

背景:长事务的崩溃恢复困境

在 PostgreSQL 中执行长时间运行的数据处理任务时,一个核心痛点是崩溃恢复的语义粒度。传统的 PL/pgSQL 过程或批处理脚本在数据库重启后必须从头重跑,即使 99% 的工作已经成功提交。这种 "全有或全无" 的恢复模型不仅浪费计算资源,更会在重放时引入副作用风险 —— 例如重复插入、重复调用外部 API,或触发下游系统的幂等性冲突。

微软开源的 pg_durable 扩展将 "持久化执行"(durable execution) 模式引入数据库内核,通过确定性重放 (deterministic replay) 机制解决上述问题。其核心承诺是:每个执行步骤都被持久化为 checkpoint,崩溃后从最后一个成功点恢复,而非全量重跑。

执行图与确定性重放架构

pg_durable 的核心抽象是执行图(function graph),由用户通过 SQL DSL 定义。DSL 提供顺序 (~>)、并行 (durable.join)、条件 (durable.if)、循环 (durable.loop) 等组合算子,每个叶子节点对应一个 SQL 查询或 HTTP 调用。

执行图的元数据存储于 PostgreSQL 的 durable.nodesdurable.instances 表中,而运行时状态则由底层的 duroxide 框架管理。duroxide 是一个 Rust 实现的持久化任务编排引擎,其关键设计是将每个执行单元封装为 activity—— 一个带有确定性输入输出的计算步骤。

当用户调用 durable.start() 启动工作流时,pg_durable 的后台 worker(以 PostgreSQL background worker 形式运行)加载执行图并按深度优先顺序执行。每个 activity 的输入参数、执行结果和完成状态被序列化后写入 SQLite 存储(由 duroxide-pg 组件管理),形成可重放的事件日志

幂等重放机制的实现

确定性重放的核心是幂等性保证:同一个 activity 在重放时必须产生与首次执行完全一致的结果。duroxide 通过以下机制实现:

Activity 级别的缓存与恢复

首次执行时,activity 运行并将结果写入 SQLite 的 history 表,键由 activity 类型、输入参数的哈希和实例 ID 组合而成。当 PostgreSQL 崩溃后重启,后台 worker 扫描 durable.instances 表中状态为 running 的实例,重新加载对应的执行图。

重放过程中,duroxide 在调度每个 activity 前首先查询 history:若记录存在,直接返回缓存结果而不重新执行 SQL 查询或 HTTP 调用;若不存在,则视为首次执行。这种 "缓存优先" 策略确保了已完成的步骤不会重复运行,从根本上消除了副作用风险。

执行状态的持久化边界

pg_durable 将执行状态分层管理:

  • 执行图结构:存储于 PostgreSQL 的 durable.nodes,包含节点类型、查询文本、左右子节点引用
  • 运行时状态:存储于 duroxide 内部的 SQLite,包含 activity 的输入输出、完成标记、时间戳
  • 实例元数据durable.instances 表记录实例 ID、标签、根节点、当前状态

这种分层设计使得执行逻辑与运行时状态解耦:PostgreSQL 负责事务性存储和并发控制,SQLite 提供轻量级的本地状态缓存以支持快速重放。

工程实现要点

后台 Worker 与权限模型

pg_durable 的后台 worker 以 superuser 身份运行,这是实现多租户隔离的关键设计。worker 需要绕过行级安全 (RLS) 以管理所有用户的 durable function 实例。应用层用户通过 df.grant_usage('app_role') 获得对 df.instancesdf.nodes 的访问权限,但 worker 本身以更高特权执行,确保崩溃后能够恢复任意用户的实例。

变量作用域与数据传递

执行图通过命名变量 (|=> 'var_name') 实现步骤间数据传递。pg_durable 在 df.vars 表中维护变量命名空间,每个用户拥有独立的作用域(通过 owner 列和 RLS 实现)。在重放时,变量值从 activity 的缓存结果中重建,确保下游步骤接收到与首次执行完全一致的数据。

监控与可观测性

pg_durable 提供 SQL 接口查询执行状态:

-- 查看实例执行状态
SELECT * FROM durable.instance_info('abc12345');

-- 可视化执行图
SELECT durable.explain('abc12345');

durable.explain 返回执行树的文本表示,标记每个节点的完成状态(✓ 已完成、✗ 未执行、→ 进行中),便于运维人员快速定位崩溃恢复后的执行进度。

适用边界与权衡

pg_durable 的执行模型是SQL-shaped的,这意味着它最适合表达为 SQL 步骤、分支、循环和 HTTP 调用组合的工作流。对于需要任意应用逻辑、非 HTTP SDK 或复杂内存状态的场景,官方建议将此类逻辑封装为 SQL 函数或暴露为 HTTP 端点供 df.http() 调用。

另一个关键限制是后台 worker 的 superuser 要求。在多用户环境中,需要谨慎配置 pg_durable.worker_role GUC 并确保 RLS 策略正确设置,以防止权限提升风险。

总结

pg_durable 通过将确定性重放机制引入 PostgreSQL,为长事务处理提供了细粒度的崩溃恢复能力。其核心在于 activity 级别的 checkpoint 与幂等重放语义:已完成的步骤在恢复时直接返回缓存结果,避免副作用;未完成的步骤从断点继续执行,无需全量重跑。

对于需要高可靠性数据处理管道的团队,pg_durable 提供了一种 "零外部依赖" 的持久化执行方案 —— 无需 Redis、Temporal 或 Airflow,仅通过 PostgreSQL 扩展即可实现故障恢复、状态监控和执行编排的完整闭环。


参考来源

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com