如何一次复制多个快照而不重复数据?

如何一次复制多个快照而不重复数据?

我有一个 3.7TiB 的实时 btrfs 文件系统,其已满率 >90%,包括旧快照和新的 4TB 备份硬盘。如何将所有现有快照复制到备份硬盘?

我试过

# btrfs send home_1 home_2 home_3 share_1 share_2 share_3 ...

但还没传输完2/3的快照,备份硬盘就满了。所以我做了一些研究:

# ### create test image
# dd bs=1M count=1000 > btrfs_test.dd
# mkfs.btrfs btrfs_test.dd

# ### create snapshots
# btrfs subvol create testvol/
# btrfs subvol snapshot -r testvol/ testvol_0/
# ### (copy some ISO image)
# btrfs subvol snapshot -r testvol/ testvol_1/
# ### (proceed until testvol_3)

我的测试文件系统已满 91%,使用了 818MiB。

# btrfs send testvol_* | wc -c 
At subvol testvol_0
At subvol testvol_1
At subvol testvol_2
At subvol testvol_3
1466441978     # 1398MiB >> 1000MiB

当简单地用1个命令发送所有快照时,数据会被重复,并且流的大小以及接收端的快照的大小超出了原始使用的空间和硬盘的容量。

所以实际的问题变成了:如何复制多个快照而不复制其中 2 个或更多快照中包含的数据?

对于这个简单的测试用例,我成功尝试了增量方法:

# ( btrfs send testvol_0; btrfs send -p testvol_0 testvol_1; btrfs send -p testvol_1 testvol_2; btrfs send -p testvol_2 testvol_3 ) | wc -c 
At subvol testvol_0
At subvol testvol_1
At subvol testvol_2
At subvol testvol_3
838778546    # 800 MiB < 1000MiB

但在真实的文件系统上有多个子卷,每个子卷都有多个快照。我无法定义任何与 一起使用的顺序-p。当然,如果一个数据块在子卷之间共享home_1home_2并且share_3我只想传输和存储一次。有什么想法如何做到这一点?

答案1

长话短说:一般来说,使用-c顶部描述的参数是有效的。当快照包含硬链接时,Linux 内核中存在一个错误,该错误会在发送快照时触发错误 - 有关详细信息,请参阅答案末尾的结论。


我正在尝试该-c参数,它看起来很有希望:

# for i in {0..3}; do btrfs send testvol_$i $(ls -d testvol_* | head -n$i | sed 's/^/-c /'); done | wc -c
### btrfs send testvol_0
### btrfs send testvol_1 -c testvol_0
### btrfs send testvol_2 -c testvol_0 -c testvol_1
### btrfs send testvol_3 -c testvol_0 -c testvol_1 -c testvol_2
At subvol testvol_0
At subvol testvol_1
At subvol testvol_2
At subvol testvol_3
838778546    # also 800MiB

我仍然不确定这是否是我所需要的。对此解决方案有何评论?

更新:为了用我的真实文件系统测试这一点,我编写了一个 Perl 脚本来轻松地发送一组子卷(创建列表很快-c就变得乏味)并将一些数据发送到/dev/null

#!/usr/bin/env perl

use strict;
use warnings;

my @subvols = @ARGV
  or die "Usage: $0 SUBVOLUME ...\n";

for(@subvols) {
    -d $_
      or die "Not a directory: $_\n";
}

for my $i (0 .. $#subvols) {
    my $subvol = $subvols[$i];
    my @clones = map { ('-c', $_) } @subvols[ 0 .. $i-1 ];
    print "btrfs send $subvol @clones\n";
}

结果:

  • btrfs send some-subvolme_* | pv > /dev/null:24GiB 0:04:17 [95.2MiB/秒]
  • perl btrfs-send-all.pl some-subvolume_* | bash | pv > /dev/null:12.7GiB 0:03:58 [54.7MiB/秒]

这并没有带来太大的性能提升,但几乎存储空间减少 50%!我现在正在尝试实际运行它......

更新:我成功传输了 2 个快照,但第三个快照btrfs receive失败并显示以下错误消息:

ERROR: unlink path/to/some/file/in/the/snapshot failed. No such file or directory

指定的文件存在于subvol_2andsubvol_3但是不是然而在subvol_1.

我尝试比较发送和接收的快照:

# ### sender
# du -s subvol_{1,2,3}
132472304       subvol_1
117069504       subvol_2
126015636       subvol_3

# ### receiver
# du -s subvol_*
132472304       subvol_1
117069504       subvol_2
132472304       subvol_3

前两个快照似乎传输正确,但subvol_3实际上是subvol_1.这绝对是一个克隆,因为备份磁盘上的已用空间仅为快照总大小的 39%,远高于 1/3,因为它们共享大多数文件。

为什么btrfs send subvol_3 -c subvol_1 -c subvol_2 | btrfs receive不能正确传输快照,而是克隆然后无法删除应保留且仅存在于的subvol_1文件?subvol_3subvol_2

更新:会不会是这个bug?https://patchwork.kernel.org/patch/10073969/

我正在运行带有内核 4.9 的 Debian 9 Stretch,它似乎比补丁更旧。

更新:因为我找不到任何解决方案,所以我只是复制了每个子卷的最新快照。然后我有大约 500GiB 的可用空间,并尝试使用已复制的快照作为-p参数添加上一个快照。然后,对于与上面相同的快照,我收到了相同的错误消息。

结论:我的结论是我遇到了上面链接的错误。我必须使用更新的 Linux 内核重新启动这台机器,或者从另一台计算机访问文件系统,但这是不可行的,因为这是一个生产系统。

到目前为止,我对 btrfs 没有任何问题,但运行 rsnapshot(这会创建大量硬链接)并发送 btrfs 快照在当前的 Debian 稳定版上仍然可能会出现问题。

相关内容