This is the mechanism that replaced content-defined chunking. A rewritten field - a body, a JSON
blob of notifications, a serialized settings array - is almost entirely the value it replaces,
but a byte-level splitter cannot see that because the change is interior. Handing the previous
version to the compressor as its dictionary can: measured on 5,967-byte notification blobs with
one flag flipped and one entry appended, zstd -19 against the previous version reached 63.70x
and 94 bytes per object, against 4.49x and 1,330 bytes standalone - 93.0% smaller. At level 3 it
is 51.93x, so the win survives the fast setting used on the flush path.
The previous version is already in memory during a capture as the entity's original, and is
retrievable from the store by digest during a restore, so neither path pays to fetch it twice.
Two rules bound the dependency this creates. A chain deeper than the policy allows is anchored
instead. And an encoding that does not beat the standalone form by DeltaCodec::MIN_GAIN is
discarded in favour of the anchor, so a codec without dictionary support produces correct output
without a branch at any call site.
How much smaller a delta must be than its anchor to be worth the dependency.
A delta that saves less than this is discarded. A chain link costs restore latency and widens
the blast radius of a lost frame, so a small saving does not cover it.
Walks a chain of successive versions to the value the last frame encodes.
The whole chain is decoded in order because a delta is meaningless without the link before
it. It must begin with an anchor: a chain starting at a delta cannot be decoded without a
parent the caller has not supplied.
An anchor part-way through is NOT an error and must not be treated as one. Two ordinary
things produce it - the depth cap forcing a re-anchor, and DeltaCodec::encode() discarding a
delta that failed to beat its own anchor by DeltaCodec::MIN_GAIN, which happens whenever a
value is small enough that a chain link cannot pay for itself. Such an anchor RESTARTS the
chain: every frame before it becomes unnecessary and depth counting begins again.
A depth that does not follow its predecessor within the current run is a corruption symptom
and is named in the error.
Parameters
list<\Drupal\strata\Delta\DeltaFrame>
$chain
Frames from an anchor to the target, in order.
Throws
\RuntimeException
When the chain is empty, does not start at an anchor, has a depth discontinuity within a
run, or any link fails to decode.
A truncated decompression is indistinguishable from correct output until much later, so the
recorded plain length is checked before the bytes leave this class.
Parameters
\Drupal\strata\Delta\DeltaFrame
$frame
The frame that was decoded.
string
$plain
The decoded bytes.
Throws
\RuntimeException
When the decoded length does not match what the frame recorded.
Returns
string
—
The same bytes, once they are known to be complete.