fivenines
13/25

Build Your Own PostgreSQL

Transactions and the illusion of instant change

Time
6 min
Prerequisites
Write Ahead Logging And The Durability Contract

What You Will Learn

  • Understand many steps, one meaning in a PostgreSQL-like database
  • Understand status makes bytes interpretable in a PostgreSQL-like database
  • Understand transaction path in a PostgreSQL-like database
  • Understand the file-system version breaks fast in a PostgreSQL-like database
transactionsillusioninstantchangestepsmeaningstatusmakesbytesinterpretable

Many steps, one meaning

A transaction lets users treat several actions as one change. You transfer money by debiting one account and crediting another. You create an order, reserve inventory, and record a payment. You update a table and its indexes. The database may perform many physical steps, but the outside world should see either the whole effect or none of it. A transaction is the boundary that groups a sequence of operations into one unit of meaning.

Atomicity is the first illusion. Internally, changes happen one at a time. Buffers are dirtied. WAL records are appended. Index entries appear, and tuple versions are created. If the transaction aborts, the system must make those changes invisible or undo their consequences. Postgre-like systems often avoid physical undo for heap changes by using transaction status and visibility rules. A tuple inserted by an aborted transaction can remain physically present until cleanup, but no valid snapshot should treat it as visible.

Status makes bytes interpretable

The transaction manager assigns identities to transactions and tracks their outcomes. A transaction may be in progress, committed, aborted, or in a prepared state for two-phase commit. Tuple headers store the creating and deleting transaction identifiers. Visibility checks consult transaction status to decide whether a tuple version belongs to the snapshot being read. So transaction state is part of ordinary row access, not a separate ledger consulted only at commit.

Transaction path

The file-system version breaks fast

Direct autocommit writes to files show why the boundary matters. If a process crashes after the debit but before the credit, the stored state is wrong. If two users update related data without a shared boundary, the database cannot know which partial states are forbidden. Transactions give users a way to tell the system which actions belong together. Without that boundary, correctness leaks into every application caller.

Autocommit does not remove transactions. It wraps each statement in its own transaction. That is a practical default because many statements are complete on their own. But multi-step business changes still need explicit transaction boundaries. The database cannot infer that three separate statements form one unit unless the session says so.

Rollback is often misunderstood as time travel that erases every byte immediately. In an MVCC design, rollback is closer to changing how bytes already written are interpreted. If a transaction inserts a tuple and aborts, the tuple version may remain on disk, marked by its creating transaction's aborted status. Future readers skip it. Vacuum later reclaims the space. This delayed physical cleanup makes abort cheaper and keeps concurrency manageable.

Boundaries inside boundaries

Savepoints add nested recovery points inside a transaction. They do not create independent committed units, but they let a session recover from part of its work. If a statement fails after a savepoint, the transaction can roll back to that point and continue. This helps with complex application flows where one optional operation may fail without invalidating the entire transaction. Internally, the system must track subtransaction effects and visibility carefully.

Isolation is the second major illusion, and it is subtler than atomicity. A transaction should not see a random blend of other transactions' half-finished work. Different isolation levels offer different promises about what changes are visible and when. Even before studying those levels in detail, the core idea is clear: transaction boundaries organize time for readers and writers.

Durability completes the visible promise. Once a transaction commits, its effects must survive a crash according to the configured policy. Atomicity without durability would let complete changes vanish. Durability without atomicity would preserve broken partial changes. Transactions bind these properties together so users can reason about state transitions rather than storage events.

The transaction manager must also handle wraparound. Transaction identifiers are finite. If the system keeps assigning them forever without care, old and new identities can become confused. Postgre-like systems therefore freeze old tuple metadata and vacuum aggressively enough to prevent ambiguity. Even abstract properties eventually meet physical limits.

Into MVCC

A transaction is the unit users think in, but readers still need a precise rule for which committed and in-progress changes they can see. That rule is MVCC. It lets old and new versions coexist so readers do not block writers by default, and writers do not erase the past out from under readers.

From above, the transaction is a clean boundary around intent. From below, it is metadata on tuple versions, log records, status bits, and locks. MVCC is where those two views meet and become readable as time.

That bridge is what lets many transactions share one database without sharing one instant.

Next step

See what actually stuck.

Take the practice scenarios now.