如何通过简单的是/否问题“有效”确定 ZFS 数据集自上次快照以来是否已更改?

如何通过简单的是/否问题“有效”确定 ZFS 数据集自上次快照以来是否已更改?

假设我有一个名为 的池tank,其中有一个名为 的数据集data。还有至少一个data命名的快照last_snapshot

tank
tank/data
tank/data@last_snapshot

diff据我所知,确定数据集是否已更改的“慢”方法是检查 的输出:

zfs diff tank/data@last_snapshot

它将显示自上次快照以来每个单独文件/文件夹的数据集的所有更改。如果有很多更改,此命令会产生大量输出并运行几秒钟甚至几分钟。

一种更快但(据我所知)不太可靠的方法是查看written数据集的属性:

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

如果数据集(不是任何快照)的值written介于 56 到 128k 之间,则数据集通常自上次/最近快照以来尚未修改。这要快得多,但在不理解为什么这个数字会变化如此之大的情况下,我不想依赖这种方法。

我如何安全快速地提出是/否问题:自上次快照以来数据集是否已更改/修改?


设计理念(背景):我确实有相当数量的数据集,它们很少会收到“重大”更改,比如每几周一次。每天制作一次快照并保留最后一个(例如 7 个)很快就会导致失去最后一个“重大”更改之前的状态。这就是我的想法:我们只在发生“重大”更改时才制作快照,并保留其中的最后 7 个。

答案1

这是一个好问题,我现在能做的就是投赞成票。

在思考这个问题时,我尝试过的一些猜测途径是:

  • 再次对数据集进行快照,并比较createtxg两个快照的(“出生”)属性。在一次测试中,我很幸运并且具有连续的值(这意味着在创建 snapA 和创建 snapB 之间的过渡期间没有发生任何事务)。这个事实后来被证明毫无价值,因为进一步的研究告诉我,事务组在整个池中全局编号,而不是在每个文件系统中唯一编号。

  • createtxg将快照的值与文件系统的最新事务组进行比较。我不确定,但如果快照createtxg等于或高于文件系统的最新事务组,则可以推断快照创建本身是已提交到该文件系统的最新事务。我不确定这是否属实,但如果可以将快照createtxg与文件系统的最新快照进行比较txg以精确确定快照是否“原始”,那将是偶然的。

此时,我能提供的最佳答案是信任written文件系统的属性,但仅限于严格的参数范围内:

1)确保所有待处理的事务已提交到文件系统

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

2) 确保文件系统处于非活动状态(未安装)

zfs umount tank/data

written3)使用-p以下标志查询属性的确切机器可解析值zfs list

zfs list -Hpo written tank/data

在我推断快照是原始的之前,该数字必须恰好为 0。 “小”还不够好,如果我们把zfs手册页按面值计算:

以下本机属性包含有关数据集的只读统计信息。这些属性既不能设置,也不能继承。除非另有说明,本机属性适用于所有数据集类型。
...

createtxg

在其中创建数据集的事务组 (txg)。书签createtxg与它们最初绑定的快照具有相同的内容。此属性适合对快照列表进行排序,例如增量发送和接收。
...

written

自上一个快照以来写入此数据集的引用空间量。

进一步的承诺是:

written@snapshot

自指定快照以来写入此数据集的引用空间量。这是此数据集引用但指定快照未引用的空间。

需要卸载文件系统,以便当您注意到值为 0 并开始对该状态采取行动时不会出现竞争条件,但与此同时另一个进程正要写入文件系统并破坏您的聚会。

如果有人知道如何查询文件系统最近提交的事务组号,我有义务知道该方法。一些基本的谷歌搜索没有找到任何我认为有用的东西。

另一个有趣的属性是referenced快照的属性与其父文件系统的属性。

referenced

此数据集可访问的数据量,可能会也可能不会与池中的其他数据集共享。创建快照或克隆时,它最初引用与创建它的文件系统或快照相同的空间量,因为其内容是相同的。

然而,我的预感是这个属性也会导致错误的希望,因为文件系统和快照可能都引用 10G 数据,但它与磁盘上实际块中的 10G 不同,正如短语“可能或可能”所暗示的那样不被共享”。匹配referenced值似乎更像是必要条件而不是充分条件。

总而言之,带着很大的谦虚和不确定性,我认为人们必须坚持这个zfs list输出,然后才能假设快照是原始的:

# 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

弄脏了快照,它就不再原始了:

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

将其滚回去,它又变得原始了:

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

但为了获得最高的完整性,在查询属性之前,应该卸载文件系统,也许需要卸载一段时间written

我将非常感谢任何人对任何错误的假设、疏忽或任何好心人提供的额外见解的纠正。

答案2

为了将来的参考,我采用了以下逻辑。经过密集测试,它似乎运行良好。不过,我们非常感谢更好的答案和想法。作为参考,另请参阅zfs-discuss 邮件列表上的讨论

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)

答案3

这是一个bash为递归池处理此问题的脚本,可以根据您的喜好进行更改。

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

相关内容