Skip to content

Sagas: compensation after a committed step ​

A saga coordinates separate local transactions and application-defined compensations. This demonstration represents a flight and a hotel with two tables in one PostgreSQL database. It runs no remote services or messaging protocol. The original saga paper describes the design, rather than an engine guarantee.

Watch a saga compensate ​

Step 1 — book the flight. A local transaction, committed immediately.

Saga> BEGIN;
BEGIN

Saga> UPDATE flights SET seats = seats - 1 WHERE id = 1 RETURNING seats;
 seats 
-------
     4 
(1 row)

Saga> COMMIT;
COMMIT

There is no enclosing transaction across steps. This standalone READ COMMITTED Reader sees the committed intermediate seat count.

Reader> SELECT seats FROM flights WHERE id = 1;
 seats 
-------
     4 
(1 row)

Step 2 — book the hotel. No rooms left: the step fails as a business outcome, not an error.

Saga> BEGIN;
BEGIN

Saga> UPDATE hotels SET rooms = rooms - 1 WHERE id = 1 AND rooms > 0; -- UPDATE 0 — nothing to book
UPDATE 0

Saga> ROLLBACK;
ROLLBACK

Step 1 already committed — there is nothing a ROLLBACK could undo. The saga runs a COMPENSATING transaction: a new forward transaction that reverses the booking.

Saga> BEGIN;
BEGIN

Saga> UPDATE flights SET seats = seats + 1 WHERE id = 1 RETURNING seats;
 seats 
-------
     5 
(1 row)

Saga> COMMIT;
COMMIT

Verified against PostgreSQL 18.6 · Run it yourself · Scenario source

What the transcript just proved ​

The flight booking commits with four seats left. A standalone READ COMMITTED Reader sees that intermediate value. The guarded hotel update affects zero rows because no room is available; this is a business outcome, not a SQL error. Its transaction rolls back. A new transaction adds the seat back and asserts five seats before committing.

Compensation is a new database change, not a rollback of the committed flight transaction. The saga has no enclosing transaction isolating all steps. This reader sees an intermediate commit; a reader using an older REPEATABLE READ snapshot need not see it. The schedule does not establish every anomaly or the behavior of every concurrent observer.

The demonstrated increment is not idempotent: repeating it would add another seat. An application must define compensation identity, retry handling, and intermediate-state rules. They are design responsibilities, not mechanisms implemented by this example. Short local transactions reduce the duration of their own locks; they do not ensure every transaction avoids all waits or reclamation delays.

If a step sends an irreversible external effect, SQL compensation cannot erase it. An outbox can store a step's event intent atomically with local work, but no saga transport, receiver deduplication, or recovery coordinator is exercised here.

Further reading ​

The reconstructed support assessment records the exact manual support and execution limits for this lesson.

MIT Licensed · Every transcript on this site was generated by a real database run against MySQL 8.4.11 and PostgreSQL 18.6 at 8fa27b8; shared YAML has an independent psycopg/PyMySQL check.