Skip to main content

Memory and eviction

Clustron Zaris is an in-memory store: every node holds its data in RAM, and each node is capped by a memory ceiling. This article explains how that ceiling works, what happens as a node fills up, and how eviction keeps a node within its limit.

The store is a bounded buffer

A Zaris node does not grow without limit. Each node continuously accounts for the size of the data it actively owns and holds that total under a configured ceiling. Accounting is never optional — a node is always a bounded buffer, and the only things you configure are the size of the ceiling and what the node does when it reaches it.

The default ceiling is 1 GiB of actively owned data per node. There is deliberately no "unbounded" setting: if you do not set a ceiling, the default applies.

note

The ceiling covers the data a node actively owns — the primary copies it serves. Replica copies that a node holds for other partitions are tracked separately and are not what the active-owner ceiling admits against.

What happens as a node fills up

A node holds its ceiling in one of two ways, depending on whether eviction is enabled:

ModeBehavior at the ceilingResult of a write that would exceed it
Eviction enabled (default)The node evicts older data to make roomThe write is admitted; other data is evicted
Eviction disabledThe node refuses new dataThe write is rejected with KvStatus.CapacityExceeded

A Zaris node behaves like a bounded cache: under memory pressure it evicts to stay within the ceiling, so writes keep succeeding and the least-valuable data is dropped first. The alternative — a reject-at-capacity store that refuses new writes rather than dropping older data — is the no-eviction mode described below.

note

In the current release, a node always runs with eviction enabled using the LRU policy. The no-eviction mode and the LFU / TTL-aware policies exist in the engine but are not yet selectable through configuration — the memory ceiling (memory.maxSizeBytes) is the setting you control.

Eviction policies

When eviction is enabled, a policy decides which items to drop first. In the current release the policy is always LRU — the other policies below describe the model but are not yet selectable through configuration. Zaris samples a small set of candidates and evicts the lowest-scoring ones, rather than maintaining a strict global order — the same sampled approach Redis uses.

PolicyEvicts firstUse when
LRU (default)The least recently accessed itemsAccess is recency-biased — recent data is likely to be read again
LFUThe least frequently accessed itemsA stable hot set benefits from being kept over one-off spikes
TTL-awareThe items closest to their TTL expiryMost data already has a TTL and you want natural expiry order
No evictionNothing — the node rejects at capacity insteadYou never want data dropped implicitly

Recency and frequency are tracked as approximate, in-place signals stamped on the read path, so they cost almost nothing to maintain and never require a separate index.

How an eviction run works

Eviction is driven by a background loop, not by each individual write. The loop uses hysteresis — a high trigger and a lower target — so it does not fire continuously at the edge of the ceiling:

  • Trigger: a run starts when active usage rises above 80% of the ceiling.
  • Target: once triggered, the run continues until roughly 30% of the ceiling is free again — that is, until usage drops to about 70%.
  • Cadence: the loop wakes on a fixed interval (one second by default) and samples a bounded set of candidate items each cycle.

Because the post-eviction target (70%) sits strictly below the trigger (80%), a run brings usage down into a quiet band instead of stopping right at the point that would immediately re-fire it.

The per-item size limit

Independent of the overall ceiling, a single item may not exceed a maximum item size4 MiB by default. This limit is checked on every write regardless of eviction mode: an oversized value is never admitted, and no amount of eviction will make room for it. A write that exceeds the limit is rejected with KvStatus.ItemTooLarge.

This protects a node from a single pathological value and keeps per-item accounting cheap.

Handling capacity signals in your code

Both capacity outcomes surface as typed statuses on the result you already inspect — they are not exceptions, and they are distinct so you can react to each correctly:

var result = await client.PutAsync("report:2026-07", payload);

if (!result.IsSuccess)
{
switch (result.Status)
{
case KvStatus.ItemTooLarge:
// The value itself is too big — split it or store a reference instead.
break;
case KvStatus.CapacityExceeded:
// The node is full and eviction is disabled — retry later or shed load.
break;
}
}

CapacityExceeded occurs only in no-eviction mode, which is not selectable in the current release, so you will not normally encounter it — a full node evicts under the always-on LRU policy rather than rejecting. ItemTooLarge, by contrast, is a live, reachable outcome and can occur in any mode.

Configuration

The node's memory ceiling is part of the store configuration, under the memory section:

{
"memory": {
"maxSizeBytes": 2147483648
}
}

Setting maxSizeBytes to null (or omitting it) applies the 1 GiB default. In the current release maxSizeBytes is the only configurable memory setting: the eviction policy (LRU), the trigger/target thresholds, and the per-item limit described above are fixed defaults, not configuration knobs. See the configuration reference for the complete memory schema.

What this means for capacity planning

Because each node is a bounded buffer, a store's total capacity is roughly the per-node ceiling multiplied by the number of partitions a node owns, adjusted for the replication factor (replicas hold additional copies). A node runs the always-on LRU cache today, so size the ceiling to your working set plus headroom for the eviction band; cold data is dropped under pressure rather than rejected.

Next steps