\Drupal\strata\File BlockSplitter

Splits a file into fixed blocks so a change costs the blocks it touched.

Whole-object storage would re-upload a 256 MiB video because two bytes of metadata changed. Blocks make the cost proportional to the edit: hash each one, store the ones that are new, and keep an ordered list of digests as the file's version.

64 KiB, and the size was measured rather than picked. On a 256 MiB file, a 2 MiB in-place edit costs 2.1 MiB at 64 KiB blocks against 3.0 MiB at 1 MiB blocks and 8.0 MiB at 4 MiB, and hashing the whole file takes 0.40 s either way - 645 MB/s. Smaller blocks would keep shrinking the patch and start costing more in per-block overhead than they save.

What fixed blocks cannot do, stated plainly. They cannot follow an insertion. Inserting 2 MiB at the front shifts every block after it, so 60.8% of the file looks changed at every block size. That is what ShiftDetector is for: a high changed ratio is the signature of a shift rather than an edit, and it is reported rather than silently paid for. Content-defined chunking is the answer to that case and costs 4.29 MB/s, which is 150x slower, so it is never a default.

Summary

Methods
Properties
Constants
__construct
size
count
split
map
mapString
secondsFor
No public properties found
DEFAULT_SIZE
MIN_SIZE
MAX_SIZE
MEASURED_THROUGHPUT
No protected methods found
No protected properties found
No protected constants found
No private methods found
size
No private constants found

Constant

DEFAULT_SIZE

DEFAULT_SIZE = 65536

Block size in bytes.

64 KiB. See the class docblock for the measurements this comes from.

MIN_SIZE

MIN_SIZE = 4096

Smallest block size accepted.

MAX_SIZE

MAX_SIZE = 8388608

Largest block size accepted.

MEASURED_THROUGHPUT

MEASURED_THROUGHPUT = 645000000

Measured throughput on the reference host, in bytes per second.

Hashing at 64 KiB blocks, which is what a first capture and every drift check cost.

Properties

$size

$size : int

Type

int

Methods

__construct()

__construct(int  $size = \self::DEFAULT_SIZE) : mixed

Constructs a splitter.

Parameters

int $size

Block size in bytes.

Throws

\InvalidArgumentException

When the size is outside the supported range.

Returns

mixed —

size()

size() : int

The configured block size.

Returns

int —

Bytes.

count()

count(int  $length) : int

How many blocks a file of a given length has.

Parameters

int $length

File length in bytes.

Returns

int —

The count. A zero-length file has no blocks, which is what lets an empty file be told apart from one whose blocks are all absent.

split()

split(resource  $stream) : \Generator<int,array{index: int, offset: int, hash: string, bytes: string}>

Splits an open stream, yielding each block as it is read.

A generator, so splitting a 256 MiB file holds one block in memory rather than the file.

Parameters

resource $stream

An open, readable stream positioned where splitting should start.

Throws

\InvalidArgumentException

When the argument is not a stream.

\RuntimeException

When a read fails.

Returns

\Generator

Each block's position, content address and content, in order.

map()

map(string  $path) : \Drupal\strata\File\FileMap

The map of a file on disk.

Parameters

string $path

Path to the file.

Throws

\RuntimeException

When the file cannot be opened or read.

Returns

\Drupal\strata\File\FileMap —

The map.

mapString()

mapString(string  $path, string  $contents) : \Drupal\strata\File\FileMap

The map of a value already in memory.

Parameters

string $path

The path the value belongs to, which the map records.

string $contents

The bytes.

Returns

\Drupal\strata\File\FileMap —

The map.

secondsFor()

secondsFor(int  $bytes) : float

How long splitting a file of a given size takes on the reference host.

Printed next to the file-capture switch, because a first capture of an 80 GB media tree is a number an operator should see before turning it on rather than after.

Parameters

int $bytes

File size.

Returns

float —

Seconds.