7

Say I have a pool named tank and a dataset inside of it named data. There is also at least one snapshot of data named last_snapshot:

tank
tank/data
tank/data@last_snapshot

The "slow" way of figuring out whether the dataset has been changed is to check the output of diff, as far as I have figured it out:

zfs diff tank/data@last_snapshot

It will show all changes to the dataset for every individual file / folder ... since the last snapshot. If there were a lot of changes, this command produces a lot of output and runs for seconds or even a few minutes.

A faster yet (as far as I can tell) less reliable way is to look at the written property of the dataset:

tank/data written 72K -
tank/data@last_snapshot written 1,83G -

If the value of written for the dataset (NOT any of the snapshots) is somewhere inbetween 56 and 128k, the dataset usually has not been modified since the last / most recent snapshot. This is much faster, but without understanding why this number can vary so much, I do not want to rely on this method.

How can I safely and quickly ask the yes/no-question: Has a dataset been changed / modified since the last snapshot?


Design idea (background): I do have a fair number of datasets which receive "significant" changes very infrequently only, say once every couple of weeks. Making a snapshot once per day and keeping the last, say, 7 quickly leads to loosing the state before the last "significant" change. This is where I thought: Let's only make snapshots if there was a "significant" change and keep the last 7 of those.

4
  • Thanks for clarifying your usecase. Just an observation, and I suspect you already realize this, but even "small" changes can be very "significant." A single character here or there can wreak havoc, depending on what kind of user data you're storing. Even if it's just Word documents, a legal contract or disclaimer can change immensely from even a minor change in wording. Think carefully before you decide whether your users' changes are significant or not. But you know your workflow better than I, so end of rant. :) Just a word to the wise.
    – Jim L.
    Jul 21, 2019 at 19:07
  • @JimL. I am my own user for the most part and it's (mostly) not legal contracts ;) Thanks, good advise though.
    – s-m-e
    Jul 27, 2019 at 11:44
  • 1
    ZFS snapshots have the huge benefit that they require very little space if nothing has changed. So an alternative approach, not an answer to the question as asked, would be to simply keep snapshots around either until they're using a predetermined amount of or fraction of space, or increase the number of snapshots you keep.
    – user
    Jul 28, 2019 at 16:11
  • @aCVn You're right. The problematic part tough is reliably and accurately measuring the "size of the snapshots". See this tool and its documentation for instance.
    – s-m-e
    Jul 29, 2019 at 6:27

3 Answers 3

3

This is a good question, and the most I can do right now is upvote it.

In thinking about this, some avenues of conjecture I have attempted are:

  • snapshotting the dataset again, and comparing the createtxg ("birth") properties of the two snapshots. On one test I got lucky and had consecutive values (implying no transactions had occurred in the interim between snapA creation and snapB creation). This fact later proved worthless because further research tells me that transaction groups are numbered globally across the entire pool, not uniquely within each file system.

  • comparing the createtxg value of the snapshot to the most recent transaction group of the filesystem. I am not certain, but it may be that if the snapshot's createtxg is equal to or higher than the filesystem's most recent transaction group, one can infer that the snapshot creation itself is the latest transaction that has been committed to that filesystem. I am not certain whether this is true or not, but it would be fortuitous if the snapshot's createtxg could be compared to the filesystem's most recent txg to precisely determine whether the snapshot was "pristine."

At this time, the best answer I can offer is to trust the written property of the filesystem, but only within strict parameters:

1) Ensure that all pending transactions have been committed to the filesystem

sync; sleep 1; sync  # maybe I'm just being superstitious?

2) Make sure the filesystem is inactive (not mounted)

zfs umount tank/data

3) Query the exact machine-parseable value of the written attribute using the -p flag of zfs list

zfs list -Hpo written tank/data

That number needs to be exactly 0 before I would infer that the snapshot is pristine. "Small" is not good enough, if we take the zfs man page at face value:

The following native properties consist of read-only statistics about the dataset. These properties can be neither set, nor inherited. Native properties apply to all dataset types unless otherwise noted.
...

createtxg

The transaction group (txg) in which the dataset was created. Bookmarks have the same createtxg as the snapshot they are initially tied to. This property is suitable for ordering a list of snapshots, e.g. for incremental send and receive.
...

written

The amount of referenced space written to this dataset since the previous snapshot.

Further promise is offered by:

written@snapshot

The amount of referenced space written to this dataset since the specified snapshot. This is the space that is referenced by this dataset but was not referenced by the specified snapshot.

The filesystem needs to be unmounted so that there is no race condition between when you notice that the value is 0 and begin to act on that state, yet meanwhile another process is just about to write to the filesystem and spoil your party.

If anyone knows how to query the most recently-committed transaction group number of a filesystem, I'd be obliged to know that method. A few basic Google searches haven't turned up anything I have found useful.

Another interesting property is the referenced property of the snapshot versus that of its parent filesystem.

referenced

The amount of data that is accessible by this dataset, which may or may not be shared with other datasets in the pool. When a snapshot or clone is created, it initially references the same amount of space as the file system or snapshot it was created from, since its contents are identical.

