Skip to main content
Clustron Team
Distributed Systems Engineering
View all authors

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.

Any Language, One Store: How Zaris Speaks the Redis Protocol

· 7 min read
Clustron Team
Distributed Systems Engineering

Any language, one store

Zaris is a distributed key/value store built natively for .NET. Its first-class clients speak an efficient binary protocol, and if your stack is .NET, that's the path you want — real async APIs, .NET types, coordination primitives, IDistributedCache and HybridCache drop-ins.

But most systems aren't only .NET. There's a Python service doing data work, a Go sidecar, a Ruby job runner, a Node gateway. Rewriting all of them onto a new client to try a new store is a non-starter. So in 2.0.0, every Zaris node can also expose a Redis-protocol (RESP2) listener: point an existing Redis client at it and use Zaris as a drop-in key/value store, in any language, without changing a line of client code.

This post is about how that front-end actually works — the routing model, the data model, the failover models, and exactly which commands are in and out.

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.

Coming From Redis: Pointing Your .NET App at Zaris

· 5 min read
Clustron Team
Distributed Systems Engineering

Two paths from Redis to Zaris

If your .NET services already talk to Redis, moving to Zaris doesn't have to be a rewrite. Zaris is a distributed, in-memory, replicated key/value store built natively for .NET — and as of 2.0.0 it speaks two protocols at once: its own efficient binary protocol for first-class .NET clients, and a Redis-compatible RESP2 front-end that any Redis client in any language can point at.

That gives you two honest migration paths, and they're not mutually exclusive. You can start with the zero-change RESP path to get Zaris under load today, then adopt the native client where it pays off. This post walks both, and gives you a clear rule for picking.

Zaris 0.9.0 Is Here — and It's Generally Available

· 4 min read
Clustron Team
Distributed Systems Engineering

Zaris 0.9.0 — Generally Available

Today we're shipping Zaris 0.9.0, and with it, Zaris hits general availability. It's production-ready, cross-platform, and now backed by commercial support.

If you haven't run into it yet: Zaris is a distributed, in-memory key/value store for .NET. It's fast, replicated, and you can operate it from a browser, PowerShell, or straight from your app. This release is the culmination of that work — the point where "promising project" becomes "thing you can put in production."

Distributed Locks Explained

· 4 min read
Clustron Team
Distributed Systems Engineering

A Practical Guide for .NET Developers

Introduction

As applications scale across multiple machines, coordination becomes one of the most challenging aspects of system design.

In a single-machine application, mutual exclusion is straightforward. Developers can rely on language primitives such as lock in C#, Mutex, or Semaphore to ensure that only one thread accesses a shared resource at a time.

However, these primitives only work within a single process or machine.

Once applications run across multiple services or nodes, traditional locking mechanisms no longer work. Two services running on different machines cannot rely on a local lock statement to coordinate access to shared state.

This is where distributed locks become essential.

Distributed locks allow services across a cluster to coordinate access to shared resources while ensuring that only one participant holds the lock at a given time.


Local Locks vs Distributed Locks

A typical local lock in C# looks like this:

private static readonly object _lock = new object();

public void ProcessOrder()
{
lock(_lock)
{
Console.WriteLine("Processing order safely.");
}
}

This works perfectly inside a single process.

However, if your application runs on multiple service instances, each instance has its own _lock object. The lock no longer coordinates work across the system.

Worker A → lock acquired Worker B → lock acquired Worker C → lock acquired

All workers proceed simultaneously, defeating the purpose of locking.

Distributed systems require a shared coordination mechanism.


Distributed Locking


The Idea Behind Distributed Locks

A distributed lock uses a shared system to represent lock ownership.

Instead of locking memory, a node records lock ownership in a distributed coordination system such as:

  • a distributed key‑value store
  • a coordination service
  • a distributed cache

Example concept:

Lock Key: order:123 Owner: worker‑1

If another worker attempts to acquire the same lock, the operation fails until the lock is released or expires.


Real World Scenario: Distributed Invoice Processing

Imagine a cluster of workers processing invoices.

Worker A Worker B Worker C

All workers scan for pending invoices.

Without locking:

Worker A → processes invoice #101 Worker B → processes invoice #101 Worker C → processes invoice #101

The same invoice could be processed multiple times.

A distributed lock ensures that only one worker processes the invoice.


Distributed Locks with Clustron DKV

Clustron DKV provides built‑in primitives for distributed coordination.

Locks are managed through the Locks API, and operations on locked keys must include the correct lock handle.


Acquiring a Lock

A client first acquires a lock for a specific key.

var locks = ((IDkv)client).Locks;

var key = "order:123";

var lockHandle = await locks.AcquireAsync(key, TimeSpan.FromSeconds(10));

if (lockHandle == null)
{
Console.WriteLine("Failed to acquire lock.");
return;
}

Console.WriteLine("Lock acquired.");

The returned lock handle represents ownership of the lock.


Performing Operations Under the Lock

Once a lock is acquired, operations must include the lock handle.

var result = await client.PutAsync(
key,
"processed",
Put.WithLock(lockHandle.Handle));

if (result.IsSuccess)
{
Console.WriteLine("Value updated safely.");
}

Only the lock owner can modify the value.


Reading with the Lock

Reads can also respect the lock ownership.

var value = await client.GetAsync<string>(
key,
Get.WithLock(lockHandle.Handle));

if (value.IsSuccess)
{
Console.WriteLine($"Value: {value.Value}");
}

Competing Operations Without the Lock

If another worker attempts to modify the key without the lock, the operation fails.

var attempt = await client.PutAsync(key, "new-value");

if (!attempt.IsSuccess)
{
Console.WriteLine($"Operation rejected: {attempt.Status}");
}

Result:

KvStatus.Locked

This prevents concurrent modification of the same resource.


