ZFS Snapshots, Bookmarks, and Checkpoints

At first glance, ZFS snapshots, bookmarks, and checkpoints may seem similar. Let's explore what these features are, how they differ, and example use cases for each.

Snapshots

Snapshots are a read-only, point-in-time view of an entire dataset; browsing a snapshot means everything will look exactly as it was when the snapshot was created. This is useful both for making backups (e.g. retaining different versions of a file as it changes over time) but also for syncing data between datasets or pools. For more on how ZFS snapshots work, see this previous article.

Bookmarks

A bookmark is similar to a snapshot in that it represents a point-in-time, but unlike a snapshot, it takes up virtually no space at all since it just records a small amount of metadata. We can use the following example to see how this is useful.

Let's look at tank/example before doing anything:

 1root@demo:~# zfs list -o space tank/example
 2NAME          AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD
 3tank/example  1.16G   600M      500M    100M             0B         0B
 4
 5root@demo:~# zfs list -t snapshot -r tank/example
 6NAME                 USED  AVAIL     REFER  MOUNTPOINT
 7tank/example@snap1   100M      -      100M  -
 8tank/example@snap2   100M      -      100M  -
 9tank/example@snap3   100M      -      100M  -
10tank/example@snap4   100M      -      100M  -
11tank/example@snap5   100M      -      100M  -

At this point, tank/example consumes 600MB of unique data:

  • 5x snapshots, each with 100MB of unique data
  • 100MB of unique data in the live dataset

Let's replicate this all to another dataset, tank/backup, using syncoid:

1root@demo:~# syncoid -r tank/example tank/backup
2INFO: Sending oldest full snapshot tank/example@snap1 (~ 100.2 MB) to new target filesystem:
3 100MiB 0:00:00 [ 124MiB/s] [==================================================>] 100%            
4INFO: Updating new target filesystem with incremental tank/example@snap1 ... syncoid_demo_2025-06-09:23:39:15-GMT00:00 (~ 500.9 MB):
5 501MiB 0:00:09 [50.3MiB/s] [==================================================>] 100%

At this point, the two datasets should be identical:

 1root@demo:~# zfs list -t snapshot -r tank
 2NAME                 USED  AVAIL     REFER  MOUNTPOINT
 3tank/backup@snap1    100M      -      100M  -
 4tank/backup@snap2    100M      -      100M  -
 5tank/backup@snap3    100M      -      100M  -
 6tank/backup@snap4    100M      -      100M  -
 7tank/backup@snap5    100M      -      100M  -
 8tank/example@snap1   100M      -      100M  -
 9tank/example@snap2   100M      -      100M  -
10tank/example@snap3   100M      -      100M  -
11tank/example@snap4   100M      -      100M  -
12tank/example@snap5   100M      -      100M  -

Let's create a new snapshot on the source dataset and create 100MB of new data:

 1root@demo:~# zfs snapshot tank/example@snap6
 2
 3root@demo:~# dd if=/dev/urandom of=/tank/example/file1.txt bs=1M count=100
 4100+0 records in
 5100+0 records out
 6104857600 bytes (105 MB, 100 MiB) copied, 1.51721 s, 69.1 MB/s
 7
 8root@demo:~# zfs list -o space tank/example
 9NAME          AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD
10tank/example   491M   701M      600M    100M             0B         0B

Now we have 100MB that is only present in tank/example but not present in tank/backup. As long as there is a common ancestor snapshot on both datasets, we can continue to sync new data like this as it comes in. However, if the source dataset is low on space, it may be impractical to keep a long history of snapshots around. This is where bookmarks come in handy - you can create a bookmark of a snapshot on the source, delete the snapshot, and still replicate new data to the destination (since the bookmark contains the reference metadata that the destination needs to know where to start replicating from). Let's give it a try:

 1root@demo:~# zfs bookmark tank/example@snap5 tank/example#snap5
 2
 3root@demo:~# zfs list -t bookmark -r tank/example
 4NAME                 USED  AVAIL     REFER  MOUNTPOINT
 5tank/example#snap5      -      -      100M  -
 6
 7root@demo:~# zfs destroy tank/example@snap1
 8root@demo:~# zfs destroy tank/example@snap2
 9root@demo:~# zfs destroy tank/example@snap3
10root@demo:~# zfs destroy tank/example@snap4
11root@demo:~# zfs destroy tank/example@snap5

At this point, tank/example consumes a lot less space:

1root@demo:~# zfs list -o space -r tank/example
2NAME          AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD
3tank/example   891M   200M      100M    100M             0B         0B

Nevertheless, we can still replicate the new data from it:

 1root@demo:~# syncoid -r tank/example tank/backup
 2Sending incremental tank/example#'snap5' ... snap6 (~ UNKNOWN):
 3Sending incremental tank/example@snap6 ... syncoid_demo_2025-06-09:23:50:18-GMT00:00 (~ 100.2 MB):
 4 100MiB 0:00:00 [ 103MiB/s] [==================================================>] 100%
 5
 6root@demo:~# zfs list -t snapshot -r tank
 7NAME                 USED  AVAIL     REFER  MOUNTPOINT
 8tank/backup@snap1    100M      -      100M  -
 9tank/backup@snap2    100M      -      100M  -
10tank/backup@snap3    100M      -      100M  -
11tank/backup@snap4    100M      -      100M  -
12tank/backup@snap5    100M      -      100M  -
13tank/backup@snap6    100M      -      100M  -
14tank/example@snap6   100M      -      100M  -
15
16root@demo:~# zfs list -o space -r tank
17NAME          AVAIL   USED  USEDSNAP  USEDDS  USEDREFRESERV  USEDCHILD
18tank           891M   901M        0B     24K             0B       901M
19tank/backup    891M   701M      600M    100M             0B         0B
20tank/example   891M   200M      100M    100M             0B         0B

If space on the source dataset is a concern, or ZFS replication might get disrupted for longer than the source dataset's retention period, consider using bookmarks to avoid problems replicating from one dataset (or pool) to another.

Checkpoints

Think of a checkpoint as a single snapshot of an entire pool; all datasets are snapshotted atomically in the checkpoint, which allows you to rollback everything if needed. One common use case for checkpoints is before an OS or other software upgrade when running / on ZFS - you can make a checkpoint, run the upgrade, and if anything breaks easily rollback to your known-good system:

1root@demo:~# zpool checkpoint tank
2
3root@demo:~# zpool status tank
4  pool: tank
5 state: ONLINE
6  scan: scrub repaired 0B in 00:00:03 with 0 errors on Sun Jun  8 00:24:04 2025
7checkpoint: created Mon Jun  9 23:58:10 2025, consumes 85.5K

Unlike snapshots and bookmarks, only a single checkpoint can exist on a pool at any given time. Moreover, these are not intended to be long-lived and your pool may quickly lose free space since everything in the checkpoint must be retained. Therefore, once your testing is complete, remove the checkpoint:

1root@demo:~# zpool checkpoint -d tank

Conclusion

ZFS snapshots, bookmarks, and checkpoints are all useful tools to help manage changing data in datasets and pools. Consider how you can design your system to maximize free space while also retaining the ability to replicate data and rollback to an earlier point-in-time if needed.


Support Us

If you found this article helpful, please subscribe to our newsletter or support us on Patreon and get access to bonus features!

Questions? Comments?

Do you have questions or comments about this article? Please contact us via Mastodon or Email - we want to hear from you!