pg_durable 0.2.6

This Release
pg_durable 0.2.6
Date
Status
Stable
Abstract
SQL-native durable orchestrations for PostgreSQL
Description
pg_durable provides in-database durable execution for PostgreSQL. Durable functions are defined in SQL using composable operators, and their state is persisted to PostgreSQL so that workflows survive crashes, restarts, and failovers. The runtime executes inside a PostgreSQL background worker, with first-class primitives for scheduling, conditions, retries, HTTP activities, and parallel execution. No external orchestrator, queue, or scheduler is required.
Released By
Pino
License
PostgreSQL
Resources
Special Files
Tags

Extensions

pg_durable 0.2.6
SQL-native durable orchestrations for PostgreSQL

Documentation

grammar
Durable Functions Grammar
ARCHITECTURE
pg_durable Architecture: Function Graph Build & Execution
pg_durable-merge-main
Merge Branch to Main
DUROXIDE_PG_DEADLOCK_ISSUE
Duroxide-PG Deadlock Issue in Parallel Orchestrations
README
Invoice Approval Pipeline — pg_durable Demo
user-isolation
User Isolation
pg_durable_mvp
pg_durable MVP User Guide
pg_durable-update-docs-tests
Update Documentation and Tests After Code Changes
pg_durable-check-upstream-fixes
Check Upstream Fixes and Update DependenciesEdit Cargo.toml: duroxide-pg = “0.1.X”
http_problems
Open Problems in the HTTP Activities
TESTING
pg_durable Testing Guide
SECURITY
SECURITY
copilot-instructions
pg_durable AI Coding Instructions
requirements
requirements
SKILL
pg_durable SQL Generation
README
Azure Functions + pg_durable Example
README
PostgreSQL Durable Extension – Vacuum, Bloat, and Wraparound Scenarios
README
Examples
feedback
pg_durable Feedback & Feature Requests
index
pg_durable — Durable SQL functions for PostgreSQL
spec-compensation
Compensation & Saga Pattern
extension_lifecycle
pg_durable Extension Lifecycle and Background Worker
superuser_guc
Superuser Submission GUC
TODO
TODO
E2E_TESTING
E2E Testing Guide for pg_durableVerify Docker is installed and runningInstall via rustup if needed
spec-signals
Signals Specification
named-results-v2
Named Result Improvements — Dot-Notation, Row Sets, Null Safety
multi-database
Multi-Database Support
design-azure-functions
Design: Azure Functions Integration for AI Workloads
move-duroxide-schema
Move Duroxide Provider Schema
without-df
Building the Invoice Pipeline Without pg_durable
pg_durable-release
Release WorkflowMerged, user-facing changes since the previous tagResolve a squashed commit to its PR when the number isn’t in the subject
security-review
pg_durable Security Review
README
Function Design: chunk_text (Python)
api-reference
API Reference
http-security
HTTP Security in pg_durable
pg_durable_spec
pg_durable
upgrade-testing
Upgrade Testing Plan
pg_durable-clean-warnings
Clean Up Compiler and Linter Warnings
CONTRIBUTING
Contributing
parameterized_queries_proposal
Proposal: Parameterized Queries (Status: Not Implemented)
spec-http-function-permissions
Spec: df.http() Function Permissions
spec-http-support
Spec: HTTP Support for pg_durable
ThreatModelDFD
pg_durable Threat Model — DFD Analysis
spec-pg-regress
pg_regress Test Suite Specification
LICENSE
LICENSE
README
Setup Scripts
pg_durable-create-scenario-test
Create Scenario Test for Durable SQL Function
SCENARIOS
pg_durable Scenarios Guide
CODESPACES_PREBUILDS
GitHub Codespaces Pre-builds Configuration
README
SQL Files for Azure Functions Scenario
CHANGELOG
Changelog
README
AI Prompts for pg_durable Development
workbook-data
pg_durable — Threat Model Workbook Data
rls
Row-Level Security (RLS) Design
bgw-applies-migrations
BGW-Managed Duroxide Migrations
nested-graph-design
DSL Graph Construction Refactor: Nested JSON Design
loop_rework_problems
Open Problems in the Loop Rework
dep_issues
Dependency Issues & Blockers
README
Azure HTTP Domain Tests for pg_durable
spec-security-model
pg_durable Security Model Specification
README
pg_durable Website
CODE_OF_CONDUCT
Microsoft Open Source Code of Conduct
requirements
requirements
proposal-management-api
Proposal: Instance Management API for pg_durable
README
Audio Round Trip
SCENARIOS_DESIGN
pg_durable Operational Scenarios – Design & Behavior Spec

