A ZFS snapshot preserves a point-in-time dataset view through copy-on-write references instead of a full duplicate of its data. Existing blocks remain available while references need them. Creation still has metadata and synchronization work; neither zero allocation nor completion before a shell redraw is a measured guarantee. This review did not run ZFS or recover the earlier claimed fixture’s raw output.

That makes retained history useful for local recovery, but every snapshot of tank/data remains in the same pool. A failure that makes that pool unavailable can also make its snapshots unavailable. This is a shared failure boundary, not a claim that every controller fault atomically destroys all blocks. Independent backups or replication need their own retention and verified recovery.

A name for one consistent tree

Snapshots atomically capture the dataset’s filesystem state; recursive -r snapshots include descendants at the same instant. Without recursion, child datasets are omitted rather than automatically captured later. Filesystem consistency is not an application checkpoint: database buffers, logs and files outside the selected datasets still require the application’s supported backup or crash-recovery procedure. Coordinate all required datasets before treating the set as recoverable.

Names such as tank/data@daily-2026-09-16 can help retention, alongside creation times, properties and backup records. Naming alone does not prove eligibility for deletion. zfs list -t snapshot -o name,used,refer,creation -s creation inspects snapshots and their accounting; dependencies and holds must also be checked before a pruning decision.

Where the space is, in zfs list -o space

zfs list -o space tank/data selects NAME, AVAIL, USED, USEDSNAP, USEDDS, USEDREFRESERV and USEDCHILD; it does not include REFER. USED decomposes into the four used-by categories, not REFER plus USEDSNAP. REFER is all data accessible through the dataset and can include shared blocks. Snapshot USED measures uniquely referenced space; deleting another snapshot can change it, so individual values do not simply sum to all recoverable space.

The earlier thirty-snapshot, 64 MiB dataset and 2 MiB daily-churn figures lack a recovered ZFS trace. The 60 MiB versus 1920 MiB comparison is illustrative arithmetic, not verified allocation. Metadata, compression, block size, sharing and the exact retention timeline affect actual cost. A recent snapshot may have little unique data while the whole retained set still prevents substantial reclamation.

Holds, diff, and comparing versions

A user hold prevents destruction until released. zfs hold keep tank/data@daily-2026-09-16 sets one; zfs holds tank/data@daily-2026-09-16 inspects it, and zfs release keep tank/data@daily-2026-09-16 releases that tag. Holds can protect an in-progress transfer, but clone-origin dependencies are separate and need not appear as named hold tags. Release only after understanding the dependent operation.

zfs diff tank/data@daily-2026-09-15 tank/data@daily-2026-09-16 reports changes between the retained versions: created, removed, modified and renamed paths. Filesystem snapshots are mounted on demand beneath .zfs/snapshot/<name>; snapdir=visible exposes that directory in listings. Snapshot access and a destructive dataset rollback are different recovery choices, and neither verifies application correctness by itself.

When it breaks

A successful send/receive into independently protected storage can survive loss of the source pool. The shown incremental transfer requires an existing destination whose latest snapshot matches the source base; a bare incremental stream is not a complete standalone backup. A second pool on the same machine can still share power, controller or administrator risks. Verify received data and a usable restore before claiming protection.

Growing USEDSNAP warrants capacity and retention review, but eighty-percent occupancy is not a universal write-failure threshold. Quotas, reservations and actual pool availability matter. A clone depends on its origin snapshot; deleting that origin or rolling back past newer dependent versions needs deliberate dependency handling. Rollback can discard newer data, and destroying clones is not the only possible recovery strategy.

ZFS snapshot lineage and clone dependencies are separated from user holds and USED accounting; rollback and independent receive retain their limits.
ZFS history has dependencies — mechanism diagram.

Check it quickly

This unexecuted standalone Bash recipe requires exclusively controlled disposable datasets, existing source snapshots, and a receiver previously seeded from the base. The destination must be unchanged since that base, mounted at the bound path without intervening mounts or symlinks; the hold tag must be unused. Before taking the target snapshot, prepare regular-file probe.txt in the source and keep a trusted external copy at expected. Listing is recursive; transfer covers only tank/data, not descendants.

(
  set -euo pipefail
  src='tank/data'
  dst='backup/data'
  base='daily-2026-09-15'
  next='daily-2026-09-16'
  tag='example-transfer'
  mount='/mnt/zfs-lab/received'
  expected='/mnt/zfs-lab/expected-v2.txt'

  zfs list -r -t snapshot -o name,used,refer,creation -s creation "$src" "$dst"
  zfs list -o name,avail,used,usedsnap,usedds,usedrefreserv,usedchild,refer "$src"
  zfs holds "$src@$base" "$src@$next"
  zfs diff "$src@$base" "$src@$next"

  latest=$(zfs list -H -r -t snapshot -o name -s createtxg "$dst" |
    awk -F@ -v dataset="$dst" '$1 == dataset { last=$0 } END { print last }')
  test "$latest" = "$dst@$base"
  source_guid=$(zfs get -Hp -o value guid "$src@$base")
  target_guid=$(zfs get -Hp -o value guid "$dst@$base")
  test -n "$source_guid"
  test "$source_guid" = "$target_guid"
  target_mount=$(zfs get -H -o value mountpoint "$dst")
  target_mounted=$(zfs get -H -o value mounted "$dst")
  test "$target_mount" = "$mount"
  test "$target_mounted" = yes
  test -f "$expected"
  test ! -L "$expected"

  zfs hold "$tag" "$src@$base" "$src@$next"
  zfs send -i "$src@$base" "$src@$next" | zfs receive "$dst"
  source_guid=$(zfs get -Hp -o value guid "$src@$next")
  target_guid=$(zfs get -Hp -o value guid "$dst@$next")
  test -n "$source_guid"
  test "$source_guid" = "$target_guid"
  test -f "$mount/.zfs/snapshot/$next/probe.txt"
  test ! -L "$mount/.zfs/snapshot/$next/probe.txt"
  cmp -- "$expected" "$mount/.zfs/snapshot/$next/probe.txt"
)

The latest receiver snapshot and base GUID must match; equal names alone are insufficient. Failed checks stop the subshell, and pipefail exposes either transfer failure. Received GUID and fixture-byte comparisons still do not establish a valid application restore: verify the complete manifest, metadata and application recovery separately. Any holds created remain after failure or success; after validation and dependency review, release only this exercise’s tag with zfs release example-transfer tank/data@daily-2026-09-15 tank/data@daily-2026-09-16. No force rollback or automatic cleanup is included.

Sources

  • zfs-snapshot(8), snapshot creation semantics and recursive snapshots, checked 2026-09-16.
  • zfs-list(8), the space column set and separate REFER property, checked 2026-09-16.
  • zfs-hold(8), holds, zfs holds, and the EBUSY returned by destroy, checked 2026-09-16.
  • zfs-send(8), incremental send streams and their base-snapshot requirements, checked 2026-09-16.
  • zfsprops(7), used-by decomposition, shared references, quotas and reservations, checked 2026-10-18.
  • zfs-clone(8), clone dependence on its origin snapshot, checked 2026-10-18.
  • zfs-rollback(8), discarded newer data and dependent clone handling, checked 2026-10-18.
  • zfs-receive(8), matching destination base for incremental receive, checked 2026-10-18.
  • PostgreSQL filesystem backup, consistent snapshot scope, WAL and multi-volume recovery requirements, checked 2026-10-18.
  • ZFS concepts, clone dependency on its origin snapshot and shared storage boundaries, checked 2026-10-18.
  • zfs-get(8), exact property reads used for GUID and mountpoint checks, checked 2026-10-19.