Copy-on-write snapshots

Freeze a volume in an instant by sharing blocks — and only copy one the moment someone writes to it.

The idea

A copy-on-write snapshot captures a volume at a point in time without copying any data. Taking it is instant: the snapshot simply records pointers to the same blocks the live volume already uses. Nothing moves, nothing duplicates — the two views share every block.

The copy happens lazily. When a write lands on a block that’s shared with a snapshot, the system allocates a fresh block for the new value and leaves the old one pinned for the snapshot to keep reading. Only blocks that actually change ever get copied, so a snapshot’s extra space grows with churn, not with volume size.

Press play to take a snapshot, then watch writes trigger copy-on-write.

How it works

Creating a snapshot just shares references — it never touches the underlying data. A write checks whether the target block is still shared; if so, it copies the block first, then writes into the private copy. Untouched blocks stay shared forever.

# Take a snapshot: share refs, copy nothing
def take_snapshot(volume):
    snap = Snapshot()
    for i, block in enumerate(volume.blocks):
        snap.ptr[i] = block          # point at the SAME block
        block.shared = True          # mark it shared
    return snap                      # 0 blocks copied — instant

# Write a block: copy only if shared (copy-on-write)
def write(volume, i, value):
    block = volume.blocks[i]
    if block.shared:                 # snapshot still needs the old value
        new = allocate_block()       # one extra block consumed
        new.data = value
        volume.blocks[i] = new       # live moves on…
        block.shared = False         # …old block stays pinned for the snapshot
    else:
        block.data = value           # private already — write in place

The snapshot keeps reading the old, pinned blocks; the live volume reads its fresh copies. Space used by the snapshot equals the number of blocks the live volume has overwritten since — the delta, nothing more.

Cost & signals

DimensionWhat to know
Create costConstant time — share pointers, copy zero data. Snapshots are effectively instant.
SignalSnapshot size grows only with the delta (changed blocks), not with the volume’s total size
SignalThe copy-on-write counter ticks up on the first write to each shared block, then stops
Write amplificationThe first write to a shared block costs an allocate + copy; later writes to it are in place
ReclaimPinned old blocks free up only when the snapshot is deleted — space returns to the pool then

Watch out for

Worked example

A database volume holds 6 blocks. At 02:00 a nightly snapshot is taken for backup — it completes in milliseconds because all it does is record 6 pointers at the existing blocks; 0 blocks copied. Through the day two rows change: a write to block 2 triggers copy-on-write (allocate a new block for the live value, keep the old one pinned for the snapshot), and later a write to block 4 does the same. The snapshot now uses exactly 2 blocks of extra space and still reads the morning’s consistent values, while the live volume shows the new ones. When the backup finishes and the snapshot is deleted, those 2 pinned blocks are reclaimed and space returns to the pool.

Check yourself

A 500 GB volume has a fresh snapshot taken with no writes since. Roughly how much extra space does the snapshot use?

Why might deleting an old, unused snapshot suddenly free a lot of space?