README

pg_durable logo [Website](https://microsoft.github.io/pg_durable/) · [Docs](docs/) · [Quick Example](#quick-example) · [GitHub](https://github.com/microsoft/pg_durable) [![License](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/) ## Durable Execution inside PostgreSQL

Long-running, fault-tolerant SQL functions for teams that already keep their state in Postgres and want to stop stitching together cron jobs, workers, queues, and status tables to make background work reliable. Define the workflow in SQL, let pg_durable checkpoint each step, and resume after crashes, restarts, or failed steps.

Durable execution is now a standard industry pattern, and pg_durable brings it inside Postgres with no extra service infrastructure required. Part of our mission to bring compute close to data.

Azure HorizonDB logoTry pg_durable now in Azure HorizonDB, Microsoft’s new PostgreSQL cloud service engineered for performance and built with pg_durable inside

Is this for me?

Who it’s for

  • Backend and data engineers who want workflows to live next to the data they touch.
  • DBAs and SREs automating runbooks that must survive restarts and be auditable in SQL.
  • Teams building data or AI pipelines that need durable execution per row, document, or batch.

The core idea

A pg_durable function is a graph of SQL steps that PostgreSQL executes and checkpoints as it goes. If the database crashes, restarts, or a step fails, execution resumes from the last durable checkpoint instead of making you reconstruct state by hand.

A durable function fans out into three parallel queries — count users, count orders, sum revenue — that join into a dashboard step

Workloads this is useful for

  • Vector embedding pipelines: chunk, call an embedding API, and upsert into pgvector.
  • Ingest pipelines: stage, deduplicate, transform, and publish large batches.
  • Scheduled maintenance: detect bloat, notify, wait for approval, then run the next action.
  • Fan-out aggregation: run independent queries in parallel, then join the results.
  • External API workflows: enrichment, classification, and webhook-style calls from SQL.

What you’re probably doing today instead

  • pg_cron plus a jobs table, status columns, retry counters, and a polling worker.
  • An external orchestrator such as Airflow, Temporal, Step Functions, or Argo calling back into Postgres.
  • A queue plus workers plus a separate state table to coordinate retries and partial completion.
  • A plpgsql procedure that works until a crash or long-running transaction forces you to start over.

Pain points it addresses

  • A restart in the middle of a long job means rerunning work that already succeeded.
  • One failed row or one failed API call turns into manual cleanup and uncertain replay.
  • Long transactions hold locks, grow WAL, and make batch jobs fragile at larger scale.
  • Parallel work in the app tier creates more places for partial-failure bugs and drift.
  • The workflow logic ends up spread across SQL, workers, queues, dashboards, and status tables.

What changes in your architecture

  • The workflow definition moves into SQL and starts with df.start(...).
  • Retry state, progress tracking, and checkpointing move into Postgres instead of bespoke app code.
  • Some app-tier workers, queue consumers, or scheduler glue can disappear entirely.
  • Operational visibility comes from Postgres tables such as df.instances, using the same auth and backup model as your data.

When not to use it

  • The job is already a single INSERT ... SELECT or one ordinary SQL statement.
  • You need sub-millisecond synchronous request handling rather than durable background execution.
  • You cannot install extensions or run a background worker in your Postgres environment.
  • The workflow mostly lives outside Postgres and spans many heterogeneous systems.
  • You need arbitrary application logic that does not map cleanly to SQL steps, branching, loops, or HTTP calls.

How it works

  1. Define a workflow in SQL using composable operators such as ~> and |=>.
  2. Start it with df.start() and get back an instance ID.
  3. Let the runtime execute each step durably with checkpointing between steps.
  4. Query status and results from PostgreSQL while the workflow runs or after it completes.

Limitations

The model is intentionally SQL-shaped. If a step needs arbitrary code, a non-HTTP SDK, or rich in-memory control flow, you may need to wrap that logic in a SQL function, expose it behind an HTTP endpoint for df.http(), or use a general-purpose orchestrator for that part of the system.

Features

  • Durable — Function state persists to PostgreSQL. Survives crashes, restarts, and failovers.
  • SQL-native — Define functions in SQL using composable operators.
  • Database-aware — First-class primitives for scheduling, conditions, and parallel execution.
  • Zero infrastructure — Runs as a PostgreSQL extension. No Redis, no Temporal, no external services.

Quick Example

-- 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 IN (SELECT id FROM $batch.*)'
);

Packages

Tagged releases publish Debian packages for PostgreSQL 17 and 18 on amd64 from the GitHub release assets. Packages are named pg-durable-postgresql-<PG major>_<pg_durable version>-1_<arch>.deb and install the extension library, control file, and SQL upgrade files into the matching PostgreSQL installation directories.

