Read files for a backup in pieces of 4 MiB instead of 8 KiB

PHP reads a file 8 KiB at a time by default. On a disk where every
request waits, such as a network or cloud volume, that made reading five
times slower: on the test server 5 MiB/s instead of 27 MiB/s. Without
PHP's read buffer every read of a blob is one request to the disk. The
same for packs on a local location.
This commit is contained in:
2026-11-07 12:44:57 +01:00
parent 569077fa69
commit 953312bf44
5 changed files with 28 additions and 3 deletions
+3
View File
@@ -40,6 +40,9 @@ shown under 1.0.0. The git tag of a release candidate goes after the date.
- The key page warns against creating a new key when you are restoring a server.
### Changed
- Reading files for a backup is much faster on disks where every request waits, such as network and
cloud volumes: files are read in pieces of 4 MiB instead of 8 KiB. On a test server the disk
delivered 27 MiB/s instead of 5 MiB/s.
- Locations are backed up one after the other: a backup that has to wait shows as queued. A restore
point has the time its backup really began, not the time it was queued.
- Choosing a time no longer starts a backup right away when that time has already passed today.
+3
View File
@@ -67,6 +67,9 @@ final class LocalBackend implements IBackend {
if ($fh === false) {
throw new BackendException('Not found: ' . $path);
}
// Packs are read in large pieces: one request each, not 8 KiB at a time (see Repository::openForReading()).
stream_set_read_buffer($fh, 0);
stream_set_chunk_size($fh, 4 * 1048576);
return $fh;
}
+1 -1
View File
@@ -120,7 +120,7 @@ final class BackupRun {
if ($cur === null || $cur['path'] !== $logical) {
$cur = ['path' => $logical, 'offset' => 0, 'blobs' => [], 'version' => $version];
}
$fh = @fopen($path, 'rb');
$fh = Repository::openForReading($path);
if ($fh === false) {
self::recordUnreadable($state, $stats, $logical);
$state['cur'] = null;
+21 -1
View File
@@ -193,6 +193,23 @@ final class Repository {
return $config;
}
/**
* Open a local file to read it in whole blobs. PHP reads a file in pieces of 8 KiB by default;
* on a disk where each request waits (a network or cloud volume) that made reading five times
* slower: 5 MiB/s instead of 27 MiB/s on nc01. Without PHP's read buffer every fread() of a
* blob is one request to the disk.
*
* @return resource|false
*/
public static function openForReading(string $path) {
$fh = @fopen($path, 'rb');
if ($fh !== false) {
stream_set_read_buffer($fh, 0);
stream_set_chunk_size($fh, self::BLOB_SIZE);
}
return $fh;
}
private static function configMatches(array $config, KeyRing $keys): bool {
try {
return isset($config['check'], $config['id'])
@@ -1148,7 +1165,10 @@ final class Repository {
for ($attempt = 1; $attempt <= 3; $attempt++) {
$before = self::fileVersion($path);
$blobs = [];
$fh = fopen($path, 'rb');
$fh = self::openForReading($path);
if ($fh === false) {
throw new RepositoryException('Cannot read ' . $path);
}
while (($data = self::readFull($fh, self::BLOB_SIZE)) !== '') {
$blobs[] = $this->storeBlob($data, $packs, $stats);
}
-1
View File
@@ -367,7 +367,6 @@
<code><![CDATA[$encrypted]]></code>
<code><![CDATA[$encrypted]]></code>
<code><![CDATA[$encrypted]]></code>
<code><![CDATA[$fh]]></code>
<code><![CDATA[$plain]]></code>
<code><![CDATA[$plain]]></code>
<code><![CDATA[$plain]]></code>