Skip to content
About
Main content

Snapshots: full, incremental, copy-on-write

Why is a snapshot a garbage-collection policy rather than a copy, and what does keeping one cost?

Four terms that are often mixed up are really one mechanism with different lifetimes:

  • A is a consistent image whose only consumer is and whose life is one interval.
  • A is the same image promoted to a named, externally addressable object with a retention policy.
  • A backup is a snapshot moved to an independent .
  • PITR (point-in-time recovery) is a backup plus the retained log from then until now.

Flink proves this framing: a checkpoint is owned by the system and deleted automatically; a is the identical artifact, triggered by a user, named and kept. Same bytes, different lifetime. In general, the difference is system-owned and auto-deleted versus user-owned and retained.

Three ways to build a snapshot (derived from cost)

Full copy. Cost is O(dataset) in time and space. And while you copy, the source keeps changing, so you need either a pause or a consistent read view. N snapshots cost N × dataset.

Copy-on-write (CoW). Do not copy anything. Freeze the root of the mapping structure and stop the collector from reclaiming anything that is reachable from it. Creating the snapshot is O(1): it is a metadata operation. Space is O(data changed since). This is only possible on storage that already has an , which every medium that cannot update in place grows. The indirection layer you were forced to build for one reason makes snapshots almost free.

This gives the most useful sentence of this section: "Snapshot" means "these blocks are not reclaimable". , Btrfs, Iceberg (a table format for data lakes), EBS (Amazon's block volumes), LVM thin pools and Git all implement it the same way, because they all already had a reference-counted indirection layer.

Incremental / differential. Copy only what changed since a reference point. This needs change tracking: a dirty bitmap, a block-change log, or the CoW metadata itself. Restore cost = the base + the whole chain of increments. So restore time grows with chain length, and the cure is periodic (a synthetic full: build a new full image from the old base plus increments).

This is the same equation as the checkpoint interval with the roles swapped: chain length trades capture cost against reconstruction cost. LSM levels (the layered files of write-optimized stores), columnar delta files, Iceberg snapshot chains, git packfiles, video keyframes and backup chains all have this one shape. As a rule: ; periodic re-basing is the only cure, and its interval is the real knob.

What a CoW snapshot is not

It shares blocks with the live data and lives in the same failure domain. It protects against errors (a bad UPDATE, a bad deploy) and against nothing else: not media failure, not controller firmware bugs, not rm -rf of the whole pool, not a stolen credential.

And it has a continuous cost. Once a snapshot exists, every overwrite in the live data must allocate a new block instead of overwriting. So , in write and pool space, for as long as the snapshot is kept. A forgotten snapshot on a CoW pool is a slow-motion outage. This is the same shape as pinned by the oldest read view (Logs and undo).

The multi-volume trap

This one kills real production systems. A block-level snapshot is , not application-consistent. It captures the state that a power loss would have produced. That is fine, because the database's own recovery knows how to handle that state, but only if the snapshot is atomic across every device that the spans.

If you snapshot the data volume and the log volume separately, you can capture a state in which a page's change is present but its log record is not. That image breaks the rule and cannot be recovered. It looks fine until the day you restore it. Hence pg_backup_start, Windows VSS writers and consistency groups (snapshots of several volumes taken at one instant).