Documentation
¶
Index ¶
- Constants
- Variables
- func BloomHash(name []byte) uint64
- func BuildOffsetNotify(nodeID pb.NodeID, offset Tip, eff *pb.Effect, data []byte, ...) *pb.OffsetNotify
- func Compress(b []byte) []byte
- func CompressEffectValues(eff *pb.Effect)
- func ComputeForkChoiceHash(nodeID proto.NodeID, hlc *timestamppb.Timestamp) []byte
- func Decompress(b []byte) ([]byte, error)
- func ForkChoiceLess(a, b []byte) bool
- func ForwardingEnabled() bool
- func InflatedSizeDelta(eff *pb.Effect) uint64
- func MarshalEffect(eff *pb.Effect) ([]byte, error)
- func ReduceBranch(effects []*pb.Effect) *pb.ReducedEffect
- func ReduceChain(seed *pb.ReducedEffect, effects []*pb.Effect) *pb.ReducedEffect
- func UnmarshalEffect(data []byte, eff *pb.Effect) error
- type Bloom
- type Broadcaster
- type BunnyStorage
- type CloudEffect
- type CloudReader
- type Context
- func (c *Context) Abort()
- func (c *Context) BeginTx()
- func (c *Context) CheckWatches() bool
- func (c *Context) ClearWatches()
- func (c *Context) Emit(eff *pb.Effect, snapshotTips ...[]Tip) error
- func (c *Context) Flush() error
- func (c *Context) GetSnapshot(key string) (*pb.ReducedEffect, []Tip, error)
- func (c *Context) PendingKeys() []string
- func (c *Context) RestoreSavepoint(sp *ContextSavepoint)
- func (c *Context) SetTraceCtx(ctx context.Context)
- func (c *Context) TakeSavepoint() *ContextSavepoint
- func (c *Context) TraceCtx() context.Context
- func (c *Context) Watch(key string) error
- type ContextSavepoint
- type CuckooChain
- type CuckooFilter
- type Encryptor
- type Engine
- func (e *Engine) ArenaBytes() int64
- func (e *Engine) AverageK() float64
- func (e *Engine) CachePeerFilter(peer pb.NodeID, data []byte, version uint64)
- func (e *Engine) CheckSerializationLeader(key string) *pb.NodeID
- func (e *Engine) Close() error
- func (e *Engine) DropPeer(peer pb.NodeID)
- func (e *Engine) EffectCache() *VertexPool
- func (e *Engine) EvictStats() keytrie.EvictStats
- func (e *Engine) FlushIndex()
- func (e *Engine) Forward(commandName string, args [][]byte, keys []string, username string) []byte
- func (e *Engine) ForwardExec(commands []ForwardCommand, watchedKeys []string, username string) []byte
- func (e *Engine) GetLock(key string) *sync.Mutex
- func (e *Engine) GetSnapshot(key string) (*pb.ReducedEffect, []Tip, int, error)
- func (e *Engine) HandleNack(nack *pb.NackNotify) error
- func (e *Engine) HandleRemote(notify *pb.OffsetNotify) ([]*pb.NackNotify, error)
- func (e *Engine) InstallCloudTips(key string, tips []Tip, sidecar []CloudEffect, missing []Tip) (pending []Tip, err error)
- func (e *Engine) KeyCount() int64
- func (e *Engine) MatchKeys(pattern string) []string
- func (e *Engine) MemoryTarget() int64
- func (e *Engine) NewContext() *Context
- func (e *Engine) NewReadOnlyContext() *Context
- func (e *Engine) NodeID() pb.NodeID
- func (e *Engine) NotePeerConnected(peer pb.NodeID)
- func (e *Engine) OwnKeyFilterSnapshot() ([]byte, uint64)
- func (e *Engine) PeerSubscribers(key string) []pb.NodeID
- func (e *Engine) PinKey(key string) bool
- func (e *Engine) ReconvergeAllKeys()
- func (e *Engine) RecordSerializationActivity(key string)
- func (e *Engine) ReleaseQueueDepth() int
- func (e *Engine) ScanKeys(after string, pattern string, fn func(key string) bool)
- func (e *Engine) SetBroadcaster(b Broadcaster)
- func (e *Engine) SetCloudReader(r CloudReader)
- func (e *Engine) SetOnLocalEffect(hook func(offset Tip, eff *pb.Effect))
- func (e *Engine) SetRTTProvider(p PeerRTTProvider)
- func (e *Engine) StartAntiEntropy(interval time.Duration)
- func (e *Engine) UnpinKey(key string) bool
- func (e *Engine) UpdateSafetyRules(defaultMode SafetyMode, rules []KeyRangeRule)
- func (e *Engine) VertexCount() int
- type EngineConfig
- type FetchHint
- type ForwardCommand
- type HorizonSet
- func (h *HorizonSet) Abort(txnID string)
- func (h *HorizonSet) Add(txnID string, bindOffset Tip, bind *pb.TransactionalBindEffect)
- func (h *HorizonSet) Empty() bool
- func (h *HorizonSet) IsInvisible(txnID string) bool
- func (h *HorizonSet) MakeVisible(txnID string)
- func (h *HorizonSet) ScheduleMakeVisible(txnID string, wait time.Duration)
- func (h *HorizonSet) WaitForClear(txnID string) *HorizonWait
- type HorizonWait
- type KeyRangeRule
- type MemoryStorage
- type ObjectStorage
- type PeerRTTProvider
- type SafetyMode
- type Tip
- type VertexPool
- func (p *VertexPool) Bytes() int64
- func (p *VertexPool) ColdEvictions() uint64
- func (p *VertexPool) Decref(tip Tip)
- func (p *VertexPool) EntryCount() int
- func (p *VertexPool) Get(tip Tip) (*pb.Effect, bool)
- func (p *VertexPool) Incref(tip Tip) bool
- func (p *VertexPool) Put(tip Tip, eff *pb.Effect)
- func (p *VertexPool) PutSized(tip Tip, eff *pb.Effect, protoLen int)
- func (p *VertexPool) PutSizedCache(tip Tip, eff *pb.Effect, protoLen int)
- func (p *VertexPool) Reclaimed() uint64
- func (p *VertexPool) Stats() (hits, misses, reclaimed uint64)
Constants ¶
const ( // BloomMinBytes is the smallest bitmap: 4KB ≈ 3k keys at target density. BloomMinBytes = 4096 // BloomTargetBitsPerKey sizes a build: m = next power of two ≥ 10·n bits // lands the fill between ~0.3 and ~0.5 for n keys. BloomTargetBitsPerKey = 10 // BloomResizeFill is the set-bit fraction past which the holder should // rebuild larger — above it the FP rate degrades past ~2%. BloomResizeFill = 0.55 )
const DefaultSerializationThreshold = 3
DefaultSerializationThreshold is the number of consecutive aborts on a key before escalating to serialized coordination.
const FlushKey = "__swytch:flush"
FlushKey is the special key used to signal a full index wipe (FLUSHDB/FLUSHALL). Lives under the __swytch: namespace so it's pinned in the cache and can't collide with a user-chosen key.
Variables ¶
var ErrAuthorityDropped = errors.New("authority dropped: not subscribed to this key")
ErrAuthorityDropped signals that HandleRemote rejected the inbound effect because this node has no authority over the key. The transport layer interprets this as "do not respond" — neither ACK nor NACK is sent — so the sender's tracked replication times out rather than counting us as a successful first-ACK replica. The sender's other peers (those with authority) still get the chance to accept the write.
var ErrBootstrapIncomplete = errors.New("bootstrap incomplete: some peers unreachable")
ErrBootstrapIncomplete is returned by ensureSubscribed when the bootstrap could not fetch the full causal chain because some peers are unreachable. A background retry continues until the chain is complete.
var ErrCDNBlobMissing = errors.New("cdn blob missing")
ErrCDNBlobMissing marks a CDN fetch that answered 404: the blob is provably absent from cloud storage, as opposed to the cloud being unreachable. Cloud tip markers are candidates, never an authoritative frontier — a node builds its own truth by walking the DAG it can actually read — so a hole under this sentinel is not this engine's to repair. classifyCloudTips reports the holed tip as pending; the cluster layer's reconcile loop retries it, and Cloud's own billing sweep is what eventually heals or prunes the marker.
ErrCloudUnavailable marks a read that could not be answered because the Cloud consult failed or returned a frontier we could not fully fetch. It must surface to the client as an error, never as a miss: Cloud provably holds data for the key, so answering "no such key" would be indistinguishable from data loss.
var ErrCuckooFull = errors.New("cuckoo filter is full")
ErrCuckooFull is returned by Add when the filter could not place a fingerprint after the maximum number of evictions. The own-filter recovers by rebuilding at a larger capacity from the index, which is the authoritative key set; peer filters are never Added to (they arrive pre-built) so they never see this.
var ErrRegionPartitioned = errors.New("region partitioned: not all same-region peers are reachable")
ErrRegionPartitioned is returned by Flush in SafeMode when not all same-region peers are reachable.
var ErrTxnAborted = errors.New("transaction aborted")
ErrTxnAborted is returned by Flush when a transaction loses FWW or encounters a real conflict that cannot be resolved.
Functions ¶
func BloomHash ¶
BloomHash is the one hash of a name every position derives from — the only thing a caller needs to retain per name to rebuild a filter.
func BuildOffsetNotify ¶
func BuildOffsetNotify(nodeID pb.NodeID, offset Tip, eff *pb.Effect, data []byte, traceCtx context.Context) *pb.OffsetNotify
BuildOffsetNotify constructs a single-effect notification. EffectData is wire format: [4-byte LE keyLen][key][protoData]. traceCtx is optional; when non-nil, OTel trace context is injected into the notification.
func CompressEffectValues ¶
CompressEffectValues applies value compression to an effect about to be emitted: the data arm directly, and a snapshot's materialized state via copy — snapshot state may share element pointers with cached reduced state, which is immutable by contract, so compression swaps in fresh structs rather than writing through shared ones.
func ComputeForkChoiceHash ¶
func ComputeForkChoiceHash(nodeID proto.NodeID, hlc *timestamppb.Timestamp) []byte
ComputeForkChoiceHash computes SHA-256(nodeID_LE8 || hlc_LE8). The result is a deterministic 32-byte hash used for fork-choice tiebreaking. Lower hash wins, eliminating systematic advantage from raw HLC comparison.
func Decompress ¶
Decompress reverses Compress. Decoded size is bounded (see decoder init) to guard against decompression bombs from a misbehaving peer.
func ForkChoiceLess ¶
ForkChoiceLess returns true if hash a is lexicographically less than hash b. Lower hash wins the fork-choice election.
func ForwardingEnabled ¶
func ForwardingEnabled() bool
ForwardingEnabled reports whether adaptive-serialization forwarding is compiled in. The redis handler checks it before extracting command keys that would only feed Forward/ForwardExec's early return — as a constant, the whole forwarding block dead-code-eliminates while disabled.
func InflatedSizeDelta ¶
InflatedSizeDelta is the byte count value compression hid from this effect's marshal: Σ (raw_len − len(stored)) over every compressed value. Billing adds it to the marshal length so raw_size keeps meaning "the customer's bytes" no matter how well their data compresses.
func MarshalEffect ¶
MarshalEffect serializes an Effect to its wire bytes, hex-encoding any map<string, X> keys inside an embedded ReducedEffect (Snapshot state). Proto3 requires string map keys to be valid UTF-8, but Redis-style collections (hashes, sets, zsets, streams) accept arbitrary byte sequences as element IDs — and the cluster membership encodes a uint64 as raw little-endian bytes. Without encoding, proto.Marshal silently rejects snapshots whose state has any binary-keyed elements.
The encoding is hex (always-on, no discriminator). The reverse step is in UnmarshalEffect. The original *pb.Effect is not mutated; if any sanitization is required the snapshot state is cloned first.
func ReduceBranch ¶
func ReduceBranch(effects []*pb.Effect) *pb.ReducedEffect
ReduceBranch reduces a linear chain of effects (oldest-first) into a single ReducedEffect.
func ReduceChain ¶
func ReduceChain(seed *pb.ReducedEffect, effects []*pb.Effect) *pb.ReducedEffect
ReduceChain reduces effects sequentially on top of a seed ReducedEffect. If seed is nil, behaves identically to ReduceBranch. This is used at DAG merge points: the seed is the merged result of concurrent dep subtrees, and effects are the linear chain above. The seed is never mutated; a clone is made before any modifications.
Virtual partitions: effects carry an optional `virtual` partition key that addresses nested state WITHIN the key (empty = root partition). Flat keys (no virtual effects, no seed partitions) take the fast path and reduce byte-identically to the pre-nesting engine. Otherwise the chain is split by virtual and each partition reduces independently via reduceFlat, with a root key-level DEL wiping all partitions (it deletes the whole key).
Types ¶
type Bloom ¶
type Bloom struct {
// contains filtered or unexported fields
}
Bloom is an approximate set of key names with no false negatives: Has returns true for every name ever Set, and false-positives at a rate set by the fill fraction (FP ≈ fill^bloomK). It backs the cloud key-name filter on both sides of the wire — the dataplane builds one per cluster and ships it as a frame; the node queries it to gate read-miss cloud consults — so the hashing is fixed: a filter built on one process and queried on another must agree. Unlike CuckooChain it cannot grow; the holder rebuilds at a larger size when Fill crosses BloomResizeFill.
Not internally synchronized: a holder either treats a built filter as immutable (build fully, then publish by pointer swap — the frame path) or guards Set/Has under its own lock (CloudSync.filterMu).
func NewBloom ¶
NewBloom returns a filter over sizeBytes of bitmap, rounded up to a power of two no smaller than BloomMinBytes.
func NewBloomForCount ¶
NewBloomForCount returns a filter sized for count names at the target build density.
func ParseBloomFrame ¶
ParseBloomFrame decodes a Frame, copying the bitmap out of the (possibly transport-owned) buffer. Anything malformed — wrong version, undersized or non-power-of-two body — is rejected; the input crosses process boundaries.
type Broadcaster ¶
type Broadcaster interface {
Broadcast(notify *pb.OffsetNotify)
BroadcastWithData(notify *pb.OffsetNotify, effectData []byte)
Replicate(notify *pb.OffsetNotify, wireData []byte) error
// ReplicateTo sends to a specific peer and waits for ACK/NACK.
// Returns NackNotify slice on conflict, nil on ACK.
ReplicateTo(notify *pb.OffsetNotify, wireData []byte, targetNodeID pb.NodeID) ([]*pb.NackNotify, error)
// ReplicateMarshalled is ReplicateTo with a pre-marshalled notify body, so
// a fan-out (the bind to every subscriber) marshals the body once instead
// of once per peer. notify is passed for inspection; the body is the wire
// payload.
ReplicateMarshalled(notify *pb.OffsetNotify, notifyBody []byte, targetNodeID pb.NodeID) ([]*pb.NackNotify, error)
// SendNack sends an enriched NACK to the originator.
SendNack(nack *pb.NackNotify, targetNodeID pb.NodeID)
// FetchFromAny fetches effect bytes from peers or the cloud CDN. hint
// orders the two sources; the second is a fallback tried only after the
// first actually failed — never raced.
FetchFromAny(ref *pb.EffectRef, hint FetchHint) ([]byte, error)
Fetch(ref *pb.EffectRef) ([]byte, error)
PeerIDs() []pb.NodeID
// AllRegionPeersReachable returns true if every same-region peer is
// alive and has a verified symmetric path. Used by SafeMode to gate
// writes: a key must not be written unless all region peers are reachable.
AllRegionPeersReachable() bool
// InMajorityPartition returns true if this node can reach a strict
// majority of same-region nodes (including itself). Used by SafeMode
// to block transactions when in a minority partition.
InMajorityPartition() bool
// ForwardTransaction sends a transaction to a specific peer for execution
// (adaptive serialization §5). Returns the leader's response.
ForwardTransaction(ctx context.Context, targetNodeID pb.NodeID, tx *pb.ForwardedTransaction) (*pb.ForwardedResponse, error)
}
Broadcaster sends effect notifications to cluster peers. Nil means standalone mode.
type BunnyStorage ¶
type BunnyStorage struct {
// contains filtered or unexported fields
}
BunnyStorage implements ObjectStorage using Bunny CDN's storage API.
func NewBunnyStorage ¶
func NewBunnyStorage(uploadEndpoint, cdnEndpoint, accessKey string) *BunnyStorage
NewBunnyStorage creates a BunnyStorage instance.
type CloudEffect ¶
CloudEffect is one closure effect delivered inline by a GetTips response: the decrypted inner effect plus its marshaled proto length for cache accounting.
type CloudReader ¶
type CloudReader interface {
// MayHold reports whether Cloud may hold key, answered from the pushed
// key-name filter — free, no RPC. False is the filter's definite no and
// lets a read free-miss without subscribing or consulting; true routes
// the read through the subscribe + consult path, whose per-leaf
// cloudConsulted marker caps the WAN round-trips.
MayHold(key string) bool
// CloudTips returns the candidate tip frontier Cloud's index holds for
// key, or nil (with nil error) if Cloud holds nothing for it. These are
// candidates, never authoritative: Cloud's tips directory is an index
// that can lag or hole, and the engine's own DAG walk is what decides
// what is actually installable. sidecar is the closure — every effect
// reachable from those tips down to the LCA snapshot — delivered inline
// by GetTips and installed into the effect cache before the tip walk, so
// the walk runs locally instead of one WAN fetch per dep. The sidecar may
// be partial (capped, or missing a blob the cloud is still fetching
// back) — the walk stays the authority and pulls anything missing on
// demand via FetchFromAny. missing names refs GetTips' own closure walk
// found reachable but does not hold the blob for — a fact declared by
// the storage side, letting the engine skip a redundant CDN round trip to
// re-learn the same hole; it is a hint, not a complete inventory, so the
// engine's walk still treats anything not listed here as normal. Content-
// blind on the wire: the implementation maps key to its Cloud PRF image
// and calls GetTips.
CloudTips(ctx context.Context, key string) (tips []Tip, sidecar []CloudEffect, missing []Tip, err error)
// MarkPending reports that key's Cloud frontier has a durable hole this
// node could not walk past — see effects.ErrCDNBlobMissing. Cloud's own
// reconcile loop owns retrying and eventually healing or pruning the
// affected markers; this is not a request the engine expects synchronous
// action on.
MarkPending(key string)
}
CloudReader is the tiered-storage backstop: it reports the tip frontier that durable Cloud storage holds for a key. A key evicted from every live peer (e.g. its writer departed) may still live on Cloud, so a read that finds nothing cluster-wide asks Cloud for the frontier, installs it, and lets the cluster take over. Nil (unset) means no Cloud is configured and the read free-misses as before.
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context tracks per-key state for dep chaining (Emit) and deferred index update + broadcast (Flush). One Context per command invocation.
func (*Context) Abort ¶
func (c *Context) Abort()
Abort discards pending index updates and broadcasts. Effects already written to the log remain durable but invisible (index not updated).
func (*Context) BeginTx ¶
func (c *Context) BeginTx()
BeginTx marks subsequent effects as transactional. This is called by read-modify-write commands (INCR, LPUSH, etc.) for atomicity AND by handleExec for MULTI/EXEC. Watch processing is NOT done here — use CheckWatches after BeginTx for EXEC.
func (*Context) CheckWatches ¶
CheckWatches validates WATCH observations and emits transactional NOOPs for the Bind's read set. Must be called AFTER BeginTx (so NOOPs get IsTransactional=true). Only called from handleExec.
Returns false if any watched key was modified — the caller should abort the transaction and return a null array to the client.
func (*Context) ClearWatches ¶
func (c *Context) ClearWatches()
ClearWatches removes all watched keys. Called from UNWATCH, DISCARD, and EXEC's pre-BeginTx abort paths — all cases where the upcoming transaction is being discarded. Also drops the lazily-generated txnID so the next WATCH/MULTI starts fresh; if we're already inside MULTI/EXEC (c.inTx) the id is load-bearing for in-flight effects and must be preserved.
func (*Context) Emit ¶
Emit writes a single effect to the log. The context tracks per-key state so that consecutive effects on the same key form a dep chain. The index is NOT updated until Flush.
Emit takes ownership of eff: it fills causality fields in place and caches the message itself, so the caller must not retain, reuse, or mutate eff (or its submessages) after the call. Byte-slice fields may alias pooled buffers (e.g. RESP parser args) — Emit copies those before caching.
For read-modify-write commands, pass the tip offsets returned by GetSnapshot as snapshotTips so that the first effect depends on the tips the handler actually read, not whatever the index contains now. Pure writes (SET, LPUSH) omit snapshotTips and Emit reads the index.
func (*Context) Flush ¶
Flush updates the index for all touched keys and broadcasts all notifications per key for durability, then resets the context for reuse.
func (*Context) GetSnapshot ¶
GetSnapshot returns the current materialized state of a key, including any unflushed effects from this context. Within MULTI/EXEC, earlier commands' effects are in the log but not yet in the index; this method reconstructs from the log so commands within the same transaction can see each other's writes. The returned ReducedEffect is immutable and may be shared by later reads.
When a txSnapshot is active (MULTI/EXEC with SSI), reads for keys not yet in the context use the snapshot instead of the live index, and a NOOP is emitted to record the read in the causal structure.
func (*Context) PendingKeys ¶
PendingKeys returns the names of all keys with pending effects in this Context. Callers use this to acquire per-key locks before calling Flush — Flush's fork-choice critical section races with any other Flush on the same key, and the handler layer (redis handler, sql handler) is where that serialisation belongs.
func (*Context) RestoreSavepoint ¶
func (c *Context) RestoreSavepoint(sp *ContextSavepoint)
RestoreSavepoint replaces the Context's pending-key state with the snapshot. Any Emit issued after TakeSavepoint is discarded from the Context; its log offsets remain in the engine's log but never reach the index (identical to what happens to all effects on Abort).
func (*Context) SetTraceCtx ¶
SetTraceCtx sets the OTel trace context for this command.
func (*Context) TakeSavepoint ¶
func (c *Context) TakeSavepoint() *ContextSavepoint
TakeSavepoint captures a deep-enough copy of the Context's pending key state that RestoreSavepoint can revert every Emit issued between Take and Restore. Cheap: each contextKey is a small struct with slice fields that we clone by reference (the underlying values — notifies, elementIDs — are immutable once added).
func (*Context) TraceCtx ¶
TraceCtx returns the OTel trace context, or context.Background() if none set.
func (*Context) Watch ¶
Watch records a key for optimistic locking by emitting a NoopEffect immediately and flushing it. This records the observation in the causal log so all nodes can verify whether the key was modified between WATCH and EXEC. The NOOP offset and post-flush TipSet pointer are stored for comparison at BeginTx time.
The noop is tx-marked with the upcoming transaction's id (generated lazily here). This is what makes write-skew detection work across nodes: a competing write on a watched key becomes a structural fork sibling of the watching tx's bind, surfaced by reconstruct's pairwise fork-choice on the key. Without the TxnId, the noop is an anonymous committed effect that other writers can chain off, producing false sequential relationships (the run-25890153673 G1a).
Errors fail the WATCH: a read that can't be answered (e.g. the Cloud consult failed for an evicted key) must not silently record hadData=false — the EXEC verdict would then rest on a fabricated observation.
type ContextSavepoint ¶
type ContextSavepoint struct {
// contains filtered or unexported fields
}
ContextSavepoint is an opaque snapshot of a Context's pending per-key state. Restore it (via Context.RestoreSavepoint) to discard every Emit made after the snapshot was taken; already- written log offsets become orphans (never indexed), same as Abort. Used by higher-level SQL SAVEPOINT semantics.
Savepoints do NOT snapshot the engine's committed index — the engine's state is unchanged until Flush, so reverting the Context's pending keys is sufficient to roll back.
type CuckooChain ¶
type CuckooChain struct {
// contains filtered or unexported fields
}
CuckooChain is a queryable approximate set with NO false negatives, built from a sequence of CuckooFilters split into:
- active: the single writable segment, always one we allocated and kept below cuckooChainChunk distinct keys, so Add on it never fails.
- sealed: query-only segments — filled-up former active segments and any spliced peer segments. Never written to, so a near-full or foreign segment can't be corrupted by an Add.
It never shrinks; a deleted key lingers as a safe false-positive (a needless subscribe, never a wrong miss) until the chain is rebuilt.
func (*CuckooChain) Add ¶
func (cc *CuckooChain) Add(key string) bool
Add inserts key into the active segment, rotating first if that segment is at capacity. Returns true iff a new fingerprint was actually stored (false for an idempotent re-add), so callers can skip version bumps when the set did not change.
func (*CuckooChain) MarshalBinary ¶
func (cc *CuckooChain) MarshalBinary() ([]byte, error)
func (*CuckooChain) MaybeContains ¶
func (cc *CuckooChain) MaybeContains(key string) bool
MaybeContains reports whether key may be in the set; false is authoritative.
func (*CuckooChain) UnmarshalBinary ¶
func (cc *CuckooChain) UnmarshalBinary(data []byte) error
UnmarshalBinary decodes a chain as query-only segments (active stays nil) — a decoded peer chain is never written to.
type CuckooFilter ¶
type CuckooFilter struct {
// contains filtered or unexported fields
}
CuckooFilter is an approximate set of keys with no false negatives: MaybeContains returns true for every key ever successfully Added, and false-positives at a bounded rate. It backs the per-node cluster key filters that let a read-only handler answer a miss without subscribing.
The filter is serialized and shipped between nodes (on NACKs), so the hashing and layout are fixed: a filter built on one node and queried on another must agree. Item placement (which candidate bucket a fingerprint landed in) does not affect queries — MaybeContains always probes both candidate buckets — so Add may relocate fingerprints freely.
func NewCuckooFilter ¶
func NewCuckooFilter(capacity int) *CuckooFilter
NewCuckooFilter returns a filter sized to hold at least capacity keys before relocations begin to fail. numBuckets is rounded up to a power of two so the index masks are cheap.
func (*CuckooFilter) Add ¶
func (c *CuckooFilter) Add(key string) error
Add inserts key. It is idempotent at the fingerprint level: re-adding a key whose fingerprint already sits in a candidate bucket is a no-op. Returns ErrCuckooFull if no slot could be freed.
func (*CuckooFilter) Count ¶
func (c *CuckooFilter) Count() uint64
Count returns the number of live fingerprints.
func (*CuckooFilter) LoadFactor ¶
func (c *CuckooFilter) LoadFactor() float64
LoadFactor is the fraction of slots occupied.
func (*CuckooFilter) MarshalBinary ¶
func (c *CuckooFilter) MarshalBinary() ([]byte, error)
MarshalBinary serializes the filter: numBuckets (8 bytes, big-endian) followed by every slot as a big-endian uint16.
func (*CuckooFilter) MaybeContains ¶
func (c *CuckooFilter) MaybeContains(key string) bool
MaybeContains reports whether key may be present. False positives are possible; false negatives are not (for keys successfully Added).
func (*CuckooFilter) UnmarshalBinary ¶
func (c *CuckooFilter) UnmarshalBinary(data []byte) error
UnmarshalBinary restores a filter produced by MarshalBinary. The decoded filter is query-only in practice (peer filters are never Added to), but Add still works should that change.
type Encryptor ¶
type Encryptor struct {
// contains filtered or unexported fields
}
Encryptor seals payloads with XChaCha20-Poly1305 under a key derived from shared input keying material, with zstd compression inside the seal.
Encryption is symmetric on purpose: every sealing party already holds the connection secret the key derives from, so asymmetric sealing would add per-blob key-encapsulation overhead without separating any capabilities. Read-only consumers are carved out by the secret-derivation tree instead — hand them the encryption key and the key-name key but not the master secret, and they can decrypt blobs yet never derive the auth key a write requires. Sealed blobs are stored durably; changing this format strands them.
func NewEncryptorFromIKM ¶
NewEncryptorFromIKM derives an Encryptor's key deterministically from input keying material. Every holder of the same IKM arrives at the same key independently — this is how a cluster shares one cloud-payload key derived from the connection secret, with no key exchange and nothing for the cloud to see.
func (*Encryptor) OpenAndDecompress ¶
OpenAndDecompress reverses SealAndCompress: authenticates and decrypts under the same info domain, then decompresses.
func (*Encryptor) SealAndCompress ¶
SealAndCompress compresses with zstd, then seals with XChaCha20-Poly1305 under a random nonce, returning nonce ‖ ciphertext. The info parameter is bound as additional data for domain separation (e.g. "effect" vs "tip-recovery"): a blob sealed under one domain does not open under another.
type Engine ¶
type Engine struct {
// Notification callbacks — fired after effects are durable
OnKeyDataAdded func(key string) // wake oldest waiter (data inserted)
OnKeyDeleted func(key string) // wake all waiters (key removed)
OnFlushAll func() // wake all waiters across all keys
OnLocalEffect func(offset Tip, eff *pb.Effect)
// Ephemeral pub/sub callbacks — fired from HandleRemote on
// receive of wire-only effects that are never stored or indexed.
// OnPubSubMessage delivers an inbound PUBLISH to local subscribers.
OnPubSubMessage func(channel, payload []byte)
// OnEphemeralSubscribe records / removes a remote peer's interest
// in a routing key. unsubscribe=false means register, true means
// drop. The routing key is the raw Effect.Key bytes — encoding is
// the cluster router's concern, not the engine's.
OnEphemeralSubscribe func(subscriberNodeID uint64, routingKey []byte, unsubscribe bool)
// contains filtered or unexported fields
}
Engine is the central coordinator for the causal effect log. Lock-free: the log uses CAS, the index manages its own concurrency, and safety config is swapped atomically.
func NewEngine ¶
func NewEngine(cfg EngineConfig) *Engine
NewEngine creates a new Engine from the given configuration.
func NewTestEngine ¶
func NewTestEngine() *Engine
NewTestEngine creates a minimal Engine for use in tests outside this package.
func (*Engine) ArenaBytes ¶
ArenaBytes returns the critbit index's slot-array footprint (the trie skeleton), distinct from the vertex pool's effect bytes (EffectCache().Bytes()). Exposed for telemetry so the two memory consumers can be compared directly.
func (*Engine) AverageK ¶
AverageK exposes the index's current eviction threshold for telemetry. k now lives on the critbit index (the vertex pool has no eviction policy of its own), so heartbeat stats read it from here rather than the effect cache.
func (*Engine) CachePeerFilter ¶
CachePeerFilter splices a peer's bulk filter into our view of that peer, if it's newer than what we've already applied. Delivered by the cluster layer at connection establishment; the arrival releases the peer from the "presumed to hold everything" default.
func (*Engine) CheckSerializationLeader ¶
CheckSerializationLeader returns the serialization leader for a key, or nil if no serialization is active.
func (*Engine) Close ¶
Close performs graceful shutdown of the engine and its background components.
func (*Engine) DropPeer ¶
DropPeer releases per-peer cached state when a peer permanently leaves the cluster. Wired into PeerManager.SetPeerLifecycleHooks on the onRemoved hook (which fires only on genuine membership removal, not transient unreachability). Subscriber sets are NOT scrubbed here: they live in per-key leafState (reclaimed on leaf eviction) and are filtered against current membership at broadcast time, so a departed peer's id simply falls out of collectSubscribers. The key filter, by contrast, is real per-peer cached data that cannot be lazily re-derived, so it must be dropped explicitly.
func (*Engine) EffectCache ¶
func (e *Engine) EffectCache() *VertexPool
EffectCache returns the engine's deserialized effect cache for use by the fetch handler (serves effects from cache when the log is unavailable).
func (*Engine) EvictStats ¶
func (e *Engine) EvictStats() keytrie.EvictStats
EvictStats exposes the index's adaptive-eviction internals for telemetry.
func (*Engine) FlushIndex ¶
func (e *Engine) FlushIndex()
FlushIndex deletes all keys from the index and evicts all cache entries.
func (*Engine) Forward ¶
Forward checks if any key has a serialization leader on another node. If so, forwards the command and returns raw RESP bytes. Returns nil if the command should execute locally.
func (*Engine) ForwardExec ¶
func (e *Engine) ForwardExec(commands []ForwardCommand, watchedKeys []string, username string) []byte
ForwardExec checks if any queued command touches a serialized key. If so, forwards the entire transaction and returns raw RESP bytes. Returns nil for local execution.
func (*Engine) GetSnapshot ¶
GetSnapshot returns the current materialized state of a key and the tip offsets the snapshot was derived from. Callers that perform read-modify-write (SETBIT, INCR, etc.) must pass the returned tips to Emit so the first effect depends on the tips the snapshot was actually computed from, not whatever the index contains at Emit time. The returned ReducedEffect is an immutable snapshot and may be shared by subsequent reads; callers that need to change it must clone it first.
Cache hit returns immediately. On miss, walks the causal DAG from the index tip set and reconstructs via ReduceBranch + canonical merge.
func (*Engine) HandleNack ¶
func (e *Engine) HandleNack(nack *pb.NackNotify) error
HandleNack processes a NACK from a remote peer.
func (*Engine) HandleRemote ¶
func (e *Engine) HandleRemote(notify *pb.OffsetNotify) ([]*pb.NackNotify, error)
HandleRemote processes a remote effect notification: stores the effect in the log, updates the index, and returns NACKs if deps don't match tips.
EffectData may be in wire format [4-byte LE keyLen][key][protoData] or raw proto bytes. Both are handled transparently.
Returns all NACKs generated (one per diverged key) so the caller can send them synchronously as the ReplicateTo response.
func (*Engine) InstallCloudTips ¶
func (e *Engine) InstallCloudTips(key string, tips []Tip, sidecar []CloudEffect, missing []Tip) (pending []Tip, err error)
InstallCloudTips classifies Cloud-provided marker candidates into the subset that is locally walkable (install) and the subset still holed (pending), merges the walkable subset into the index alongside whatever is already there, and returns the holed subset for the caller to track. Besides the read-miss rehydrate it serves the cloud-sync reconcile path: a read served from the outbox during a Cloud outage re-installs the missed Cloud frontier here once Cloud answers again.
It never mints and never consumes local tips: Cloud's tips directory is a candidate index, not authoritative state, so nothing here may supersede local ancestry. A hole is not this engine's to repair — see ErrCDNBlobMissing.
func (*Engine) MatchKeys ¶
MatchKeys returns all keys matching the glob pattern. Uses a point-in-time snapshot for consistency.
func (*Engine) MemoryTarget ¶
MemoryTarget returns the byte budget the governor holds the vertex pool under (0 = unbounded, no --maxmemory configured). The pool is byte-bounded rather than slot-bounded, so this is its capacity for heartbeat telemetry.
func (*Engine) NewContext ¶
NewContext creates a new write context bound to the engine.
func (*Engine) NewReadOnlyContext ¶
NewReadOnlyContext creates a context for read-only, non-transactional commands. It uses the per-node cluster key filters to answer a read-miss without issuing a subscription — a key no peer holds returns nil for free. It must not be used inside MULTI/EXEC (SSI reads must subscribe).
func (*Engine) NotePeerConnected ¶
NotePeerConnected marks peer as a live cluster member whose key filter we may not have yet. Until its bulk filter arrives, the peer is presumed to hold every key (clusterMaybeHasKey answers true), so reads bootstrap for real instead of fabricating misses. Called by the cluster layer when a peer connection is registered.
func (*Engine) OwnKeyFilterSnapshot ¶
OwnKeyFilterSnapshot returns the serialized own filter and its version, re-marshaling only when the filter changed since the last call. The cluster layer sends this to a peer at connection establishment.
func (*Engine) PeerSubscribers ¶
PeerSubscribers returns the current subscriber set for key. The returned slice is a snapshot; callers may iterate without holding any lock. It is unfiltered by membership — flushTx's collectSubscribers applies the current-membership filter at broadcast time.
func (*Engine) PinKey ¶
PinKey adds a dynamic do-not-evict hold on key; UnpinKey releases it. The cloud outbox is the caller: a key with un-acked uploads must stay findable — evicting it frees almost nothing (the outbox pins the effect bytes) while unsubscribing makes the committed data invisible to the whole cluster until the ack lands. Reports whether a live leaf carried the operation.
func (*Engine) ReconvergeAllKeys ¶
func (e *Engine) ReconvergeAllKeys()
ReconvergeAllKeys triggers a debounced background anti-entropy pass to fetch any effects missed during the partition. Multiple calls within the debounce window are coalesced into a single pass. This replaces the previous approach of deleting all subscriptions, which caused a thundering herd of blocking re-bootstraps on the read path and cascading peer timeouts.
func (*Engine) RecordSerializationActivity ¶
RecordSerializationActivity updates the last activity timestamp for a serialized key. Called when the leader processes a forwarded command or performs a local write on a serialized key.
func (*Engine) ReleaseQueueDepth ¶
ReleaseQueueDepth returns the number of cold-evicted keys whose deferred ref-release walk has not yet run. Each pending entry pins its key's cached subdag and resident effect chain, so a persistently deep queue means the governor's reclaim pass is falling behind eviction and live heap will read high for memory eviction can't actually free yet.
func (*Engine) ScanKeys ¶
ScanKeys iterates over keys matching the glob pattern, starting after `after`. Pass empty string for `after` to start from the beginning. Return false from fn to stop iteration.
func (*Engine) SetBroadcaster ¶
func (e *Engine) SetBroadcaster(b Broadcaster)
SetBroadcaster sets the broadcaster for replicating effects to peers. Must be called before any Emit/Flush if cluster mode is desired.
Also lazy-inits the horizon set if not already present: NewEngine gates horizon on cfg.Broadcaster != nil, but the bootstrap path in beacon/runtime.go constructs the engine first (with a nil Broadcaster) and wires the PeerManager as broadcaster afterwards (chicken-and-egg: PeerManager needs engine to build the effect handler). Without this lazy init, horizon stays nil for the life of the engine and bind-arrival visibility isn't deferred — peers see aborting-txn effects before fork-choice has settled, which surfaces as Elle :incompatible-order on cross-node reads.
func (*Engine) SetCloudReader ¶
func (e *Engine) SetCloudReader(r CloudReader)
SetCloudReader installs the Cloud read backstop consulted on a read-miss. Nil leaves the engine free-missing as before (standalone / no cloud).
func (*Engine) SetOnLocalEffect ¶
SetOnLocalEffect replaces the local-mint hook. It waits for callbacks already in flight, which gives lifecycle owners a clean boundary before draining or tearing down the hook's resources.
func (*Engine) SetRTTProvider ¶
func (e *Engine) SetRTTProvider(p PeerRTTProvider)
SetRTTProvider sets the RTT provider for adaptive serialization leader selection.
func (*Engine) StartAntiEntropy ¶
StartAntiEntropy launches a background goroutine that periodically exchanges tips with peers and fetches missing effect chains. This ensures effects missed during partitions are eventually discovered without polluting the log with redundant subscription effects. Also starts the reconvergence debounce loop that coalesces peer recovery events into background anti-entropy passes.
func (*Engine) UpdateSafetyRules ¶
func (e *Engine) UpdateSafetyRules(defaultMode SafetyMode, rules []KeyRangeRule)
UpdateSafetyRules atomically replaces the key-range safety configuration.
func (*Engine) VertexCount ¶
VertexCount returns the number of effects resident in the vertex pool.
type EngineConfig ¶
type EngineConfig struct {
NodeID pb.NodeID
Broadcaster Broadcaster // nil for standalone
RTTProvider PeerRTTProvider // nil disables RTT-based leader selection
DefaultMode SafetyMode
KeyRangeRules []KeyRangeRule
MemoryLimit int64 // memory budget for effect cache (0 = 10MB default)
// MemoryLimitPercent, when non-zero, expresses the cache budget as a
// fraction (0,1] of the memory available to this process. Takes
// precedence over MemoryLimit. The cache enforces this via a live
// re-evaluating tick so cgroup/system changes propagate.
MemoryLimitPercent float64
// CompressValues makes this node store data values zstd-compressed inside
// the effects it emits, trading decompression on read for more resident
// DAG per byte. Write-side only: the per-effect compression flag drives
// reading, so nodes with different settings share one DAG.
CompressValues bool
}
EngineConfig holds configuration for creating an Engine.
func (*EngineConfig) ModeForKey ¶
func (c *EngineConfig) ModeForKey(key string) SafetyMode
ModeForKey returns the safety mode for a key. First matching rule wins.
type FetchHint ¶
type FetchHint uint8
FetchHint orders the two effect-byte sources for FetchFromAny. The engine derives it from knowledge it already holds: the per-peer key filters claim the key → PreferPeers; otherwise the cluster provably lacks it → PreferCDN. The unpreferred source is a failure fallback, never raced.
const ( // PreferPeers tries connected peers first, cloud CDN on total peer // failure. The default for peer-origin fetches (NACK ingest, backfill) // and any fetch without key context. PreferPeers FetchHint = iota // PreferCDN tries the cloud CDN first, peers on failure. Chosen when no // peer's key filter claims the key — a cloud rehydrate of state the // cluster no longer holds. PreferCDN )
type ForwardCommand ¶
ForwardCommand describes a single command to be forwarded.
type HorizonSet ¶
type HorizonSet struct {
// contains filtered or unexported fields
}
HorizonSet tracks Binds in their horizon wait period. Effects from invisible Binds are excluded from reconstruction until the horizon completes.
func (*HorizonSet) Abort ¶
func (h *HorizonSet) Abort(txnID string)
Abort removes a single entry from the horizon set without promoting it. Used by flushTx when the originator decides to abort after NACK processing. The bind effect is still in the local DAG and at peers; reconstruct's cross-key reachability is what skips it on read.
Other entries in the same group are unaffected — they continue waiting on their own visibility trigger (timer or explicit MakeVisible).
func (*HorizonSet) Add ¶
func (h *HorizonSet) Add(txnID string, bindOffset Tip, bind *pb.TransactionalBindEffect)
Add registers a Bind in the invisible set. The bind stays invisible until MakeVisible, Abort, or a timer scheduled via ScheduleMakeVisible fires. If the bind's keys overlap with an existing group's consumed tips, it joins that group; otherwise a new group is created.
func (*HorizonSet) Empty ¶
func (h *HorizonSet) Empty() bool
Empty reports whether no txn is currently held invisible. When true, reconstruct can skip its per-read invisibility precompute: any bind reachable from a read's captured tips had its horizon entry added during ingest (before the index update that made it visible to the read), so an empty horizon at reconstruct setup proves no walked bind is invisible.
func (*HorizonSet) IsInvisible ¶
func (h *HorizonSet) IsInvisible(txnID string) bool
IsInvisible returns true if the given txnID is in the invisible set.
func (*HorizonSet) MakeVisible ¶
func (h *HorizonSet) MakeVisible(txnID string)
MakeVisible promotes an entire group: removes all txnIDs from the invisible set, cleans up pendingTxTips, evicts cache, and fires OnKeyDataAdded callbacks.
func (*HorizonSet) ScheduleMakeVisible ¶
func (h *HorizonSet) ScheduleMakeVisible(txnID string, wait time.Duration)
ScheduleMakeVisible schedules MakeVisible(txnID) to fire after `wait`. Used by handleRemoteBind as a crash-fallback for the remote-arrival wait — the primary signal is the originator's verdict-snapshot arriving via applySnapshotVerdicts, which short-circuits this timer. The timer only fires when the originator died between broadcasting the bind and broadcasting the verdict; we keep it generous (~5s) to avoid racing the snapshot under normal conditions. Resets any existing timer on the group; a later-joining bind extends the wait.
func (*HorizonSet) WaitForClear ¶
func (h *HorizonSet) WaitForClear(txnID string) *HorizonWait
WaitForClear blocks until the bind for txnID has been resolved (MakeVisible or Abort releases the entry's waiter lock). Returns nil if the bind is not in the invisible set — the caller may proceed without waiting. Returns a *HorizonWait the caller must Release when done.
type HorizonWait ¶
type HorizonWait struct {
// contains filtered or unexported fields
}
HorizonWait is a handle returned by WaitForClear. It carries the horizonEntry pointer and the RToken so Release can RUnlock the same mutex even after the entry has been removed from h.entries by MakeVisible or Abort.
func (*HorizonWait) Release ¶
func (w *HorizonWait) Release()
Release returns the read-lock token to the horizonEntry. Safe on nil.
type KeyRangeRule ¶
type KeyRangeRule struct {
Pattern string
Mode SafetyMode
}
KeyRangeRule maps a key pattern to a safety mode.
type MemoryStorage ¶
type MemoryStorage struct {
// contains filtered or unexported fields
}
MemoryStorage is an in-memory ObjectStorage implementation for testing.
func NewMemoryStorage ¶
func NewMemoryStorage() *MemoryStorage
NewMemoryStorage creates a new MemoryStorage.
func (*MemoryStorage) Keys ¶
func (m *MemoryStorage) Keys() []string
Keys returns all stored paths (for testing).
type ObjectStorage ¶
type ObjectStorage interface {
Upload(ctx context.Context, path string, data []byte) error
Download(ctx context.Context, path string) ([]byte, error)
}
ObjectStorage abstracts uploading/downloading blobs to/from object storage.
type PeerRTTProvider ¶
type PeerRTTProvider interface {
// GetRTT returns the estimated round-trip time to the given peer.
// Returns 0 if the peer is unknown or RTT has not been measured.
GetRTT(nodeID pb.NodeID) time.Duration
// AlivePeerIDs returns the IDs of all peers that are currently alive.
AlivePeerIDs() []pb.NodeID
}
PeerRTTProvider provides RTT measurements to peers for optimal leader selection.
type SafetyMode ¶
type SafetyMode int
SafetyMode determines write behavior during network partitions.
const ( SafeMode SafetyMode = iota // blocks writes when quorum is unreachable UnsafeMode // allows writes during partitions, forming branches )
type Tip ¶
func NormalizeCloudTipCandidates ¶
func NormalizeCloudTipCandidates(key string, candidates, localRoots []Tip, load func(Tip) (*pb.Effect, error)) (real, superseded []Tip, err error)
NormalizeCloudTipCandidates reduces Cloud's marker candidates to the maximal tips of the effect DAG. Cloud's tips directory is an index: stale markers can remain after a newer effect has dep-referenced them, so callers must not treat every returned marker as an independent logical frontier branch. localRoots seeds the same walk with this node's own current tips for key, so a candidate already covered by local ancestry the cluster itself produced — not yet reflected in Cloud's index — is dropped as superseded too. Only candidates, never localRoots, can be marked superseded: local tips aren't entries in Cloud's index and have nothing there to correct.
Missing blobs do not abort normalization. A missing candidate can still be proven non-maximal when another candidate's or local root's readable ancestry names it. Any missing candidate that cannot be so proven remains a real tip and the caller's classification decides what to do with it.
type VertexPool ¶
type VertexPool struct {
// contains filtered or unexported fields
}
VertexPool is the engine's deserialized effect store: a map from Tip to its effect, replacing the per-offset CloxCache. Presence in the pool means "resident locally"; absence means getEffect must fetch the bytes from a peer. The pool is dumb storage — it has no eviction policy of its own. Eviction is driven by the engine memory governor, which is the only component that knows a key's active DAG path and can therefore choose safe victims.
The pool does NOT meter its own footprint: a protoLen-based sum undercounted each decoded effect graph ~3x and was blind to the off-pool per-key state (subdags, reduced memos, subscriptions) that grows alongside it. Bytes() reports the runtime's exact live heap instead — see Bytes.
DAG-reference counting lets eviction drop whole keys and reclaim only the vertices no other key or read still needs; a vertex is referenced by every tipset that reaches it and every cached subdag that adopted it.
func (*VertexPool) Bytes ¶
func (p *VertexPool) Bytes() int64
Bytes returns the process's live heap as marked by the last GC (/gc/heap/live:bytes). This is the exact, total memory signal the governor triggers and sizes eviction on, and it counts everything that grows per key — decoded effect graphs in the pool, off-pool subdags/reduced memos/subscription state, the trie — not just the pool's slice. A protoLen-based pool sum undercounted each decoded graph ~3x and was blind to the off-pool half, so triggering on it let the heap reach 10GB while the pool read 3GB.
It deliberately uses /gc/heap/live (live objects only) rather than HeapAlloc (/memory/classes/heap/objects), which is live + not-yet-collected garbage and sawtooths up to the GC goal (~2x live at GOGC=100). Triggering on HeapAlloc fired eviction on the garbage peaks while the true working set was under target. /gc/heap/live updates once per GC cycle — stable between cycles and, under a churning workload, fresh well within the governor's 1s tick. metrics.Read is cheap and non-STW.
func (*VertexPool) ColdEvictions ¶
func (p *VertexPool) ColdEvictions() uint64
ColdEvictions returns the number of keys the index's bounded sweep evicted under memory pressure — the real "evicted_keys", distinct from reclaim churn.
func (*VertexPool) Decref ¶
func (p *VertexPool) Decref(tip Tip)
Decref releases a DAG reference on the vertex at tip.
func (*VertexPool) EntryCount ¶
func (p *VertexPool) EntryCount() int
EntryCount returns the number of resident effects.
func (*VertexPool) Get ¶
func (p *VertexPool) Get(tip Tip) (*pb.Effect, bool)
Get returns the effect at tip.
func (*VertexPool) Incref ¶
func (p *VertexPool) Incref(tip Tip) bool
Incref adds a DAG reference to the vertex at tip and reports whether the reference was taken. See incref for the failure contract.
func (*VertexPool) Put ¶
func (p *VertexPool) Put(tip Tip, eff *pb.Effect)
Put stores eff at tip, computing its accounted size from the proto. Callers that already hold the serialized length — a marshalled buffer on emit, the wire protoData on ingest — should use PutSized to skip the proto.Size walk.
A re-Put of an already-resident tip keeps the existing vertex so its accumulated refcount survives: effects are immutable (a Tip is written once), so the payload and byte cost are identical. Replacing the vertex would reset refs to 0, letting reclaimUnreferenced free an effect the index still holds as a frontier tip — a premature free surfacing as a missing read. The resident fast-path also skips the proto.Size walk on the common re-delivery.
func (*VertexPool) PutSized ¶
func (p *VertexPool) PutSized(tip Tip, eff *pb.Effect, protoLen int)
PutSized stores eff at tip using a caller-supplied serialized length, avoiding the proto.Size walk Put would do. protoLen is the marshalled effect size (the length of MarshalEffect's output, or the wire protoData); vertexOverhead is added on top. Re-Put semantics match Put: an already-resident tip is kept.
A new vertex is born with its creation refcount equal to the number of tipsets it occupies — its "walkable from a tipset" references, one per reaching tip. An ordinary effect is the tip of its single key, so it starts at 1; a bind commits a whole keyset and is the tip of each key, so it starts at len(Keys). This pins the vertex (interior chain nodes included, local or backfilled from a peer) until each occupying key is evicted, where the eviction reachable-walk decrefs it once per key (releaseChainRefs). The count is stored before the vertex is published via LoadOrStore, so reclaimUnreferenced never observes a fresh vertex at 0 (a birth-race free). Read adoption layers further refs on top in publishSubdag.
If the tip is already resident as a cache entry (PutSizedCache left it at refs==0 because we did not yet serve its key), this owned Put promotes it by adding the creation ref it lacked, so reclaim won't drop a vertex we now own. The CAS makes that race-safe: it loses to reclaim's 0→tombstone claim (the vertex is being freed; we refetch on the next miss) and to a concurrent read's publishSubdag incref (refs>0; left un-promoted, resolved by a later refetch), and it never double-counts an already-owned re-put (refs>0 → CAS fails → keep the accumulated count).
func (*VertexPool) PutSizedCache ¶
func (p *VertexPool) PutSizedCache(tip Tip, eff *pb.Effect, protoLen int)
PutSizedCache stores a fetched effect on a key we do NOT serve as a reclaimable cache entry: refs start at 0, so it carries no creation ref and reclaim frees it the moment no subdag adopts it. These are cross-key bind adjudication fetches (and remote effects on unsubscribed keys): a read that walks them holds them via the reconstruct dag for its duration, and they are refetchable from a peer, so they must not pin memory the way an owned effect's creation ref does. An already-resident owned tip is kept (LoadOrStore), never downgraded. protoLen semantics match PutSized.
A freshly-stored entry is born at refs==0 and will never see a refs→0 transition unless first adopted (publishSubdag) and then dropped, so it is enqueued as a reclaim candidate at birth — without this an un-adopted cache fetch would never be reclaimed (there is no full-map scan to catch it).
func (*VertexPool) Reclaimed ¶
func (p *VertexPool) Reclaimed() uint64
Reclaimed returns the number of vertices freed by reclaimUnreferenced (below-LCA history and orphans) — storage churn, not cache eviction.
func (*VertexPool) Stats ¶
func (p *VertexPool) Stats() (hits, misses, reclaimed uint64)
Stats returns cumulative hit, miss, and reclaim counts. The third value is reclaimUnreferenced churn (vertices freed), not cache eviction — use ColdEvictions for keys dropped under memory pressure.