pg_durable (GitHub Repo)
Microsoft released pg_durable, a PostgreSQL extension that enables durable, fault-tolerant workflow orchestration directly inside the database using a custom SQL DSL.
Deep dive
- pg_durable provides a SQL DSL for defining stateful workflow graphs using operators like ~> and |=>.
- It enables durable execution by checkpointing function progress directly into Postgres tables, allowing resumes after crashes.
- Built using the pgrx framework for Rust-based PostgreSQL extension development.
- It includes dedicated schemas (df.* for definitions, duroxide.* for internal state) to manage workflow life cycles.
- Designed for tasks like vector embedding pipelines, batch data ingestion, and multi-step background maintenance.
- Integrates row-level security for multi-tenant environments.
- Requires PostgreSQL 17 or 18; no external services like Redis or dedicated orchestrators are needed.
- Supports both SQL-native definition and raw Rust integration via the duroxide crate.
Decoder
- Durable Execution: A pattern where a task's state is persisted at every step so that if a process crashes, it can resume from the exact point of failure instead of restarting from the beginning.
- Fan-out: An architectural pattern where one task triggers multiple independent parallel tasks, often collecting their results at a later join point.
- SQL DSL: A domain-specific language built using SQL syntax to express logic, in this case, workflow control flow.
- pgrx: A framework for developing PostgreSQL extensions using the Rust programming language.
Original article
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.
Try 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.
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_cronplus 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
plpgsqlprocedure 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 ... SELECTor 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
- Define a workflow in SQL using composable operators such as
~>and|=>. - Start it with
df.start()and get back an instance ID. - Let the runtime execute each step durably with checkpointing between steps.
- 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 = ANY($batch)'
);
Packages
Tagged releases publish Debian packages for PostgreSQL 17 and 18 on amd64 from the GitHub release assets. 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;
Development Installation
Prerequisites
- PostgreSQL 17 or 18
- Rust (nightly)
- cargo-pgrx 0.16.1
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.
Grant privileges to an application role:
-- Grant to specific roles after CREATE EXTENSION
SELECT df.grant_usage('app_role');
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) │
└────────────────────────────────────────────────────────────────────┘
Status
Preview - This project is currently in preview.
Fresh dev news, AI-digested daily. Never miss a trend at devoured.fyi