However, my hunch is that this property also leads to false hope, because the filesystem and the snapshot might both reference 10G of data, but it's not the same 10G in the actual blocks on disk, as is implied by the phrase "may or may not be shared". Matching referenced values seems more like a necessary condition than a sufficient one.

In summary, and with much humility and uncertainty, I think one has to insist on this zfs list output before one can assume a snapshot is pristine:

# zfs create -o mountpoint=/root/test w541/test
# zfs snap w541/test@snap1
# zfs list -po written,written@snap1 w541/test
WRITTEN  WRITTEN@SNAP1
      0              0

Soil the snapshot and it is no longer pristine:

# touch test/foo
# zfs list -po written,written@snap1 w541/test
WRITTEN  WRITTEN@SNAP1
  57344          57344

Roll it back, and it becomes pristine again:

# zfs rollback w541/test@snap1
# zfs list -po written,written@snap1 w541/test
WRITTEN  WRITTEN@SNAP1
      0              0

But for highest integrity, the filesystem should be unmounted, perhaps for some period of time, before querying the written property.

I will be extremely grateful for any corrections to any mistaken assumptions, oversights, or additional insights that anyone may be kind enough to offer.

3
  • Thanks for the answer. It actually improved my understanding of what I am dealing with. One question / remark, though: Unmounting the filesystem may work under certain circumstances, but not always ... A solution that also works on a "live" / mounted filesystem would help a lot.
    – s-m-e
    Jul 21, 2019 at 8:36
  • It might work on mounted systems too, especially if that system doesn't have much activity. But on tank/var/log you might find a pristine snapshot, and then an instant later syslogd and friends will write to it. Can you expand your question to say why you want to figure out whether a snapshot is pristine? What will your script do if it is, or if it isn't?
    – Jim L.
    Jul 21, 2019 at 17:13
  • I appended a section about why I am interested in this to my question. I do not expect daemons like syslogd or journald to write to my datasets. It's more about user data.
    – s-m-e
    Jul 21, 2019 at 19:01
2

For future reference, I went with the below logic. After intensive tests, it appears to be working fine. Better answers and ideas are much appreciated, though. For reference, also see this discussion on the zfs-discuss mailing list.

for dataset in tree:

    # If there is no snapshot for this dataset, make one ...
    if len(dataset['SNAPSHOTS']) == 0:
        make_snapshot(dataset)
        continue

    # If nothing has been written, no snapshot is required.
    if dataset['written'] == 0:
        continue

    # If written is north of 1 MByte, make a snapshot.
    if dataset['written'] > (1024 ** 2):
        make_snapshot(dataset)
        continue

    # If the dataset is not a filesystem and
    # we got this far, make a snapshot.
    if dataset['type'] == 'volume':
        make_snapshot(dataset)
        continue

    # Only filesystems with less than 1 MByte written left.
    # Let's look at the diff of the last snapshot.
    diff_out = run_command([
        'zfs', 'diff',
        dataset['NAME'] + '@' + dataset['SNAPSHOTS'][-1]['NAME']
        ])

    # If the diff is not empty, make a snapshot.
    if len(diff_out.strip(' \t\n')) > 0:
        make_snapshot(dataset)
2

Here's a bash script that handles this for a recursive pool, alter to your liking.

function processChanges() {
        POOL=$1
        ROWS=$(zfs get -Hr written ${POOL} |grep -v @ |awk '{print $1 "|" $3}' |grep -v "${POOL}|")
        for ROW in ${ROWS}; do
                CHANGED=0
                V=${ROW##*|}
                P=${ROW%%|*}
                echo -n "Checking ${P}... "
                if [ "${V: -1}" = "K" ]; then
                        BYTES_CHANGED=$(echo "${V:0:-1} * 1024" |bc )
                elif [ "${V: -1}" = "M" ]; then
                        BYTES_CHANGED=$(echo "${V:0:-1} * 1024 * 1024" |bc )
                elif [ "${V: -1}" = "G" ]; then
                        BYTES_CHANGED=$(echo "${V:0:-1} * 1024 * 1024 * 1024" |bc )
                else
                        BYTES_CHANGED=${V}
                fi
                BYTES_CHANGED=${BYTES_CHANGED%%.*}
                MEG=$(( 1 * 1024 * 1024 ))
                if [ ${BYTES_CHANGED} -gt ${MEG} ]; then
                        CHANGED=1;
                elif [ ${BYTES_CHANGED} -ne 0 ]; then
                        LASTSNAP=$(zfs list -t snap ${P} |grep ${P} |sort |tail -n1)
                        LASTSNAP=$(echo ${LASTSNAP##*@}); LASTSNAP=$(echo ${LASTSNAP%% *});
                        DIFF=$(zfs diff ${P}@${LASTSNAP})
                        if [ "${DIFF}" != "" ]; then
                                CHANGED=2;
                        fi
                fi
                if [ ${CHANGED} -gt 0 ]; then
                        echo "Changed"
                else
                        echo "No changes"
                fi
        done
}

processChanges s/storage

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .