Skip to main content

Zaris 2.0.0: Native Data Structures, Streams, and a Redis-Protocol Front-End

· 7 min read
Clustron Team
Distributed Systems Engineering

Zaris 2.0.0 — Native data structures, Streams, and a Redis-protocol front-end

Today we're shipping Zaris 2.0.0 — the biggest release since Zaris reached general availability, and the first with a major version bump. It turns Zaris from a fast, replicated key/value store into a full data-structure server: native hashes, lists, sets, sorted sets, pub/sub, and Redis Streams, all partitioned and replicated the same way your keys already are.

And it opens the door to everyone else. Every node can now expose a Redis-protocol (RESP) front-end, so an application in Python, Ruby, Java, Go — any language with a Redis client — can point at Zaris and use it as a drop-in, without a native SDK and without rewriting a line.

The version number is a 2, not a 1.x, for a reason: 2.0.0 changes the wire. Read the upgrade note below before you roll it out.

Two ways in, one store, seven kinds of data

Native data structures

Zaris 1.x stored values. Zaris 2.0.0 stores collections. Hashes, lists, sets, and sorted sets are now first-class, server-side types with real, typed .NET APIs:

await client.DataStructures.Hash("user:42").SetAsync("email", "ada@example.com");
await client.DataStructures.SortedSet("leaderboard").AddAsync("ada", score: 4096);
var top = await client.DataStructures.SortedSet("leaderboard").RangeByRankAsync(0, 9);

Each collection is a single logical value that lives on the partition that owns its key, and every mutation goes through the same replicated path as an ordinary Put — a whole-value compare-and-swap read-modify-write — so a hash or a sorted set is exactly as durable and highly available as a plain key. We built and validated these against .NET, Java, and Ruby clients so the semantics hold across languages, not just in-process.

See the data structures guide for the full API.

Pub/Sub and Streams

Two more collection kinds round out the set.

Pub/Sub gives you cluster-wide publish/subscribe — fan a message out to every subscriber across the cluster, not just the ones connected to one node.

Streams are the real headline: an append-only log with consumer groups and blocking reads, native to Zaris and reachable over the RESP XADD / XREAD / XREADGROUP commands. That makes Zaris a fifth kind of collection — a durable, replicated event log you can use for work queues, change feeds, and event sourcing, live-validated against a stock StackExchange.Redis client over a socket.

await client.Streams.Stream("orders").AppendAsync(new { id = 1001, total = 59.90 });
var batch = await client.Streams.Stream("orders").ReadGroupAsync("fulfilment", "worker-1", count: 10);

Details are in the streams and pub/sub guides.

A Redis-protocol front-end for any language

Zaris has always spoken its own efficient binary protocol to first-class clients — the .NET client, PowerShell, the console. 2.0.0 adds a second door: each node can also run a RESP2 listener, so any existing Redis client can talk to Zaris directly.

It works the way Redis Cluster does. Zaris hashes keys with the same CRC16(key) % 16384 slot math Redis Cluster uses, and each node advertises an accurate CLUSTER SLOTS view, so a cluster-aware Redis client routes each key directly to the node that owns it — no proxy, no extra hop. When the command reaches the owner it's served straight from that node's in-process store, and on a failover the client gets a MOVED and refreshes its slot map. Prefer not to run a cluster-aware client? Put the nodes behind a load balancer or use Sentinel instead, and any node will accept any key.

# point any Redis client at a Zaris node's RESP port
redis-cli -h zaris-0 -p 6379 SET greeting "hello from zaris"
redis-cli -h zaris-0 -p 6379 GET greeting

We measured coverage against StackExchange.Redis 2.8.16: 126 commands supported, 92 cleanly not-supported, and zero failures. "Cleanly not-supported" is the important part — commands Zaris doesn't implement (Lua/EVAL, geo, HyperLogLog, bitmaps, cross-key *STORE, blocking list pops, RDB/AOF persistence) are rejected honestly rather than silently doing the wrong thing. What is there covers the day-to-day: strings, INCR/DECR, MGET/MSET, hashes, lists, sets, sorted sets, streams, SUBSCRIBE/PUBLISH, keyspace notifications, MULTI/EXEC/WATCH (mapped onto Zaris's native two-phase-commit transactions), and the SET NX PX distributed-lock pattern. It's RESP2, so pin your client versions.

There's a whole post on how this front-end works — Any Language, One Store — and the exact command compatibility matrix if you want to check a specific command before you switch.

Reads that scale with your cores

Zaris routes each key to an owner and dispatches work per core, rather than funnelling everything through a single event loop. The practical effect: read throughput climbs as you add cores. Through the RESP front-end, GET runs at roughly 483,000 ops/sec on one core and about 946,000 on four, while single-threaded engines like Redis and Memurai stay flat near 740,000–780,000 no matter how many cores you give them.

We'll be honest — that's about 2× the throughput for 4× the cores, not perfectly linear. But the direction is the whole point: a partitioned, multi-threaded engine has somewhere to grow, and a single-event-loop engine doesn't. The full breakdown, with the honest caveats about client-bound benchmarking, is in Reads That Scale With Your Cores.

2.0.0 also tightens the rest of the hot path: the SET path lost a redundant read-before-write, the native client's core operations (Get/Put/Delete/Expire/Persist/TTL) now return ValueTask so synchronous completions allocate nothing, and an optional large-object mode stores big, frequently-updated values as sub-LOH chunks to ease GC pressure.

Upgrading: this is a wire break

2.0.0 is a major version because it changes the protocol. Native cluster routing moved to CRC16-aligned hashing, and the protocol minimum is now 2 — a 2.0.0 server rejects pre-2.0 (1.x) clients at the HELLO handshake. That's deliberate: a 1.x client and a 2.0.0 server would disagree about where a key lives, so we fail fast at connect time instead of corrupting placement.

The upgrade rule is simple: move clients and servers to 2.0.0 together. If you point a Redis client at the RESP front-end instead, you sidestep the native-client version coupling entirely — the RESP door speaks a stable, standard protocol.

Getting started

NuGet:

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

Windows: grab clustron-zaris-2.0.0-win-x64.zip from the release page (verify against SHA256SUMS.txt).

Docker:

docker pull clustron/zaris-node:2.0.0
docker pull clustron/zaris-manager:2.0.0

Kubernetes (Helm):

helm install zaris oci://registry-1.docker.io/clustron/zaris --version 2.0.0 \
-n zaris --create-namespace

Already on Redis and wondering what a move looks like in practice? Start with Coming From Redis, or the install guide if you just want a cluster running.

Zaris remains free to run under the Business Source License 1.1, with commercial support available at clustron.io/support. Full release notes and downloads are on GitHub. We'd love to hear what you build with it.