Remove the read-ahead folder of a killed restore with what it kept; changelog and DR guide: a stopped full restore goes on where it was
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / psalm (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled
CI / integration-targets (push) Has been cancelled
CI / integration-user (32) (push) Has been cancelled
CI / integration-user (33) (push) Has been cancelled
CI / integration-user (34) (push) Has been cancelled
CI / integration-user (35) (push) Has been cancelled
CI / integration-full-restore (push) Has been cancelled

This commit is contained in:
2026-11-05 08:35:28 +01:00
parent 029b3b9cc7
commit bf48d339e4
4 changed files with 30 additions and 6 deletions
+12 -3
View File
@@ -35,9 +35,18 @@ shown under 1.0.0. The git tag of a release candidate goes after the date.
since 1.0.0-rc.4. Over SMB the packs are read in 8 MiB reads instead of 1 MiB: over a line with
a 130 ms round trip, reading was four times faster in a test (18 instead of 4.5 MiB/s).
- Over SMB the index files, which a new server reads in `backup:target:add`, take fewer round
trips: from Canada to a Hetzner Storage Box (130 ms),
0.66 instead of 1.05 s per file, so about 5 instead of 8 hours for the 27,000 index files of a
1 TB repository.
trips: from Canada to a Hetzner Storage Box (130 ms), 0.66 instead of 1.05 s per file, so about
5 instead of 8 hours for the 27,000 index files of a 1 TB repository.
- `occ backup:restore:full` that runs again after it stopped (`--force`) goes on where it was: data
files already in place with the size and modification time of the backup are left, and only
the rest is read from the location. Before, a stopped restore of 1 TB started over.
### Fixed
- A full restore over SMB could hang at 100% CPU when the connection to the server dropped: a
failed read was counted as data. It is now read again over a new connection. Found in a
disaster recovery test of 1 TB from Canada to a Hetzner Storage Box, after 392 GiB.
- The read-ahead folder of a restore that was killed while it wrote is removed by the next run;
it kept one file of up to 8 MiB.
- `occ backup:target:add` only estimates the time left for reading the index once enough of it
is in. The first file also carries the time to connect, which made it say "about 642 min left"
for something done within a minute.
+3 -1
View File
@@ -103,7 +103,9 @@ mode on yourself before, it stays on: switch it off with `occ maintenance:mode -
If it stops halfway, maintenance mode stays on (the instance may be partly restored). Fix the cause,
then switch maintenance mode off and run the restore again with `--force`, in one line (the message
prints it): `occ maintenance:mode --off && occ backup:restore:full <location> <snapshot> --force`.
Leave out `--install-apps` this time, so that the instance is open only for the seconds until the
It goes on where it stopped: data files that are already in place with the size and modification
time of the backup are left, so only the rest comes from the location again (since 1.0.2; before,
it started over). The database is restored again in full, which takes minutes. Leave out `--install-apps` this time, so that the instance is open only for the seconds until the
restore has switched maintenance mode on again; apps the first run could not install are listed at
the end of the restore, install those afterwards.
If occ then says that Nextcloud or one of the apps requires an upgrade, the database is already
+11 -1
View File
@@ -253,9 +253,19 @@ final class Repository {
if (!$this->backend instanceof IReadsAhead || !$this->backend->canReadAhead()) {
return null;
}
// what a restore that was stopped left behind: empty, its files were unlinked when opened
// what a restore that was stopped left behind: its files are unlinked once opened, but one
// that was being written when the process was killed stays (8 MiB, DR test 2026-11-05)
$stale = glob(rtrim($target, '/') . '/.ngb-restore-spool-*', GLOB_ONLYDIR);
foreach ($stale === false ? [] : $stale as $dir) {
if (is_link($dir)) {
continue;
}
$files = glob($dir . '/*');
foreach ($files === false ? [] : $files as $file) {
if (is_file($file) && !is_link($file)) {
@unlink($file);
}
}
@rmdir($dir);
}
$plan = new ReadAheadPlan(rtrim($target, '/') . '/.ngb-restore-spool-' . bin2hex(random_bytes(4)));
+4 -1
View File
@@ -75,11 +75,14 @@ class ReadAheadTest extends TestCase {
$this->assertSame(2, $backend->snapshotReads, 'the restore itself read the snapshot again');
}
public function testTheEmptySpoolOfAStoppedRestoreIsRemoved(): void {
public function testTheSpoolOfAStoppedRestoreIsRemoved(): void {
$target = $this->tempDir();
mkdir("$target/.ngb-restore-spool-0badc0de", 0700); // a restore that was killed
mkdir("$target/.ngb-restore-spool-8badf00d", 0700); // ... while it wrote a range
file_put_contents("$target/.ngb-restore-spool-8badf00d/smb-01234567", str_repeat('x', 4096));
$this->assertRestored($this->restore(new ReadAheadBackend(new LocalBackend($this->root)), '', $target));
$this->assertSame([], glob("$target/.ngb-restore-spool-*"));
}
public function testARangeThatCouldNotBeReadAheadIsReadTheUsualWay(): void {