Postgres Transactions are a Distributed Systems Superpower

DBOS argues that running workflow state and application data in the same Postgres database collapses the idempotency and atomicity problems that separate workflow engines introduce, by reducing distributed coordination to a local transaction.

Postgres Transactions are a Distributed Systems Superpower

Every guide to durable workflows tells you to separate workflow state from application data. That separation is not architectural cleanliness — it is the source of every idempotency bug and outbox reconciliation job you have ever written.

Source

Postgres Transactions are a Distributed Systems Superpower

By: Peter Kraft, Qian Li
Published: Jun 15 2026
Type: Engineering article

The framing is clear and the examples are concrete. Rather than describing the co-location idea abstractly, the post works through the idempotency problem and the outbox pattern as specific code-level scenarios where separation introduces risk that co-location removes entirely.

The transaction as the unit of fault tolerance

Durable workflow engines — Temporal, Conductor, similar systems — achieve fault tolerance by checkpointing the result of each step after it completes. If a workflow is interrupted, it replays from the last checkpoint rather than starting over. This is a solid model, but there is a gap in it: the checkpoint write happens after the step’s database transaction commits. A failure in the window between those two events leaves the system in an ambiguous state. The step ran, the database was mutated, but the engine has no record of it. On recovery, the step runs again.

For idempotent operations this is fine. For a step that credits a bank account or deducts inventory, it is not. The standard remedy is application-level bookkeeping — an applied_payments table or an events_seen set, checked transactionally inside the step to detect whether it already ran. This works but it is boilerplate that must be written individually for every non-idempotent operation in your system. The bookkeeping table is not a feature of the application; it is an artifact of the architectural gap between the workflow engine and the database.

DBOS’s argument is that this gap exists only because the workflow engine and the application database are different systems. When they share the same Postgres instance, the gap closes. The engine provides a database transaction to the step, the step performs its application writes inside that transaction, and the engine records the checkpoint as another write inside the same transaction. The whole thing commits atomically or rolls back entirely. There is no window in which the database was updated but the checkpoint was not, because both operations are part of one atomic unit. This gives transactional steps exactly-once semantics without any bookkeeping: the step either ran and was checkpointed, or it did not run at all.

The atomicity story extends to the transactional outbox pattern as well. The outbox solves the problem of performing two operations across two systems — a database update and a message delivered to a downstream service — without a distributed transaction. You write both to the database (the update and an outbox record), then a separate poller reads the outbox and delivers the message. Either both the update and the outbox record land in the database, or neither does. The poller handles delivery with retries. This is correct but it requires ongoing infrastructure: a polling process, retry logic, monitoring, and occasionally reconciliation jobs to detect cases where a database record was updated without a corresponding outbox entry being delivered.

When workflow state is co-located in the same Postgres database, the outbox poller can be replaced with a Postgres user-defined function. That UDF creates a workflow row — containing the workflow name, its queue, and its input — in the same transaction as the application update. The atomicity guarantee is the same as the outbox: either both records commit or neither does. But the delivery mechanism is not a separate infrastructure component that can drift out of sync; it is a Postgres table being read by a worker that is part of the application. The operational surface area shrinks.

The deeper consequence is a change to the failure model. When the workflow engine and the application database are separate systems, a crash during replication or a network partition during commit can leave them inconsistent with each other. Detecting that inconsistency requires reconciliation jobs comparing workflow state to application state. When everything is in one Postgres instance, Postgres’s own crash recovery — WAL, fsync, a single transaction log — handles all of it. You did not need a distributed commit protocol; you never made a distributed commit.

There is a constraint worth naming. This model works cleanly when a workflow primarily operates on data in one Postgres database. The advantage disappears if a single step needs to atomically modify two separate databases — that is a genuine distributed transaction problem and nothing about co-location helps you avoid it. The pattern is most powerful for workflows that are essentially long-running database transactions with external side effects: order processing, financial transfers within one institution, pipeline stages that transform records from one schema to another. It fits less well for workflows that coordinate across independently owned systems.

The benchmarks that accompany the broader DBOS work show that this is not a theoretical convenience at the cost of performance. Postgres-backed durable execution using this co-location model can handle workloads that would require a dedicated workflow cluster if built conventionally, without adding an operational dependency beyond the database that most applications already run.

My take

The reframe is direct: a distributed systems problem you introduce architecturally can be eliminated architecturally. Idempotency and outbox complexity are not properties of durable workflows in general — they are properties of durable workflows implemented across a trust boundary. Removing that boundary by putting the workflow engine inside the database makes those problems disappear, not because you solved them cleverly, but because they no longer apply. This is the kind of simplification that comes from challenging the assumed component boundary rather than optimizing within it.

Where this is useful

  • Workflows where the primary operations are database mutations inside one Postgres instance and idempotency bookkeeping has become a maintenance burden
  • Services using the transactional outbox pattern that want to eliminate the polling infrastructure and reduce the risk of engine-to-database drift
  • When NOT applicable: workflows that need to atomically coordinate writes across multiple independent databases or external systems that have no Postgres-compatible interface

Apply this: If you have a workflow step that modifies the database and then checkpoints, count how many idempotency guards you’ve written for it — each one is evidence that a transaction boundary should not exist at that point in your architecture.

What I want to remember

Remember: Co-locating workflow state with application data in the same Postgres database turns the idempotency and outbox problems from distributed coordination challenges into local transaction problems — and local transactions have exactly-once semantics for free.