MIN_GAIN
MIN_GAIN = 0.05
How much smaller a rewrite must be to be worth the requests it costs.
An object that shrinks by less than this is left alone. The saving has to cover a GET, a PUT and the index writes.
Rewrites stored objects at a denser compression setting.
The flush path compresses at zstd level 1 because it runs inside a web request: 4.28x at 422.9 MB/s, against level 19's 4.87x at 4.5 MB/s. Compaction is not in a request and can spend the CPU, and with a trained dictionary level 19 reaches 5.86x. On a store where tree manifests are 72% of the bytes, that is the largest single reduction available after the base interval itself.
A frame's content address does not change, because the address is the digest of the DECODED bytes. That is what makes this safe to do to live data: every tree, segment and delta parent that named the frame still names it, and nothing has to be rewritten to follow it. Only the object the frame lives in changes, and the index row that says where.
Packs are rewritten whole, never frame by frame. Densifying one 16 KiB frame out of a 1 MiB pack into its own object would trade a compression gain for one Class A operation per frame, which is the cost Packer exists to avoid. One pack in, one pack out, one GET and one PUT.
Every frame in a pack is carried forward, including ones nothing references any more. Compaction changes how bytes are stored and never which bytes exist; removing content is a prune, and a prune produces a receipt.
$provider : \Drupal\strata\Storage\StorageProviderInterface
$index : \Drupal\strata\Cas\FrameIndexInterface
$store : \Drupal\strata\Cas\ObjectStore
$codecs : \Drupal\strata\Codec\CodecRegistry
$cipher : \Drupal\strata\Crypto\CipherInterface
$packer : \Drupal\strata\Cas\Packer
__construct(\Drupal\strata\Storage\StorageProviderInterface $provider, \Drupal\strata\Cas\FrameIndexInterface $index, \Drupal\strata\Cas\ObjectStore $store, \Drupal\strata\Codec\CodecRegistry $codecs, \Drupal\strata\Crypto\CipherInterface $cipher, \Drupal\strata\Cas\Packer $packer = new \Drupal\strata\Cas\Packer(), int $level = 19, string|null $dictionary = null, string|null $dictionaryId = null) : mixed
Constructs a recompressor.
| \Drupal\strata\Storage\StorageProviderInterface | $provider | Where objects are read from and written to. |
| \Drupal\strata\Cas\FrameIndexInterface | $index | Repointed at each frame's new location and size. |
| \Drupal\strata\Cas\ObjectStore | $store | Reads each frame back through whatever encoded it. |
| \Drupal\strata\Codec\CodecRegistry | $codecs | Supplies the dense writer, which may be one that shells out. |
| \Drupal\strata\Crypto\CipherInterface | $cipher | Re-seals each rewritten frame. |
| \Drupal\strata\Cas\Packer | $packer | Rebuilds the pack, so the rewritten object carries a directory like any other. |
| int | $level | Compression level to rewrite at. |
| string|null | $dictionary | Dictionary bytes to compress against, or NULL. |
| string|null | $dictionaryId | Identifier recorded in each rewritten frame, required whenever a dictionary is given. |
recompressPack(string $key, bool $force = false) : array{frames: int, before: int, after: int, saved: int, rewritten: bool}
Rewrites one pack at the dense setting.
| string | $key | The pack's object key. |
| bool | $force | TRUE to write the new object however little it saves. A key rotation re-seals bytes without shrinking them, and refusing on the gain would leave it unable to finish. |
When the pack cannot be read or a frame inside it does not decode. Either is a corruption symptom and neither is safe to skip silently while rewriting the rest.
How many frames were carried forward, the object's size before and after, how many bytes that saved, and whether the new object was actually written.
recompressFrame(string $hash, bool $force = false) : int
Rewrites one standalone frame at the dense setting.
Only frames at or above the pack target are stored this way, so this path is rare and stays standalone rather than being folded into a pack.
| string | $hash | Frame content address. |
| bool | $force | TRUE to write the new object however little it saves, for the same reason Recompressor::recompressPack() takes the flag. |
When the frame is not indexed, is packed, or does not decode.
Bytes saved, or zero when the frame was left as it was. Zero is also a re-seal that saved nothing, so it is not a statement that nothing was written when $force is TRUE.
recompressPacks(list$keys, int $budget = 0) : array{packs: int, frames: int, before: int, after: int, skipped: int, problems: list<string>}
Rewrites a run of packs, stopping at a byte budget.
Compaction runs inside a cron window, so the caller sets how much work one pass may do rather than discovering it afterwards. A pack that fails is reported and skipped, because one corrupt object must not stop the pass from densifying the rest.
| list |
$keys | Pack object keys. |
| int | $budget | Most stored bytes to read in this pass; zero for no limit. |
How many packs were rewritten, how many frames that moved, what those packs occupied before and occupy now, how many packs were already dense enough, and what could not be read.
decoded(string $hash) : string
Reads a frame back and checks it against its address.
| string | $hash | Frame content address. |
When the frame does not read back, or reads back as different content. Re-encoding content that does not match its address would file the wrong bytes under a name something else depends on.
The decoded frame.
encode(string $plain, string $hash, string|null $parent = null) : string
Compresses and seals a frame at the dense setting.
| string | $plain | The decoded frame. |
| string | $hash | Its content address, used as the associated data exactly as the flush path does. |
| string|null | $parent | The frame this one is a delta of, or NULL when it stands alone. A delta has to be re-encoded against the same parent it was coded against: the index goes on calling it a delta, so a frame recompressed against the realm dictionary instead would be decoded with the parent's bytes as its dictionary and come back as noise. |
When the codec or the cipher fails, or the parent does not read back.
The encoded frame.
writer() : \Drupal\strata\Codec\CompressionCodecInterface
The codec that writes the dense form.
The bulk writer rather than the per-frame one, because a codec whose per-call overhead rules it out of the request path is exactly what compaction wants.
The codec.