Blobs
A blob is an immutable file of a Store, named by the SHA-256 of its content
(blobs/<2 hex>/<64 hex>). A catalog definition references it by a BlobRef (facade
graphersal::blob): the hash, the content's length and its CRC-32C. Blobs hold large binary
content beside the graph (the files of embedding models, later user BLOB values) without
passing it through the write-ahead log. The byte-level rules are the
format specification, section 4.6.
use graphersal::blob::{BlobHashAlgo, BlobRef};
let blob = BlobRef::new(BlobHashAlgo::Sha256, &[0xab; 32], 1024, 7).unwrap();
assert_eq!(blob.relative_path(), format!("ab/{}", "ab".repeat(32)));
Writing and reading
Store::write_blob(reader) streams the content into a temporary file while it computes the
SHA-256, the CRC-32C and the length (constant memory), syncs it and renames it to its name. The
same content written twice is stored once. The returned PendingBlob protects the new blob
from every collection until you drop it: put its BlobRef into a catalog definition's payload,
commit, then drop the handle. A blob that no commit references is an orphan the next prune
removes.
Store::read_blob(&blob) returns the content: the length is checked first, the process memory
ceiling before anything is allocated (operation "blob read"), the CRC-32C after reading. The
SHA-256 is computed only when content is written or arrives from outside and by verify, never
on a read.
In this version a BlobRef lives only in catalog payloads: a graph refuses it as an element
property value (GraphError::InvalidPropertyValue, kind "blob reference").
How long a blob stays
A blob stays exactly as long as some retained history references it: a snapshot's blob table, a
commit in the WAL (the derived ledger file lists every commit's reference changes), or the
history an attic entry keeps. prune, removing an attic entry and a rollback with delete
remove every other blob (PruneReport::blobs_removed, the journal record blob.collected).
Store::info() lists the blobs by name and length (StoreInfo::blobs: count, bytes, how many no
history references); graphersal store info prints a blobs line when there are any.
Copies
Every copy carries the blobs its history references, checked by length and CRC-32C on both
sides: full and incremental backups, ZIP backups (entries blobs/<2 hex>/<hash>, checked by
their hash again on restore), backups from memory, forks, attic forks, repairs, conversions and
packed snapshots (.gsnap, as rows of segment kind 4). BlobCopy::Exclude (ModelCopy is its
0.4 name) leaves the models and blobs out of a ZIP backup or a packed snapshot meant to be
shared; the copy's catalog still references them, so the receiver has them missing.
Damage
A missing or damaged blob is not damage of the store: the damage policy does not apply,
nothing turns read-only, and only what needs the blob's bytes fails
(StoreFailure::BlobUnavailable or BlobDamaged). The system journal gets one blob.damaged
error per blob and failure, with a recommendation; graphersal store verify lists every missing
or damaged blob. Any copy with the same hash is the same bytes, so two tools bring a store back
to a healthy state:
-
Restore the blobs from any copy that still has them: a backup directory (full or with its increments), a ZIP backup, a packed snapshot (
.gsnap) or another store (a fork, a repaired copy, a.gstore). The store must be closed (the writer lock):graphersal store restore-blobs <dir> --from <backup_dir|file.zip|file.gsnap|other_store> [--json]In Rust
Store::restore_blobs_from(dir, BlobSource::store(..) | BlobSource::zip(..) | BlobSource::packed(..)). Every blob the store's retained history references is checked (length, CRC-32C, hash); each missing or damaged one is looked up in the source by its algorithm and hash (a blob of an unknown algorithm by its name), streamed intoblobs/tmp-*, checked the same way, synced and renamed into place. A healthy blob is never touched. The report (RestoreBlobsReport) lists what was restored, what the source does not hold and what it holds damaged too; the command exits 1 unless every referenced blob is intact afterwards. Each restored blob is ablob.repairedrecord (how=restored,source). -
Leave them out when no copy exists:
graphersal store repair <dir> --to <new_dir> --drop-damaged-blobs(RepairOptions::with_drop_damaged_blobs) builds a repaired store without every catalog definition that references a missing or damaged blob (and without the blob):RepairReport::blobs_droppedanddefinitions_dropped, oneblob.droppedrecord per blob in the new store's journal. It is refused, before anything is written, while the schema needs such a definition (a model a vector property'sembeddernames); the error names the properties: restore the blob first, or change the schema. Without the flag a repair carries the definitions and reports their blobs as not carried.
By hand, copying the file blobs/<2 hex>/<hash> from another copy works too, and writing the
content again (Store::write_blob) replaces a damaged blob.