pg_durable: Durable Workflows in PostgreSQL vs Temporal and Cron+Queue
Microsoft's pg_durable brings durable workflow execution inside PostgreSQL. Here is the syntax, what it replaces, and an honest comparison against Temporal and cron+queue patterns.
On this page
Background jobs that need to survive a crash are one of those problems every backend team eventually reinvents from scratch. You build a jobs table, add status columns, write a polling worker, bolt on retry counters, and six months later you are debugging why a job got stuck in "processing" forever because the worker died mid-update.
Microsoft open-sourced pg_durable in early June 2026 to attack this problem from a different angle: define the workflow in SQL, and let PostgreSQL itself handle checkpointing, retries, and crash recovery. No separate orchestrator service. No queue infrastructure. The state lives in the same database as everything else you already trust with your data.
Here is what it actually does, and an honest comparison against the patterns you are probably already using.
What Problem pg_durable Solves
The pattern everyone reinvents: background work that must survive a crash, restart, or failed step, with retries, checkpointing, and progress tracking. Currently solved with cron jobs plus status tables, external orchestrators like Temporal or AWS Step Functions, or hand-rolled plpgsql procedures with manual retry counters.
pg_durable replaces all of that with a SQL-native DSL. You define workflows as composable SQL expressions using three operators, and PostgreSQL handles the rest: checkpoint after every step, resume from the last completed step on crash, and expose the workflow state through ordinary SQL tables.
The Actual Syntax
A minimal example, straight from the project's own quick start:
SELECT df.start(
'SELECT now() as step1' |=> 't1'
~> 'SELECT pg_sleep(5)'
~> 'SELECT now() as step2' |=> 't2'
~> 'SELECT now() as step3' |=> 't3',
'sequential-timing'
) as i;Three operators do the work. ~> sequences one step after another. |=> gives a step's result a name you can reference later. & runs two branches in parallel:
SELECT df.start(
('SELECT now() as branch1' |=> 'b1' ~> 'SELECT pg_sleep(20)')
& ('SELECT now() as branch2' |=> 'b2' ~> 'SELECT pg_sleep(10)')
~> 'SELECT now() as after_join' |=> 'final',
'parallel-sleep'
) as i;You query the workflow's progress and result with ordinary SQL:
SELECT * FROM df.instance_nodes('your-instance-id');
SELECT * FROM df.instance_executions('your-instance-id');| Operator | Purpose | Example |
|---|---|---|
| <code>~></code> | Sequence: run this step after the previous one completes | 'SELECT step1' ~> 'SELECT step2' |
| <code>|=></code> | Name: label this step's result for later reference | 'SELECT now()' |=> 'timestamp' |
| <code>&</code> | Parallel: run two branches concurrently, join before continuing | (branch1) & (branch2) ~> 'after-join' |
What You Get for Free
Each step checkpoints automatically. If the process crashes mid-workflow (server restart, OOM kill, whatever), resuming picks up from the last completed step, not from the beginning. You are not writing that retry logic by hand.
Operational visibility comes from regular tables: df.instances, df.instance_nodes, df.instance_executions. No separate monitoring dashboard to stand up. The same SQL client and backup tooling you already use for your application data works here too.
Security and access control inherit from PostgreSQL directly. The workflow state is just data in your database, covered by your existing RBAC roles, backup schedules, and encryption-at-rest configuration.
pg_durable vs Temporal
Temporal is mature, proven at scale, and built for the general case: arbitrary application logic, calling out to multiple heterogeneous systems, complex branching and loops in whatever language your services are written in. If your workflow needs to call five different microservices with substantial conditional logic between steps, Temporal's general-purpose execution model is the better fit. pg_durable is specifically a SQL-native tool, and forcing complex non-SQL logic into it works against the grain.
pg_durable's case is narrower and specific: when the workflow steps are already SQL statements (or close to it), and you would rather not run, monitor, and keep available a second piece of infrastructure just to get durability for jobs that mostly touch your own database anyway.
| pg_durable | Temporal | |
|---|---|---|
| Workflow language | SQL with custom operators | Any language (Go, Java, Python, TS, etc.) |
| Infrastructure | PostgreSQL extension (no extra services) | Separate Temporal server + workers |
| Best for | SQL-native workflows within one database | Multi-system orchestration with complex logic |
| Checkpointing | Per-step, in Postgres tables | Per-activity, in Temporal's own storage |
| Monitoring | SQL queries against df.* tables | Temporal Web UI + SDK metrics |
| Maturity | New (June 2026, open-source) | Battle-tested (years of production use) |
pg_durable vs Cron + Jobs Table
This is the comparison that matters for most teams, because the cron+queue pattern is what almost everyone already has: a jobs table, status columns (pending, processing, failed, done), retry counters, and a worker process polling for work.
pg_durable's pitch is that this entire layer (the worker, the queue consumer, the scheduler glue) can disappear. Retry state and checkpointing move into Postgres tables the project maintains for you, instead of bespoke application code your team owns and debugs.
The job that used to be a single INSERT ... SELECT or one ordinary SQL statement might not need pg_durable at all. The project's own documentation explicitly lists "the problem is a single SQL statement" as a case where you do not need it.
When pg_durable Is the Wrong Choice
Worth being direct about this, straight from the project's own scoping:
- Sub-millisecond synchronous request handling: pg_durable is for durable background execution with checkpointing overhead, not your hot request path
- Cannot install extensions: if your managed Postgres tier does not allow custom extensions or background workers, pg_durable cannot run
- Multi-system workflow orchestration: if your workflow calls five microservices with complex conditional logic, branching, and loops in application code, that is Temporal/Step Functions/Argo territory
- Single SQL statement: if the job is one query, you do not need a workflow orchestrator at all
Trying It Locally
Docker quick start using the official image:
docker run -d --name pg-durable-demo \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
ghcr.io/microsoft/pg_durable:latest-pg18Run the hello-world example:
SELECT df.start(
'SELECT ''Hello, durable world!'' AS message',
'hello-world'
) as i;Query the result with SELECT df.result('your-instance-id'); and inspect the checkpoint structure with df.instance_nodes().
If you use GitHub Actions for CI, you can add the pg_durable Docker image as a service container in your workflow to test durable workflows alongside your integration tests.
The Honest Verdict
This is genuinely new, open-sourced in early June, with Microsoft already running it inside Azure HorizonDB as a signal of internal confidence, but without years of broad production track record yet.
If you are a Postgres-heavy shop dealing with the specific pain of "background jobs that need to survive crashes reliably," it is worth experimenting with now. It is not yet an obvious default replacement for Temporal in complex, multi-system workflow scenarios, and teams with critical infrastructure should weigh the relative newness against the appeal of removing a layer of infrastructure.
For teams currently managing a cron+queue pattern that handles a handful of multi-step SQL workflows, pg_durable solves exactly the pain they are carrying. For teams running Temporal for complex cross-service orchestration, this is not a replacement. It is a complement for the subset of workflows that happen to be SQL-native. Like any caching strategy, the right answer depends on your specific workload shape.
Frequently Asked Questions
What is pg_durable?
pg_durable is a PostgreSQL extension open-sourced by Microsoft in June 2026 that brings durable, fault-tolerant workflow execution directly inside the database. Workflows are defined using a SQL-native DSL with three composable operators (~> for sequencing, |=> for naming, & for parallel branches). Each step checkpoints automatically, so a crash mid-workflow resumes from the last completed step.
Microsoft ships it inside Azure HorizonDB and open-sourced it on GitHub for self-hosted use.
Does pg_durable replace Temporal?
Not for most use cases. Temporal is built for arbitrary application logic across heterogeneous systems with complex branching, loops, and multi-language support. pg_durable is specifically for SQL-native workflows that run inside your PostgreSQL database.
If your workflow steps are mostly SQL statements touching your own database, pg_durable can replace the need for a separate orchestrator. If your workflow calls multiple external services with substantial conditional logic, Temporal is the better fit.
Can pg_durable survive a database restart?
Yes. Every step checkpoints to PostgreSQL tables. After a crash, restart, or OOM kill, the workflow resumes from the last completed step. This is the core value proposition: crash recovery without writing your own retry and checkpoint logic.
What database operations can pg_durable orchestrate?
Any SQL statement that PostgreSQL can execute. Each step is a SQL expression string. You can run SELECT, INSERT, UPDATE, DELETE, function calls, or any other valid SQL. Steps can reference the named results of previous steps using the |=> operator.
The limitation is that each step must be expressible as SQL. If a step requires arbitrary application logic (HTTP calls, file system operations, complex branching in Python/Go/Java), it does not fit pg_durable's model.
Is pg_durable production-ready?
It is very new. Microsoft open-sourced it in early June 2026 and ships it inside Azure HorizonDB, which signals internal confidence, but there is no broad multi-year production track record yet from the wider community.
For experimental branches and non-critical background workflows, it is worth trying now. For critical infrastructure, weigh the newness against the benefits and consider waiting for the ecosystem to mature.
Can I use pg_durable on managed PostgreSQL (RDS, Cloud SQL)?
Most managed PostgreSQL services (AWS RDS, Google Cloud SQL, Supabase) do not allow custom extensions or background workers, which pg_durable requires. Azure HorizonDB is the notable exception, as Microsoft ships pg_durable as a built-in feature.
For self-hosted PostgreSQL or managed services that support custom extensions, pg_durable can be installed normally.
Related Articles
GitHub Actions Tutorial: CI/CD from Push to Deploy (2026)
Learn GitHub Actions: write your first workflow, run tests automatically, use secrets safely, deploy via SSH, cache dependencies, and run matrix builds.
40 SQL Interview Questions and Answers (2026)
40 SQL and relational database interview questions covering joins, indexes, ACID, window functions, CTEs, MySQL vs PostgreSQL, and query challenges.