Capacity planning and sizing
Capacity planning for a Clustron Zaris store is the exercise of fitting your working set within the cluster's per-node memory ceilings, with enough headroom for eviction, across enough nodes, while accounting for the extra copies that replication creates. Every node is a bounded buffer capped by a memory ceiling — there is no unbounded setting — so a store's total capacity is finite and predictable once you know the ceiling, the node count, and the replication factor.
This article walks through the inputs you need, the storage math that turns them into a node count, how to choose a per-node ceiling, how eviction changes the sizing target, and how partition count and replication factor interact. Treat the formulas here as estimation guidance for a first cut, not exact guarantees — always confirm the real footprint with a benchmark before you commit.
The sizing inputs
Before you can size a cluster you need a handful of numbers. Gather these first:
- Working-set bytes — the total size of the data you need resident at once: keys, values, and per-item overhead (Zaris stamps recency/frequency and accounting metadata on each item). For the always-on LRU cache this is the hot set you want to keep in memory; if a future no-eviction mode is used it would instead be the full dataset.
- Replication factor (RF) — how many copies of each partition exist (
replication.factor, default1). Every replica is a full additional copy of the data, so RF multiplies stored bytes. - Per-node memory ceiling — the ceiling on actively owned data per node (
memory.maxSizeBytes, default 1 GiB). This is the hard cap each node holds under. - Eviction headroom — the fraction of the ceiling you can actually plan to fill. Because eviction runs on hysteresis (trigger at 80% of the ceiling, target ~70% after a run), a cache node settles around 70% of its ceiling, so you should plan usable capacity below the ceiling, not at it.
- Growth — expected increase in the working set over your planning horizon. Size for where you are heading, not only for today.
The storage math
The two questions capacity planning answers are how many bytes will the cluster store and how many nodes does that take. Both follow from the inputs above.
Total stored bytes is the working set multiplied by the replication factor, because each replica is a complete extra copy:
total stored bytes ≈ working set × replication factor
The number of nodes needed is the total stored bytes divided by what each node can usefully hold — its ceiling reduced by the eviction headroom, expressed as a usable fraction of the ceiling:
nodes needed ≈ (working set × replication factor)
─────────────────────────────────────
(per-node ceiling × usable fraction)
The usable fraction leaves room for the eviction band. For the always-on LRU cache, plan around the post-eviction target of roughly 0.70 (70% of the ceiling), since that is where a node settles after a run. If a future no-eviction mode is used, plan a smaller fraction still — you would want to stay clear of the ceiling entirely, because reaching it rejects writes rather than evicting.
A worked example
Suppose measurement (see benchmarking) puts your hot set at about 24 GiB — for instance, roughly 50 million items averaging under 500 bytes each once keys, values, and per-item overhead are counted. You want an HA cache at RF 2, on boxes where you have set a 4 GiB per-node ceiling, and you plan for the cache-workload usable fraction of 0.70.
| Input | Value |
|---|---|
| Working set | 24 GiB |
| Replication factor | 2 |
Per-node ceiling (memory.maxSizeBytes) | 4 GiB |
| Usable fraction (eviction headroom) | 0.70 |
Working the formula through:
total stored = 24 GiB × 2 = 48 GiB
usable per node = 4 GiB × 0.70 = 2.8 GiB
nodes needed = 48 GiB ÷ 2.8 GiB/node = 17.1 → round up to 18 nodes
So a first-cut sizing is 18 nodes carrying a 4 GiB ceiling each. Round up, never down — the fractional node is real data that has to live somewhere. If you later raise the ceiling to 8 GiB, usable per node becomes 5.6 GiB and the same working set fits in ⌈48 ÷ 5.6⌉ = 9 nodes; fewer, larger nodes trade some failover blast radius for a smaller node count.
These numbers are a starting point, not a guarantee. Per-item overhead, value-size distribution, and access patterns all move the real footprint, so validate the estimate with a representative benchmark before you finalize a topology.
Choosing the per-node ceiling
The per-node ceiling (memory.maxSizeBytes) should reflect the RAM of the box the node runs on, minus what the process runtime and the operating system need. A node holds its actively owned data under this ceiling, but the process as a whole also uses memory for the runtime, buffers, connection state, and any replica copies it keeps for other partitions — none of which the active-owner ceiling admits against. Set the ceiling so the whole process comfortably fits in physical RAM with room to spare; a node that pushes the machine into paging will be far slower than one sized to stay resident.
There is deliberately no unbounded option. If you omit maxSizeBytes (or set it to null), the 1 GiB default applies — never assume a node will simply grow to consume all available RAM, because it will not. Size the ceiling explicitly for production boxes rather than relying on the default.
Eviction versus no-eviction sizing
A node in the current release always runs the always-on LRU cache, so size the ceiling to the hot set, not the full dataset. When a node fills past its trigger, eviction sheds the coldest data to make room, so writes keep succeeding and the working set self-limits to what fits. Here the 0.70 usable fraction is the right planning target, and undersizing degrades hit rate rather than failing writes — though sizing too small forces constant eviction churn that costs throughput.
A no-eviction mode (a node that rejects new writes with CapacityExceeded at its ceiling rather than dropping older data) exists in the engine but is not selectable through configuration today. If a future release exposes it, size for the full dataset plus headroom instead: with eviction off there is no self-limiting behavior to fall back on, so you would have to provision enough total ceiling to hold everything you intend to store, with margin for growth, or writes would start failing.
In both modes the 4 MiB default maximum item size applies independently: a single value larger than that is rejected with ItemTooLarge regardless of how much room the cluster has, so account for it when your values can be large.
Partition count and replication factor
Two topology dials shape how data and load spread across the cluster, and both have capacity consequences.
Partition count defaults to one partition per node (topology.partitionCount is null, meaning system-determined). More partitions spread ownership and request load more evenly across nodes and make rebalancing finer-grained, at the cost of more coordination overhead. Production topologies typically keep the default; raising partitions per node is mainly a local-testing aid.
Replication factor is the direct multiplier on stored bytes. RF 1 keeps a single copy — compact and fast, but a node loss is data loss. RF ≥ 2 gives you a hot standby so the cluster survives a machine failure, but it doubles (RF 2) or triples (RF 3) the bytes the cluster must hold, and therefore the node count from the formula above.
The trade-offs at a glance:
| Dial | Increase it when… | Cost |
|---|---|---|
Partition count (topology.partitionCount) | You need to spread data and load more evenly, or want finer rebalancing | More partitions add coordination overhead; the default of one per node is right for most production clusters |
Replication factor (replication.factor) | You need to survive node loss without losing data — RF 2 for one failure, RF 3 for two | Each extra copy is a full extra copy of the data, multiplying stored bytes and the node count needed |
Replicas must land on distinct machines, so the replication factor cannot exceed your machine count in production. Plan machine count and RF together: RF 2 needs at least two machines, RF 3 at least three.
Validate with benchmarks
Every number on this page is an estimate until you measure it. Per-item overhead and value-size distribution determine the real bytes-per-item, and throughput under your workload determines whether a given node count actually serves your traffic — neither is knowable from a formula alone.
Before committing a topology, populate a store that mirrors your data and use the built-in tools to measure the real footprint and throughput: watch per-node memory under a representative load, and confirm the cluster sustains your target ops/sec without eviction churn or rising error rates. See performance and benchmarking for the cmdlets and how to read their results, and re-measure after any change to the ceiling, replication factor, or partition count.