Developer Tools

MVCC Explained: Snapshots, Concurrent Updates and Vacuum

Use a two-session timeline to understand what reads can see and why snapshots do not remove write conflicts.

AiFolder Editorial Team

AiFolder Editorial Team

5 min read

Updated Oct 9, 2026

Three translucent offset glass sheets contain different versions of the same red geometric object, a small observer lens reads one stable sheet while others change
Original AiFolder editorial illustration

Readers see versions

Multiversion concurrency control lets transactions read a consistent view while concurrent transactions change data. PostgreSQL's MVCC introduction describes its snapshot model. Visibility is defined by isolation and statement or transaction boundaries, not by a promise that every query sees the newest row.

Imagine a product priced at 100. Session A starts a transaction, session B changes the price to 120 and commits, then A reads again. Whether A sees 100 or 120 depends on A's isolation level and when each snapshot is taken.

A two-session exercise

Create a disposable product row. In session A use a transaction at Repeatable Read and select the price. In session B update the row and commit. Select again in A, then commit A and run a fresh query. Compare that sequence with Read Committed.

PostgreSQL's isolation documentation explains why the default Read Committed mode uses a fresh snapshot for each command, while Repeatable Read preserves a transaction snapshot. Do not extrapolate those exact details to every database that uses the term MVCC.

Two readers see different valid versions

Imagine an order dashboard reading a total while another transaction records a refund. Under PostgreSQL's Read Committed isolation, each statement sees a snapshot appropriate to that statement, so two queries in the same transaction can observe different committed states. Under Repeatable Read, successive reads use the transaction's snapshot under the documented rules. Neither behavior is automatically wrong; the application must choose the visibility it needs for its business operation.

For a report that must reconcile several related totals, think about whether they must share a consistent snapshot. For an interactive screen, a more current answer on the next query may be acceptable. Snapshot visibility is separate from conflict control: two writers can still contend, and an invariant spanning rows may require a stronger transaction design. Write a small example using two sessions and the exact isolation level before encoding an assumption in application logic. Record which transaction commits when and what each statement is allowed to see, rather than describing MVCC as a universal promise that all reads are simultaneously fresh and consistent.

Two readers see different valid versions: Transaction begins → Concurrent update → Another statement → Snapshot rule
Two readers see different valid versions. Original AiFolder explanatory diagram; illustrative workflow.

Visibility does not replace business constraints

Suppose two scheduling transactions each observe that another doctor is on call and then independently mark their own doctor unavailable. A consistent snapshot alone does not express the rule that at least one doctor must remain available. The application needs a concurrency design that protects that business invariant, such as a suitable serializable transaction with retry handling or explicit coordination on the relevant shared state. A single-row uniqueness rule is often better placed in a database constraint.

Choose the isolation level and locking strategy around the operation, then handle the failures they intentionally expose. Retrying a serialization failure requires replaying the complete transaction from a fresh state, not continuing with values read before the abort. Keep external effects outside an automatically replayed transaction unless they have their own stable idempotency boundary. Long transactions also have operational cost because their snapshots can keep older versions relevant. A report or idle connection that remains open far longer than expected should be visible to operators. Transaction duration is part of the concurrency design, not only an implementation detail.

Visibility does not replace business constraints: Read Committed → Repeatable Read → Serializable → Unique constraint
Visibility does not replace business constraints. Original AiFolder explanatory diagram; illustrative workflow.

MVCC does not eliminate coordination

Concurrent writes can still contend on the same row. An invariant spanning multiple rows may require constraints, explicit locking or Serializable isolation with retry handling. A read followed by a write is not automatically safe merely because the read did not block.

For example, two workers can both observe that a job has no invoice. A unique constraint on the durable invoice relationship protects the invariant more reliably than that observation. See distributed coordination for the distinction.

Old versions have a cost

Readers may need older versions, so the database cannot always remove them immediately. Long-lived transactions and retained snapshots can delay cleanup. PostgreSQL's vacuum documentation explains maintenance that reclaims obsolete row storage and supports visibility-related work.

Monitor transaction age, dead tuples, vacuum progress and query latency together. Avoid leaving an application connection idle in an open transaction. A slow external API call should not casually extend a transaction holding a snapshot or lock.

Choose the isolation from the invariant

Write down an anomaly your workflow cannot tolerate, then test concurrent sessions that could produce it. Stronger isolation may require retrying a whole transaction after a serialization failure. Keep that retry bounded and keep irreversible external effects outside blindly repeated transactions.

MVCC is a concurrency mechanism, not a replacement for application correctness. Use AiFolder interview practice to explain the timeline, the chosen isolation and the recovery policy together. The exercises here are illustrative procedures rather than measurements on a production database.

Find the transaction that prevents cleanup

A table can keep growing even when rows are updated rather than newly inserted, because obsolete versions need cleanup. PostgreSQL vacuuming makes space reusable under its rules, and autovacuum is an essential operational mechanism. Do not assume that ordinary vacuum will immediately shrink the file on disk. When cleanup seems ineffective, inspect long-lived transactions and the database's vacuum statistics before reaching for disruptive maintenance commands.

Separate logical live data, reusable space and physical file size in the investigation. An idle transaction holding an old snapshot can affect cleanup while appearing inactive at the application level. Bound transaction lifetimes, avoid leaving a transaction open during user think time, and make reporting jobs release snapshots when they finish. Rehearse maintenance in an appropriate environment and verify which operation locks the table or requires extra disk space. The useful invariant is that the application keeps concurrency semantics correct while allowing the database to retire versions it no longer needs. MVCC improves concurrent access, but its benefits depend on disciplined transaction boundaries and ongoing maintenance.

Find the transaction that prevents cleanup: Observe growth → Inspect old transactions → Check vacuum activity → Shorten lifetime
Find the transaction that prevents cleanup. Original AiFolder explanatory diagram; illustrative workflow.

Further reading for these examples

Topics

AiFolder Editorial Team

Written by

AiFolder Editorial Team

Practical technology guides with clear sources, examples and limitations.

Continue reading

More articles

View all