Skip to main content

Reads That Scale With Your Cores

· 5 min read
Clustron Team
Distributed Systems Engineering

Zaris GET throughput scales with cores

Redis is fast. It is also, by design, single-threaded on the data path: one event loop, one core, one command at a time. That's a genuinely good design — it makes Redis simple to reason about and removes a whole category of concurrency bugs. But it has a ceiling you can't buy your way out of. Give a single-event-loop engine a 4-core box, an 8-core box, a 32-core box, and read throughput lands in the same place. The extra cores sit idle.

Zaris takes the other road. Keys are partitioned and each partition has an owner, so reads for different keys land on different cores and run at the same time. The result is throughput that moves when you add cores — instead of a flat line, a slope.

The numbers

We measured GET throughput through the Zaris RESP front-end — the same Redis-protocol path an ordinary Redis client would use — against Redis and Memurai on the same hardware.

Engine1 core4 cores
Zaris (RESP GET)~483,000 ops/sec~946,000 ops/sec
Redis / Memurai~740–780,000 ops/sec~740–780,000 ops/sec (flat)

Two things are true here, and we want to be straight about both.

First, the honest caveat: going from 1 core to 4 cores roughly doubles Zaris throughput, not quadruples it. That's about 0.5 scaling efficiency — sub-linear. Cross-core coordination, memory bandwidth, and the network path all take their cut. Anyone who tells you a distributed store scales perfectly linearly is selling something.

Second, the part that matters: Zaris climbs, and a single-event-loop engine can't. At one core, Redis and Memurai are actually ahead — a tight single-threaded loop with no cross-core coordination is hard to beat on one core. But that number is also their ceiling. Zaris starts lower and passes them as cores come online, because the work is genuinely parallel. On a big modern box, "scales to 0.5 efficiency" beats "pinned to one core" every time.

Why it scales

A single event loop serializes everything: every GET, every SET, every client, funneled through one thread. It's cache-friendly and lock-free, but it is fundamentally one core's worth of work.

Zaris instead gives every partition an owner and dispatches per-core. Two clients reading two keys that live in different partitions never touch the same thread. Add cores, add partitions, and more of those reads run concurrently.

And there's no proxy tax to pay for it. Zaris hashes keys with the same CRC16(key) % 16384 slot math Redis Cluster uses, so a cluster-aware Redis client routes each key straight to the node that owns it:

redis client ──CRC16(key) % 16384 → slot → owner──▶ node that holds the key ──▶ reply

That's one hop, exactly like talking to Redis Cluster — the client hashes the key and connects directly to its owner, and when the command lands there it's served from that node's in-process store. What you gain over a single instance is read capacity that grows with the cluster instead of topping out on one core, without giving up direct-to-owner routing to get it.

The write path got faster too

2.0.0 wasn't only about reads. On the SET path we removed a read-before-write that every write was paying for, and fixed a stray Count() call on the hot path that had quietly capped SET throughput. On the native .NET client, the hot-path operations — Get, Put, Delete, Expire, Persist, Ttl, and their chained forms — now return ValueTask, so a call that completes synchronously allocates nothing:

// Alloc-free when the value is already local — no Task on the fast path.
ValueTask<ReadResult<Order>> pending = client.GetAsync<Order>("order:42");

And for workloads with large, frequently-updated values, an optional large-object chunking mode stores big hot values as sub-LOH byte[][] pieces, keeping them off the Large Object Heap and out of the way of the garbage collector.

Measure it honestly

If you benchmark this yourself, two things will bite you:

  • Read benchmarks are usually client-bound. A single load generator saturates long before the server does, and then you're measuring your client, not Zaris. To actually see the server scale, you need enough concurrent load — multiple generators, enough connections — to keep every core busy. If throughput is flat as you add server cores, check whether your client is the bottleneck first.
  • Measure server CPU correctly. Read it from the cgroup, not from a cluster metrics-server estimate. The estimate lags and smooths, and it will lie to you about whether the server is actually saturated.

Try it

dotnet add package Clustron.Zaris.SDK --version 2.0.0

If you're weighing Zaris against Redis for a .NET stack, the Clustron vs Redis comparison lays out the trade-offs, and Zaris 2.0.0 covers everything else that shipped in this release — native data structures, Streams, and the Redis-protocol front-end that made these numbers measurable in the first place. If you're coming from an existing Redis deployment, Coming from Redis walks the migration, and the Redis-protocol deep dive explains the proxy design in detail.

Downloads and full release notes are on GitHub, or install Zaris on your platform of choice.