Skip to content
About
Main content

Savepoints and group commit

Where does a savepoint's cost really land, and why does group commit tune its own batch?

Savepoints and nested transactions

The constraint. All-or-nothing is sometimes too coarse. A long transaction that hits a recoverable error (for example, one bad line in a batch of 1,000) should not lose everything.

The mechanism falls out of the log for free. If means replaying the undo records backwards from the end of the transaction's chain, then a partial abort is just "stop replaying at marker M". A is a position in the undo chain. Creating one costs almost nothing, because the structure it needs already exists for another reason.

BEGIN;
INSERT INTO orders (...) VALUES (...);          -- step 1
SAVEPOINT before_bonus;
INSERT INTO wallet_bonus (...) VALUES (...);    -- step 2, may fail
-- if step 2 fails:
ROLLBACK TO SAVEPOINT before_bonus;             -- undo only step 2
COMMIT;                                         -- step 1 still commits

The cost moved somewhere else, though. The Postgres case teaches this well. Each subtransaction gets its own transaction ID. Each backend process caches up to 64 of them. Past that, the cache overflows, and every other backend must consult an on-disk structure (pg_subtrans) during visibility checks, which turns a cheap in-memory comparison into a shared-buffer lookup. So a loop with a savepoint per iteration (or an exception block per iteration in PL/pgSQL, which is the same thing) produces a performance cliff that is system-wide and has no obvious link to the query that caused it. This is "immutable storage grows an indirection layer plus a collector" and "costs are conserved" at close range. It shows why "this mechanism is cheap" is never a complete sentence until you name which path pays.

A naming correction. What products call "nested transactions" is almost always savepoints, i.e. : a subtransaction's is only relative to its parent and means nothing until the top-level transaction commits. True nested transactions in Moss's sense (a subtransaction commits with its own semantics and passes its up to the parent) exist mostly in research systems. Oracle's go the other way: a child transaction commits independently of its parent, so that, for example, an audit record survives when the parent rolls back. That is a deliberate violation of atomicity, and it is the right design for that job. The mechanism exists because the all-or-nothing boundary is sometimes the wrong boundary.


Group commit

How one flush is shared by many commits, and why its batch tunes itself to the load, is durability's subject: a for the flush already in flight and rides the next one, so each commit pays a fraction of the for a little added . What matters here is what batching does to the commit protocol.

The hidden dividend. Once durability is batched, the commit order coarsens into epoch order. Transactions in the same flush have no relative order at all, and nobody can observe one. This is exactly the property that the Silo system used in Multiversion and optimistic schemes to remove the global validation critical section of , and it is the same idea as the batching of (Distributed transactions). A coarser order is not only cheaper to execute, it is cheaper to reason about: there are fewer orderings to guarantee. Batching the durability event buys ordering relief as well as I/O relief.