Tagged releases also publish a ready-to-run Docker image (linux/amd64) for PostgreSQL 17 and 18 to GitHub Container Registry: ghcr.io/microsoft/pg_durable. The image installs the released Debian package on top of the official postgres image. Each release publishes immutable X.Y.Z-pg<major> and vX.Y.Z-pg<major> tags (for example 0.2.2-pg17, 0.2.2-pg18); the highest stable release additionally updates the floating pg<major> tags, and the default major (pg17) also updates latest. The PG major version is part of every tag so multiple PostgreSQL versions can be published alongside each other. Browse all published images and tags at https://github.com/microsoft/pg_durable/pkgs/container/pg_durable.

Warning: The published Docker image is intended for evaluating and learning pg_durable only — do not use it in production. It enables superuser durable instances for a frictionless out-of-the-box demo. Its HTTP egress policy is whatever the released Debian package was built with (http-allow-azure-domains — Azure domains only). Multi-arch (linux/arm64) images are not published yet; they will follow once arm64 Debian packages are available.

Run the published image — PostgreSQL 17 and 18 can run side by side on different host ports:

# PostgreSQL 17 (the `latest` tag also points at the newest PG17 release)
docker run -d --name pg_durable_pg17 \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=secret \
  ghcr.io/microsoft/pg_durable:pg17

# PostgreSQL 18 (run alongside PG17 on a different host port)
docker run -d --name pg_durable_pg18 \
  -p 5433:5432 \
  -e POSTGRES_PASSWORD=secret \
  ghcr.io/microsoft/pg_durable:pg18

# Connect with psql (PG17 on 5432, PG18 on 5433)
psql "postgresql://postgres:secret@localhost:5432/postgres"
psql "postgresql://postgres:secret@localhost:5433/postgres"

The extension is preloaded and created in the postgres database on first init. POSTGRES_DB is ignored — pg_durable always installs into postgres so the extension and the background worker never target different databases. For reproducible deployments, pin an immutable X.Y.Z-pg<major> tag (for example 0.2.2-pg17) rather than the floating pg<major>/latest tags; immutable tags are never overwritten once published.

After installing a package, add pg_durable to shared_preload_libraries, restart PostgreSQL, and create the extension in the configured pg_durable database:

CREATE EXTENSION pg_durable;

The default pg_durable database is postgres; see User Guide for background worker configuration and privilege setup.

Each release also publishes source archives and a SHA256SUMS file. To build and install from a source archive, initialize cargo-pgrx for the target PostgreSQL installation, build the package as your normal user, then install the generated artifacts with elevated privileges:

export PG_CONFIG=/usr/lib/postgresql/17/bin/pg_config
cargo pgrx init --pg17 "$PG_CONFIG"
make PG_CONFIG="$PG_CONFIG"
sudo make install PG_CONFIG="$PG_CONFIG"

Source installation is supported on Linux and macOS for PostgreSQL 17 and 18. Windows source installation is not currently supported. Set EXTRA_FEATURES on the build command to enable an HTTP policy feature. DESTDIR may be set on make install when staging files for a package.

sudo make uninstall PG_CONFIG="$PG_CONFIG" removes the installed files again. It needs no build, so it also works from an unbuilt source tree.

The extension is also distributed on PGXN, the PostgreSQL Extension Network. PGXN carries the source distribution, built and installed exactly as described above, so it needs the Rust toolchain and cargo-pgrx. For prebuilt binaries use the Debian packages or the Docker image.

Development Installation

Prerequisites

  • PostgreSQL 17 or 18
  • Rust (stable)
  • cargo-pgrx 0.16.1

GitHub Codespace

The main branch prebuild installs PostgreSQL 17, builds pg_durable, and prepares a local cluster under ~/.pgrx with the extension ready. PostgreSQL is not left running, so start it when you begin working.

# Start PostgreSQL
./scripts/pg-start.sh

# Connect
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres

On a branch without a ready prebuild, run pg-start.sh — it will build and install the extension on first run (expect a few minutes):

./scripts/pg-start.sh

Other environments

Local and Dev Container

A VS Code Dev Container (.devcontainer/) provides Rust, cargo-pgrx, and PostgreSQL 17 pre-installed. For a bare local machine, install the toolchain first by following the steps in .devcontainer/onCreateCommand.sh.

# Build, initialize PostgreSQL, and install the extension
# This takes a while - go do something else
./scripts/pg-start.sh

# Connect to the local pgrx PostgreSQL instance
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres

