SD System Design In Depth
0%
lesson system-designstorage-engines

Immutable versioned data files

Module 2.3: Storage Engine Internals & Blob Systems Updated Sep 17, 2026
📄 Cheat Sheet
Howard Chu - LMDB [The Databaseology Lectures - CMU Fall 2015]
CMU Database Group Howard Chu - LMDB [The Databaseology Lectures - CMU Fall 2015] Watch on YouTube ↗

Howard Chu, LMDB's author, explains copy-on-write B+trees, the dual meta pages that make commits atomic, and how readers avoid locks: the precise mechanics this unit describes.

0:00 / --:--

Under the Hood: Copy-on-Write (CoW) & Persistent Data Structures

Traditional database engines (like MySQL InnoDB) overwrite pages in-place. If the server crashes mid-write, the page is corrupt (torn write), requiring complex Doublewrite Buffers and WAL replay. Copy-on-write storage engines (LMDB, CouchDB, ZFS) never overwrite live pages on disk. Instead, every mutation writes a new version of the page and swings an atomic root pointer. Table formats such as Apache Iceberg apply the same idea at file granularity (immutable data files plus a new snapshot metadata file), not as a page tree.

Copy-on-Write B-Tree Pointer Swapping

flowchart TD subgraph Version1 ["Version 1 (Old Root: Pointer A)"] R1["Root Page V1"] --> N1["Internal Node 1"] R1 --> N2["Internal Node 2"] N1 --> L1["Leaf Page A (Unchanged)"] N1 --> L2["Leaf Page B (Target of Update)"] end subgraph Version2 ["Version 2 (New Root: Pointer B)"] R2["Root Page V2 (New)"] --> N1New["Internal Node 1 (New)"] R2 --> N2 N1New --> L1 N1New --> L2New["Leaf Page B' (Updated Copy)"] end

Why Immutability Eliminates Concurrency Locks

  • Lock-Free Readers: Readers take a reference to the active root pointer at transaction start. Because pages are immutable, readers traverse the tree without page locks or latches and without risk of reading inconsistent or partial writes (in LMDB a reader still registers its snapshot in a reader table so the writer does not reuse pages it needs).
  • Single Atomic Root Swap: When a writer finishes constructing the new branch, it must persist the new pages before publishing durable commit metadata that identifies the new root. Implementations commonly use checksummed or redundant metadata pages, a WAL, and explicit flush ordering so recovery can select a complete generation after a crash; a CPU-atomic pointer store alone is not a durable commit.
  • Instant Time Travel & Snapshots: Creating a snapshot is free: simply preserve the root pointer of that version. Older versions are reclaimed by background garbage collection only when all referencing reader transactions terminate.

Worked example: one update in a copy-on-write B+tree

A tree of depth 3 with 4 KB pages (root, internal, leaf). Updating one key:

  1. Write a new leaf containing the change (4 KB).
  2. Write a new internal page whose pointer now targets the new leaf (4 KB).
  3. Write a new root pointing at the new internal page (4 KB).
  4. Flush those pages, then write and flush a meta page: txn_id = 1042, root = page 9913, checksum.

That is 12 KB of page writes plus a meta page for a 100-byte change, the price of never overwriting. LMDB keeps two meta pages and alternates between them; on startup it picks the valid one with the highest transaction id, so a crash during step 4 simply leaves the previous version in force. Pages from transaction 1041 are freed only when no active reader still uses that snapshot.

Table formats do the same thing at file granularity

In Apache Iceberg, a commit writes new data files, new manifest files and a new table metadata file, then atomically swaps the catalog's pointer from v41.metadata.json to v42.metadata.json using compare-and-swap. If another writer committed first, the swap fails and the writer retries against the new base (optimistic concurrency). Old snapshots remain queryable until expired.

Comparison

ApproachCrash safety mechanismReader isolationSpace costExamples
In-place pages plus WALLog replay, doublewrite or full-page writesLocks, latches, MVCC undoLowInnoDB, PostgreSQL
Copy-on-write B+treeFlush pages, then meta pageSnapshots by root pointerPath copies; pinned old pagesLMDB, Btrfs, ZFS
Append-only B-treeHeader written after dataSnapshotsHigh until compactionCouchDB
Immutable files plus metadata pointerAtomic catalog swapSnapshot isolationOld files until expiryIceberg, Delta Lake

Failure modes

  • Snapshot pinning: a forgotten read transaction stops reclamation; monitor oldest reader age.
  • Metadata explosion: frequent tiny commits create thousands of small files and snapshots; table formats need regular compaction and snapshot expiry.
  • Single writer: LMDB serializes writers; high write concurrency needs a different engine.
Check yourself Question 1 of 3
CoW commit ordering recall
From: Why Immutability Eliminates Concurrency Locks

A copy-on-write B+tree has built a new leaf, internal page and root for an update. In what order must it persist things for the commit to be crash safe?

Key Takeaways

Copy-on-write preserves older pages while building a new generation, but commit metadata, flush ordering, checksums, and writer coordination still determine crash safety.
Immutable published pages can let readers avoid page-update latches when lifetime and reclamation are coordinated correctly.
A CoW commit publishes a new root only after its referenced pages are durable, and recovery must distinguish a complete generation from a torn publication.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue