TL;DR — Copy‑on‑write (COW) B‑trees let you take point‑in‑time snapshots without blocking writes. By sharing unchanged pages and versioning modified nodes, you get O(log N) reads, cheap snapshots, and strong consistency guarantees that scale in production systems like CockroachDB and RocksDB.

Database snapshots are a cornerstone of modern data platforms—think point‑in‑time restores, analytical clones, or multi‑tenant tenant isolation. Traditional approaches (full copy, logical dump, or heavyweight MVCC) either stall the workload or explode storage costs. A COW B‑tree combines the elegance of B‑tree indexing with immutable page semantics, delivering near‑zero‑copy snapshots that integrate cleanly into existing write‑ahead logs (WAL). This post walks through the architecture, consistency model, and production‑ready patterns you need to adopt COW B‑trees in a real system.

Why Copy‑on‑Write B‑Trees?

  • Fast Snapshots – Creating a snapshot is a single atomic pointer update; no data movement is required.
  • Low Write Amplification – Only the nodes on the update path are duplicated, keeping I/O similar to a classic B‑tree.
  • Strong Consistency – Each snapshot sees a completely isolated view of the tree, enabling snapshot isolation without extra locking.
  • Storage Efficiency – Unchanged pages are shared across all snapshots, and background garbage collection reclaims stale pages.

These benefits map directly to production concerns: SLA‑driven backup windows, multi‑tenant tenant‑level cloning, and cost‑effective long‑term retention.

Core Architecture

Data Layout

A COW B‑tree stores pages (leaf or internal) in a page‑oriented storage engine (e.g., RocksDB, LMDB). Each page has:

  • Page ID – a monotonically increasing 64‑bit identifier.
  • Generation – the snapshot version that created the page.
  • Checksum – for integrity verification.
  • Payload – sorted key/value pairs (leaf) or child pointers (internal).

Pages are immutable; any modification creates a new page with a fresh Page ID and Generation. The root pointer for a given snapshot lives in a small snapshot table (often a single row in a meta‑table). The table entry maps a snapshot ID (timestamp or transaction ID) to a root Page ID.

snapshot_table:
  - snapshot_id: 20260601120000
    root_page_id: 0x7f3a9c01
  - snapshot_id: 20260601130000
    root_page_id: 0x8b4d2e11

Write Path

When a client issues an insert, delete, or update:

  1. Locate the leaf – traverse from the snapshot’s root, following child pointers. Because pages are immutable, the traversal reads a consistent view.
  2. Copy‑on‑Write – duplicate each page on the path (root → … → leaf). The new leaf contains the modified key/value pair; interior nodes receive updated child pointers.
  3. Persist New Pages – write the new pages to the storage engine; the write‑ahead log records the logical operation for recovery.
  4. Atomic Root Update – once all pages are flushed, atomically write the new root Page ID into the snapshot table for the current transaction.

The algorithm is identical to a classic B‑tree insert, except that every visited node is copied rather than edited in place.

def cow_insert(snapshot_id, key, value):
    # 1. Resolve current root
    root_id = snapshot_table.get_root(snapshot_id)
    # 2. Recursively copy path and apply change
    new_root_id = _copy_path_and_modify(root_id, key, value)
    # 3. Persist new root atomically
    snapshot_table.update_root(snapshot_id + 1, new_root_id)

Read Path

Reading from a snapshot is a pure B‑tree lookup:

  1. Fetch the root Page ID for the desired snapshot.
  2. Walk down the tree, loading pages by their immutable IDs.
  3. Return the value when the leaf is reached.

Because pages never change, no locking is required, and the read cost is exactly the same as a traditional B‑tree: O(log N) page reads.

# Example using a low‑level CLI that accepts a snapshot timestamp
dbctl get --snapshot 20260601120000 --key user:1234

Consistency Guarantees

Snapshot Isolation

Each snapshot corresponds to a generation of the tree. All reads that specify the same snapshot ID see a consistent view, regardless of concurrent writers. This matches the snapshot isolation level used by PostgreSQL’s MVCC, but with the advantage that the underlying index is immutable, eliminating the need for tuple‑level version chains.

“The snapshot is a point‑in‑time view that never changes, making repeatable reads trivial.” – as described in the PostgreSQL MVCC documentation

Handling Concurrent Updates

Concurrent transactions each create their own copy of the affected path. When two writers modify overlapping keys, their updates diverge into separate snapshots. The commit protocol (often two‑phase commit) decides which snapshot becomes visible next:

  • Serializable workloads – a global commit order is enforced; later commits see earlier snapshots’ root IDs.
  • Multi‑tenant workloads – each tenant may have its own independent snapshot stream, avoiding cross‑tenant conflicts.

Conflict detection can be performed by checking whether the expected root ID matches the current root before committing. If they differ, the transaction must retry.

Patterns in Production

Incremental Backups with WAL Integration

Many production systems already ship a write‑ahead log for durability. By coupling the WAL with COW page writes, you can generate incremental backups automatically:

  1. The WAL records logical operations (insert, delete, update).
  2. A background job scans the WAL, builds a new snapshot root, and writes it to the snapshot table.
  3. Retention policies prune old snapshots and run a garbage collector that deletes pages whose generation is older than the oldest retained snapshot.

RocksDB’s built‑in COW support demonstrates this pattern: the checkpoint operation writes a new manifest pointing at the current set of immutable SST files, enabling fast point‑in‑time restores. See the RocksDB blog for details: RocksDB checkpointing with copy‑on‑write.

Multi‑Tenant Snapshot Service (e.g., CockroachDB)

CockroachDB uses a range‑level COW B‑tree to implement its distributed MVCC storage. Each range stores a B‑tree of key/value pairs, and every transaction writes a new version of the affected leaf pages. Snapshots are identified by a hybrid logical clock timestamp, and replicas can serve reads from any past timestamp without coordination.

Key production takeaways from CockroachDB’s architecture:

  • Hybrid logical clocks provide globally monotonic timestamps without a single point of failure.
  • Range leases protect against split‑brain scenarios while still allowing concurrent snapshot reads.
  • Range tombstones enable efficient deletion of old generations without scanning the entire tree.

Read more in the official docs: CockroachDB architecture overview.

Monitoring & Metrics

Operating a COW B‑tree at scale requires visibility into page churn and snapshot health:

MetricMeaningTypical Alert
cow_pages_created_totalNumber of pages allocated per minute> 10 k pages/min (possible hot‑spot)
cow_snapshot_age_secondsAge of the newest snapshot vs. current time> 300 s (lag in backup pipeline)
cow_garbage_collector_duration_secondsTime spent reclaiming stale pages> 30 s (GC throttling)
cow_root_updates_totalFrequency of root pointer writesSudden drop may indicate commit stall

Integrate these counters with Prometheus and Grafana dashboards to catch performance regressions before they impact SLA.

Key Takeaways

  • Atomic snapshots are achieved by a single root pointer update; no data copy is required.
  • Immutable pages eliminate read‑write contention and simplify consistency: every snapshot gets a clean, repeatable view.
  • Write amplification stays low because only the nodes on the update path are duplicated.
  • Production patterns such as WAL‑driven incremental backups, multi‑tenant snapshot services, and robust monitoring make COW B‑trees viable at scale.
  • Garbage collection is essential; without it, storage grows linearly with the number of snapshots.

Further Reading