pg-start.sh bootstraps new local data directories with a postgres superuser and also creates a matching superuser role for the current OS user, so default local psql usage continues to work. Use -U postgres if you want to force the canonical bootstrap role explicitly.

Docker

To run the prebuilt published image, see the Packages section. For local development and testing, build and run from source:

# Build and test (source Dockerfile — compiles the extension)
./scripts/test-e2e-docker.sh --rebuild

# Optional: Deploy to ACR (for a custom PG17 image with pg_durable baked-in)
./scripts/deploy-acr.sh

The published GHCR image installs the released .deb on top of the official postgres image; the source Dockerfile used here compiles the extension and is meant for CI and local development. They are different artifacts.

Multi-User Setup

CREATE EXTENSION pg_durable does not grant any privileges to PUBLIC. After installing the extension, the admin must explicitly grant access to application roles. Row-level security (RLS) ensures each user can only see and manage their own durable function instances and nodes.

Grant privileges to an application role:

-- Grant to specific roles after CREATE EXTENSION
SELECT df.grant_usage('app_role');

Alternatively, create an indirection role and grant membership to application roles:

-- 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;

See the User Guide — Privilege Grants section for the full list of individual grants, revoking access, and hardening upgraded installs.

Note: GRANT EXECUTE ON ALL FUNCTIONS only applies to functions that exist when the grant runs. After upgrading pg_durable with ALTER EXTENSION pg_durable UPDATE, re-run df.grant_usage('role') (or re-issue the manual grants) so new functions are accessible.

Key points: - The background worker role (pg_durable.worker_role GUC, default: postgres) must be a superuser — it bypasses RLS to manage all users' instances - Users get SELECT + INSERT on df.instances / df.nodes, column-level UPDATE (status, updated_at) on instances for df.cancel() - Identity column (submitted_by) cannot be modified by users - df.vars uses per-user scoping — each user has their own variable namespace via an owner column and RLS. Superusers bypass RLS but DSL functions still scope to the calling user via explicit filters. Avoid storing secrets in plain text

Continuous Integration

All pull requests must pass the following checks before merging:

  1. Format Checkcargo fmt --check
  2. Clippy & Testscargo clippy, unit tests (cargo pgrx test pg17), pg_regress tests, and E2E tests

The CI workflow is defined in .github/workflows/ci.yml. It uses pgrx to download and manage PostgreSQL.

Testing

pg_durable has two test suites:

pg_regress Tests (Standard PostgreSQL Regression Tests)

Fast, deterministic tests for core DSL functionality using PostgreSQL’s standard testing framework. Test SQL lives in sql/, expected output in expected/, and PGXS is configured in the root Makefile.

make test-regress          # recommended: reset the dedicated local cluster and run
make installcheck          # advanced: run against a disposable configured server

Direct installcheck drops and recreates its regression database. It also requires explicit connection settings when PostgreSQL is not on the default socket and port. See Testing for the full command and server prerequisites.

E2E Tests (Comprehensive Scenario Tests)

Complex local integration tests with 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

See tests/e2e/ for details.

Documentation

  • User Guide — Complete usage guide with examples
  • MVP Guide — Implementation details and internals
  • Examples — Example conventions and smoke-check guidance

Architecture

pg_durable is a PostgreSQL extension (built with pgrx) — everything runs inside the PostgreSQL server, no external services. The extension exposes a SQL DSL for building function graphs and registers a background worker that executes them durably on top of two lower-level Rust libraries:

  • duroxide — a durable task framework providing the orchestration runtime (deterministic replay, checkpoints, sub-orchestrations, timers).
  • duroxide-pg — a PostgreSQL-backed state provider for duroxide. It persists runtime state (instances, history, work queues) in a dedicated duroxide.* schema owned by the extension.
┌────────────────────────────────────────────────────────────────────┐
│                             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)               │
└────────────────────────────────────────────────────────────────────┘

If you’d rather author durable functions in Rust, Python, or Node while still persisting state in PostgreSQL, you can use duroxide and duroxide-pg directly from your host language — pg_durable is what you’d build on top of that pair when you’d prefer authoring in SQL.

Status

Preview - This project is currently in preview.

Support

Use GitHub Issues for bug reports and feature requests. Do not report security vulnerabilities through public GitHub issues; follow the instructions in SECURITY.md instead.

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact opencode@microsoft.com with questions or comments.

Security

Microsoft takes the security of our software products and services seriously. Please do not report security vulnerabilities through public GitHub issues. See SECURITY.md for security reporting instructions.

Privacy and Telemetry

pg_durable does not send telemetry to Microsoft.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos is subject to those third-party policies.

License

PostgreSQL License