Releasing the Lock

Once the work is finished, the lock can be released.

await lockHandle.ReleaseAsync();

Console.WriteLine("Lock released.");

After the lock is released, another worker may acquire it.


Example Cluster Workflow

Worker A → Acquire Lock → Success Worker B → Acquire Lock → Failed Worker C → Acquire Lock → Failed

Worker A → Update Key Worker A → Release Lock

Only one worker performs the operation, ensuring correctness.


Handling Failures

Locks in Clustron are time‑bound.

When acquiring a lock, a duration must be specified:

await locks.AcquireAsync(key, TimeSpan.FromSeconds(10));

If the client holding the lock crashes or disconnects, the lock eventually expires, allowing another worker to acquire it.

This prevents locks from remaining permanently held.


Best Practices

When implementing distributed locks:

  • Keep critical sections short
  • Avoid global locks when possible
  • Partition locks by resource
  • Always design for failure scenarios

Final Thoughts

Distributed locks are one of the most fundamental coordination primitives in distributed systems.

They allow services running across multiple machines to safely coordinate access to shared resources while preserving system correctness.

Platforms like Clustron DKV simplify this by providing built‑in locking primitives, atomic operations, and strong consistency guarantees.

In the next article, we will explore another essential distributed systems primitive:

Leader Election.

Building Distributed Systems

· 4 min read
Clustron Team
Distributed Systems Engineering

A Practical Guide for .NET Developers

Introduction

Most backend systems begin their life on a single machine.

An ASP.NET Core application processes requests, a database stores application data, and background workers handle asynchronous tasks such as sending emails or generating reports. In this environment, concurrency is manageable and coordination between components is relatively straightforward.

As the system grows, however, things start to change.

Traffic increases, more application instances are deployed behind a load balancer, and services begin running across multiple machines. What was once a single application evolves into a distributed system.

At this point, the nature of the problem changes dramatically.

The challenges are no longer limited to writing correct business logic. Developers must now deal with coordination across machines, network failures, partial outages, and maintaining consistency across distributed state.

This is where distributed systems engineering begins.


Distributed Systems Architecture


What Is a Distributed System?

A distributed system is a collection of independent computers that appear to the user as a single coherent system.

Instead of running all logic on one machine, responsibilities are distributed across multiple nodes that communicate over the network.

Examples include:

  • Microservice architectures
  • Distributed databases
  • Clustered caches
  • Message queues
  • Cloud infrastructure platforms

These systems provide scalability and resilience but introduce new complexities.


Why Distributed Systems Are Hard

Unlike a single-machine application, distributed systems must deal with realities such as:

Network Failures

In distributed systems, communication happens over a network. Networks can be slow, unreliable, or temporarily unavailable.

A request may fail because the remote node crashed, because the network dropped packets, or because a timeout occurred.

The system must handle these scenarios gracefully.


Partial Failures

In a distributed system, components can fail independently.

One node in a cluster might crash while others remain healthy. A service instance may restart while other services continue running.

This means the system must operate correctly even when only part of it is functioning.


Consistency Challenges

When data is replicated across multiple nodes, keeping it consistent becomes difficult.

For example:

  • Two nodes may attempt to update the same data simultaneously.
  • Network delays may cause nodes to temporarily disagree about the current state.
  • A node might crash after performing only part of an operation.

Designing systems that maintain consistent state despite these scenarios requires careful coordination.


Key Building Blocks of Distributed Systems

Most distributed systems rely on a few core primitives.

Understanding these primitives helps developers design reliable architectures.


Distributed Storage

Data must often be stored across multiple machines.

This enables:

  • horizontal scaling
  • fault tolerance
  • high availability

Distributed key-value stores are commonly used for this purpose.


Coordination

Nodes must coordinate actions such as:

  • leader election
  • distributed locking
  • cluster membership
  • configuration updates

Without coordination mechanisms, multiple nodes might attempt conflicting operations.


Consensus

Some systems require strong guarantees about shared state.

Consensus algorithms such as Raft or Paxos allow nodes to agree on decisions even in the presence of failures.

These algorithms are fundamental to distributed databases and coordination services.


Partitioning and Replication

Data is typically:

  • partitioned across nodes to distribute load
  • replicated to protect against failures

Balancing performance, availability, and consistency is one of the core design challenges.


Common Distributed System Patterns

Several patterns appear repeatedly in real-world systems.

Examples include:

  • Leader election for coordinating cluster activities
  • Distributed locks for ensuring mutual exclusion
  • Job queues for asynchronous processing
  • Rate limiters for controlling request volume
  • Transaction protocols for atomic multi-node updates

These patterns form the foundation of many large-scale platforms.


The Role of Coordination Systems

To simplify distributed development, many organizations rely on coordination systems such as:

  • etcd
  • ZooKeeper
  • Consul

These systems provide primitives such as:

  • leader election
  • distributed locks
  • cluster membership
  • configuration storage

Applications can then build higher-level behavior on top of these primitives.


Distributed Systems in the .NET Ecosystem

While distributed infrastructure tools are often associated with other ecosystems, .NET applications increasingly operate in distributed environments.

Modern .NET systems frequently use:

  • Kubernetes
  • distributed caches
  • microservices
  • event-driven architectures

As a result, coordination and distributed state management are becoming increasingly important for .NET developers as well.


Final Thoughts

Building distributed systems is fundamentally different from building traditional single-machine applications.

The challenges of network communication, partial failures, and data consistency introduce complexities that require specialized patterns and infrastructure.

Fortunately, by understanding the key building blocks — storage, coordination, consensus, and replication — developers can design systems that scale reliably and remain resilient in the face of failures.

In future articles, we will explore some of these primitives in more detail, including leader election, distributed coordination patterns, and practical implementations in .NET.