CultCache: a single-file version is the hash of the record's exact stored bytes, and a copied record keeps them
A record read from a store file carries the bytes it was read from (CultPersistedRecord.RawBytes, dropped when any field is assigned) and is written as those bytes, the way a schema catalog entry already is. A version is therefore the SHA-256 of the slice the file holds, whatever legal encoding its writer chose, not of C#'s re-encoding, and a write that copies a record leaves its bytes and so its version alone. The contract says exactly that, so the parity cuts port it literally. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
49a0bcb001
commit
6001ebf932
3 changed files with 35 additions and 12 deletions
|
|
@ -306,6 +306,12 @@ public static class CultDocumentMessagePackSerialization
|
|||
|
||||
private static void WritePersistedRecord(ref MessagePackWriter writer, CultPersistedRecord record)
|
||||
{
|
||||
if (record.RawBytes is { } raw)
|
||||
{
|
||||
writer.WriteRaw(new ReadOnlySpan<byte>(raw));
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteArrayHeader(record.Variant == null ? PersistedRecordFieldCount : VariantRecordFieldCount);
|
||||
writer.Write(record.Key);
|
||||
writer.Write(record.SchemaId);
|
||||
|
|
@ -389,6 +395,7 @@ public static class CultDocumentMessagePackSerialization
|
|||
|
||||
public static CultPersistedRecord ReadPersistedRecord(ref MessagePackReader reader)
|
||||
{
|
||||
var start = reader.Position;
|
||||
var fieldCount = reader.ReadArrayHeader();
|
||||
var record = new CultPersistedRecord();
|
||||
|
||||
|
|
@ -429,6 +436,7 @@ public static class CultDocumentMessagePackSerialization
|
|||
}
|
||||
}
|
||||
|
||||
record.RawBytes = reader.Sequence.Slice(start, reader.Position).ToArray();
|
||||
return record;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,10 +47,14 @@ visible, to the committing flow or to observers, until the store has accepted
|
|||
it; a failed commit changes nothing on disk or in memory.
|
||||
|
||||
A batch may carry conditions: per-record version, or the whole store unchanged
|
||||
since it was last loaded. A record's version is the SHA-256 of its persisted
|
||||
encoding, the array `[key, schemaId, storedAt, payload(, variant)]` a directory
|
||||
page holds; it names the schema id and storedAt as well as the bytes. storedAt is
|
||||
metadata and decides no condition. Conditions are evaluated
|
||||
since it was last loaded. A record's version is the SHA-256 of the exact bytes the
|
||||
record is stored as: in a single file the record's own slice of the file, the array
|
||||
`[key, schemaId, storedAt, payload(, variant)]` exactly as the file holds it, in
|
||||
whatever legal MessagePack encoding its writer chose (never a re-encoding of the
|
||||
decoded record); in a directory store the page the manifest indexes. A record being
|
||||
written has the version of the bytes it is written as. It names the schema id and
|
||||
storedAt as well as the payload. storedAt is metadata and decides no condition.
|
||||
Conditions are evaluated
|
||||
under the store's exclusive lock against what is durably on disk. A failed
|
||||
condition is a lost race, not an error: the commit reports it and writes
|
||||
nothing.
|
||||
|
|
@ -59,9 +63,9 @@ nothing.
|
|||
the store's exclusive lock the writer reads what is durably in the store now,
|
||||
applies only what it staged (writes and removals) and, for a commit, its batch,
|
||||
and replaces the store with the result. Every other record, and every catalog
|
||||
entry the result references, is copied forward as stored: key, schema id,
|
||||
storedAt, payload and variant, and a catalog entry's exact bytes, unknown
|
||||
fields included. No type interprets a copied record: a write may hash another
|
||||
entry the result references, is copied forward as stored: a record's exact bytes
|
||||
and a catalog entry's exact bytes, unknown fields included, so a copied record
|
||||
keeps its version. No type interprets a copied record: a write may hash another
|
||||
record's bytes to compare versions, and reads its header (key, schema id, variant
|
||||
slot), but never decodes its payload.
|
||||
A record written over one the store holds gets a storedAt later than the one it
|
||||
|
|
|
|||
|
|
@ -134,13 +134,24 @@ namespace GameCult.Caching
|
|||
|
||||
public sealed class CultPersistedRecord
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string SchemaId { get; set; } = string.Empty;
|
||||
public string StoredAt { get; set; } = string.Empty;
|
||||
public byte[] Payload { get; set; } = Array.Empty<byte>();
|
||||
private string _key = string.Empty;
|
||||
private string _schemaId = string.Empty;
|
||||
private string _storedAt = string.Empty;
|
||||
private byte[] _payload = Array.Empty<byte>();
|
||||
private CultVariantDelta? _variant;
|
||||
|
||||
public string Key { get => _key; set { _key = value; RawBytes = null; } }
|
||||
public string SchemaId { get => _schemaId; set { _schemaId = value; RawBytes = null; } }
|
||||
public string StoredAt { get => _storedAt; set { _storedAt = value; RawBytes = null; } }
|
||||
public byte[] Payload { get => _payload; set { _payload = value; RawBytes = null; } }
|
||||
|
||||
// Non-null for a variant record (slot 4): its base and overrides. Its payload is empty.
|
||||
public CultVariantDelta? Variant { get; set; }
|
||||
public CultVariantDelta? Variant { get => _variant; set { _variant = value; RawBytes = null; } }
|
||||
|
||||
// The exact bytes this record was read from, as a store file held them. A record is written as the bytes it was read from
|
||||
// while no field has been assigned since, so a copied record stays byte-identical and its version (the SHA-256 of its
|
||||
// stored bytes) is the one the file's bytes name, whatever encoding their writer chose. Assigning any field drops them.
|
||||
internal byte[]? RawBytes { get; set; }
|
||||
}
|
||||
|
||||
// One record a write puts in a single-file store: encoded, with the catalog entry that publishes the id it is stored under and
|
||||
|
|
|
|||
Loading…
Reference in a new issue