Replicated cluster state
July 20, 2026 ยท View on GitHub
Last modified: 2026-07-20
The mesh's typed-state cache routes every key to a single owner and keeps
everything in memory. That is fine for best-effort caching, but it cannot
hold canonical state: an owner restart loses data, a delete reaches only
the current owner, and a healed partition never reconciles. The
replicated state substrate closes those gaps. It is configured under
proxy.cluster.replication and stores each record on several nodes, on
disk, with explicit consistency levels and a deletion protocol that
keeps removed data removed.
This substrate is infrastructure for cluster-internal state, not a
feature you turn on to change what a request does. Configuring
replication provisions the substrate and its admin surface without
changing any request-path behavior on its own. One public feature can
select it explicitly: compression.state.backend: mesh stores AI
compression session summaries on this substrate, and requires this
block to be configured. Redis remains the default and recommended
compression state backend; see
ai-context-compression.md for the
when-to-choose guidance and the consistency differences.
The mesh it extends is on the request path today, just not through
this replicated layer: examples/model-cluster-symmetric
and examples/model-cluster-split
place managed-model requests using the same gossip cluster's typed-state
cache, and approximate-tier key budgets disseminate per-node counters
over it too (see key-management.md). Neither
consumer uses the durable, quorum-checked replication block described
below โ they read the best-effort single-owner cache this substrate
sits underneath. Read this page when you are standing up or operating
the cluster substrate itself; read model-cluster-symmetric
or model-cluster-split for the
user-facing feature that currently rides on the surrounding mesh.
Configuration
proxy:
cluster:
cluster_id: prod
node_id: gw-1
state_dir: /var/lib/sbproxy/cluster
# ... seeds, security, roles ...
replication:
factor: 2 # copies kept per record
write_consistency: quorum # one | quorum | all
read_consistency: quorum # one | quorum | all
anti_entropy_interval_secs: 30
tombstone_gc_grace_secs: 86400
max_entries: 65536
max_value_bytes: 1048576
state_dir is required for canonical clusters and the replica shard
persists to <state_dir>/replicated-state.redb. A shard that cannot
open fails startup rather than running without durability.
Validation enforces: factor between 1 and 8, anti_entropy_interval_secs
between 5 and 3600, tombstone_gc_grace_secs at least ten anti-entropy
intervals, max_entries at most 1048576, and max_value_bytes at most
4 MiB.
Placement and consistency
Each key maps to a preference list: the first factor distinct nodes
reached walking the consistent-hash ring clockwise from the key's hash.
The first entry is the same node the single-owner cache would pick, so a
factor of 1 degenerates to the old routing. A cluster smaller than the
factor replicates to every node it has and runs degraded rather than
unavailable.
Writes build a versioned record (a logical version above the highest version any reachable replica holds), fan out to the whole preference list, and report success only after the configured number of replicas acknowledged:
one: a single acknowledgement. The write is durable on at least one node.quorum: a majority of the key's replica set. Withfactor: 2this means both replicas; withfactor: 3it means two.all: every replica.
A replica acknowledges only after committing the record to its disk shard, so an acknowledged write does not depend on any process's memory. If fewer replicas acknowledge than the level requires, the write returns a quorum failure. The write may still have applied on some replicas; retrying is safe (see idempotency below), and anti-entropy converges the remainder either way.
Reads consult read_consistency replicas, reconcile divergent answers
by logical version (ties resolve deterministically), and repair stale or
missing replicas in line before returning. Choose levels by the usual
overlap rule: write quorum plus read quorum yields
read-your-latest-acknowledged-write behavior; one/one maximizes
availability and tolerates stale reads.
A node that the failure detector has isolated from the cluster fails
its state operations fast instead of pretending a quorum is reachable.
one-level operations still serve from the local shard.
Durability and restart
Every replica apply is written through to a redb database before it is acknowledged or served. On restart a node reloads its shard from disk, drops live records whose absolute TTL elapsed while it was down, and keeps serving. Expiries are wall-clock deadlines, not process-relative, so they survive restarts.
One deliberate exception: a node that was offline longer than
tombstone_gc_grace_secs discards its stored records on startup and
rebuilds from the surviving replicas via anti-entropy. The reason is
deletion safety, explained next.
Deletion, tombstones, and why deleted data stays deleted
A delete is a write: it replicates a tombstone through the same quorum path. Tombstones never expire by TTL. They are physically removed only by acknowledgement-aware garbage collection, which requires both:
- The tombstone is older than
tombstone_gc_grace_secs. - Every replica in the key's current preference list confirms it holds the tombstone (or a causally newer record) for that key.
Until both hold, the tombstone stays and fences out every stale live copy: the merge rejects any candidate at a lower or equal version. A legitimate re-create is still possible, because a coordinator that read the tombstone writes at a higher version.
The no-resurrection argument composes three rules:
- While the tombstone exists anywhere in the replica set, a stale live record loses every merge against it.
- The tombstone is collected only after every current replica confirmed it AND the grace period elapsed, so no replica that participated in the deletion can later surface the old value.
- A node absent longer than the grace period might hold a live record whose covering tombstone was already collected cluster-wide. Such a node discards its shard on rejoin (the quarantine rule above) and resyncs from the current replicas, so its stale copy never re-enters the cluster.
The remaining window is a node absent for less than the grace period: its covering tombstone cannot have been collected yet (collection requires the full grace period), so anti-entropy delivers the tombstone to it, not the other way around.
One boundary rule makes collection terminate: a tombstone already past the grace period may still fence out an existing stale record on a replica, but it never re-creates an entry on a replica that stores nothing for the key. Without this, two replicas that both hold a collectable tombstone would re-teach it to each other through anti-entropy forever, each collecting it and then pulling it back.
Repair
A maintenance loop runs every anti_entropy_interval_secs on every
node, in three bounded phases:
- Handoff. Records this node holds but no longer replicates (the ring changed: a node joined, left, or was evicted) are pushed to every current replica. The local copy is dropped only after every push acknowledged; a failed push retries next round. Rebalancing moves data, it does not lose it.
- Anti-entropy. For each peer, exchange compact per-key digests (version, writer, tombstone flag) in bounded pages and reconcile both directions. This is what converges both live records and tombstones after a partition heals.
- Tombstone GC. As described above.
Quorum reads additionally repair stale replicas in line, so hot keys converge faster than the maintenance cadence.
Idempotent retries
Every successful write returns a receipt containing the exact versioned record that was replicated. A caller whose response was lost (timeout, connection drop) retries by re-submitting the receipt: replicas merge the identical record and report it unchanged. No version inflation, no duplicate effects, any number of retries.
Admin surface
Authenticated admin routes operate on the whole fleet (full request and response shapes in admin-api-reference.md):
GET /admin/cluster/state?prefix=&page_token=&limit=walks every current member and lists each record every holder still has. Entries name their holder, so a key withfactor: 2appears twice; collapse by key client-side. Pagination is bounded and topology-safe: if the member a page token points at leaves the cluster, the walk resumes at the next surviving member instead of failing or looping. Members that could not be queried are listed inunreachablerather than silently skipped.GETandPUT /admin/cluster/state/key?key=read and write one record through the configured consistency levels;PUTtakes the raw value as the request body and an optionalttl_secsparameter.DELETE /admin/cluster/state?key=performs a replicated delete with the configured write consistency.POST /admin/cluster/state/purgewith{"prefix": "...", "max": 1000}deletes every distinct live key under the prefix, bounded bymax; the response reportsdeleted,failed, andtruncatedso an operator can repeat until done.
Bounds and metrics
Each node's shard holds at most max_entries records; a write that
would exceed the bound is rejected, not silently dropped, and the
rejection is the backpressure signal. Values are capped at
max_value_bytes. Digest and listing pages are bounded server-side.
Diagnostics never log record values.
The substrate exports mesh_replication_writes_total{outcome},
mesh_replication_read_repairs_total, mesh_anti_entropy_rounds_total,
mesh_anti_entropy_keys_total{direction},
mesh_tombstone_gc_total{outcome}, mesh_handoff_keys_total{outcome},
and mesh_replica_shard_entries.
What this is not
These guarantees are scoped to what is written here: quorum-checked eventual consistency with durable local shards, deterministic conflict resolution, and a proven deletion protocol. It is not a consensus system, it does not serialize concurrent writers (the deterministic merge picks one), and it is not a claim of parity with any external datastore. Strict atomic semantics (reservations, compare-and-swap budgets) remain the job of a shared backend; see key-management.md for that split.
Wire compatibility
The replica operations extend the mesh transport protocol. The mesh wire format is not versioned across releases: all nodes in a cluster must run the same sbproxy release, and a rolling upgrade should drain nodes one at a time as usual. See upgrade.md.