为什么 rsync 不对网络上的单个文件使用增量传输?

Mic*_*ael 15 rsync

我看过这个问题这个问题,但它们似乎没有解决我看到的症状。

我有一个很大的日志文件(大约 600 MB),我试图通过蜂窝网络传输它。因为它是一个日志文件,所以它只是附加到(虽然它实际上是在一个 SQLite 数据库中,只执行了 INSERT,所以它并不那么简单,但除了最后 4k 页(或者可能是一个很少)文件每次都是相同的。重要的是只有更改(以及需要传输的任何校验和)实际发送,因为数据连接是计量的。

然而,当我通过未计量的连接(例如免费 wifi 热点)执行测试时,我没有看到观察到或报告的数据传输加速或减少。通过慢速 WiFi 连接,我看到大约 1MB/s 或更少,报告传输将需要近 20 分钟。通过快速的 WiFi 连接,我看到了统一的更快速度,但没有关于加速的报告,第二次尝试传输(现在应该更快,因为两个文件相同)现在确实显示出任何差异。

我正在使用的(经过消毒以删除敏感信息)命令是:

rsync 'ssh -p 9999' --progress LogFile michael@my.host.zzz:/home/michael/logs/LogFile
Run Code Online (Sandbox Code Playgroud)

我最后得到的输出如下所示:

LogFile
    640,856,064 100%   21.25MB/s   0:00:28 (xfr$1, to-chk=0/1)
Run Code Online (Sandbox Code Playgroud)

没有提到任何类型的加速。

我怀疑问题可能是以下之一:

  • 我缺少一些命令行选项。但是,重新阅读手册页似乎表明默认启用增量传输:我只看到禁用它们的选项。
  • 由于服务器位于仅允许 ssh 的防火墙后面,我在 ssh 上使用 rsync(甚至在非标准端口上)。不过,我没有看到任何明确说明如果 rsync 守护程序未运行,增量传输将不起作用的内容。我尝试使用“::”表示法而不是“:”,但手册页对“模块”是什么不是很清楚,并且我的命令因指定无效模块而被拒绝。

我排除了以下情况:

  • 不在本地网络上执行的增量传输。排除,因为我正在尝试通过 Internet 执行传输
  • 校验和计算造成的开销。我在快速和慢速 Wifi 连接上都看到过这种行为,而且传输速率似乎不受计算限制。

der*_*ert 28

概括

数据库往往会保存大量元数据、组织数据等。插入不太可能是简单的附加,就像文本文件一样。测试 SQLite 表明它在 WAL 和非 WAL 模式下都是这样的。这导致 rsync 必须同步比您预期的更多的数据。您可以通过使用低--block-size(以更多开销计算和传输校验和为代价)来减少这种开销。

更好的方法可能是将新记录转储为 SQL 转储、压缩并传输它。或者,似乎有多种 SQLite 复制解决方案,您可以使用其中之一。

roaima建议至少你可以做一个完整的 SQL 转储,使用 压缩它gzip --rsyncable,然后 rsync 。我想值得一试,看看这是否是一个足够小的增量。

细节

你正在尝试的应该有效。我个人会添加--partial到您的 rsync 选项中,以防万一它以某种方式将不断增长的文件检测为部分传输。您还可以使用 获得更好的转会统计数据--stats

要检查的第二件事是 SQLite 是否真的只涉及几页——老实说,如果它在整个文件中写入页面,我不会感到惊讶。检查的一种快速方法是cmp -l在两个版本上使用- 查看除最后几个之外的页面是否有更改。请记住,rsync“页面”/块的概念与 SQLite 的不同;您可以通过 .rsync 更改 rsync --block-size。减少它可能会有所帮助。

编辑:我用 SQLite 做了一个快速测试。即使有 32k 页,在每一页上添加一堆乱写的日志条目。详情如下。

编辑 2:在 WAL 模式下似乎更好,尽管您仍然需要大量开销,可能来自检查点。

编辑 3:每次传输添加的数据越多越好 - 我猜它可能一遍又一遍地涂抹某些块。因此,无论是向它们写入一次还是一百次,您都在传输同一组块。

顺便说一句:为了最小化传输,您可能比 rsync 做得更好。例如,自上次传输运行xz --best(或什至gzip)以来新记录的 SQL 转储可能会小一些。

快速 SQLite 测试

架构:

CREATE TABLE log (id integer primary key not null, ts integer not null, app text not null, message text not null);
CREATE INDEX log_ts_idx on log(ts);
CREATE INDEX log_app_idx on log(app);
Run Code Online (Sandbox Code Playgroud)

Perl程序:

use 5.022;
use DBI;

my $DBH = DBI->connect('dbi:SQLite:test.db', '', '', {RaiseError => 1, AutoCommit => 0})
    or die "connect...";

my @apps = (
    '[kthreadd]',        '[ksoftirqd/0]',
    ? # there were 191 of these
    '[kworker/5:0H]',
);

my @messages = <DATA>;

(my $curr_time) = $DBH->selectrow_array(<<QUERY);
    SELECT COALESCE(MAX(ts),978307200) FROM log
QUERY

my $n_apps = @apps;
my $n_msgs = @messages;
say "Apps: $n_apps";
say "Messages: $n_msgs";
say 'Start time: ', scalar gmtime($curr_time), ' UTC';

my $sth = $DBH->prepare(<<QUERY);
    INSERT INTO log(ts, app, message) VALUES (?, ?, ?)
QUERY

for (my $i = 0; $i < 10_000; ++$i) {
    $sth->execute(int($curr_time), $apps[int rand $n_apps], $messages[int rand $n_msgs]);
    $curr_time += rand 0.1;
}
$DBH->commit;

__DATA__
microcode: CPU0 microcode updated early to revision 0x19, date = 2013-06-21
Linux version 4.5.0-2-amd64 (debian-kernel@lists.debian.org) (gcc version 5.3.1 20160528 (Debian 5.3.1-21) ) #1 SMP Debian 4.5.5-1 (2016-05-29)
?
Run Code Online (Sandbox Code Playgroud)

还有更多示例日志消息 (2076)。

检查哪些页面发生了变化:

cp test.db test.db.old
perl test.pl
cmp -l test.db.old test.db | perl -n -E '/^\s*(\d+) / or die "wtf"; $bucket{int $1/32768} = 1; END { say join "\n", sort( { $a <=> $b } keys %bucket) }'
Run Code Online (Sandbox Code Playgroud)