Redis protocol (RESP) compatibility
The RESP front-end is generally available as of Zaris 2.0.0. The commands and failover behaviour described here are validated against StackExchange.Redis and the mainstream Python, Ruby, and Java clients. It remains off by default — enable it deliberately per node — and because the endpoint is RESP2-only you should still pin your client versions (see Language clients).
Zaris speaks its own efficient binary protocol to first-class clients (the .NET client, PowerShell, the console). Alongside that, each node can also expose a Redis-protocol (RESP2) listener, so an existing application that already talks to Redis — in any language — can point at Zaris and use it as a drop-in key-value store without rewriting to the Zaris client. This page explains what that gives you, how to turn it on, the three ways a Redis client can connect and fail over, and the gotchas that come from Zaris being a different engine underneath a familiar protocol.
If you are porting application code command-by-command, read Coming from Redis first — that page maps Redis commands to the native Zaris API. This page is about pointing an unmodified Redis client at Zaris over the wire.
What it is (and what it is not)
- It is a real RESP2 server on each node that accepts ordinary Redis commands (
GET,SET,DEL,EXPIRE,TTL,INCR,MGET/MSET,MULTI/EXEC/WATCH,SUBSCRIBE/PUBLISH, keyspace notifications, and theCLUSTER/SENTINELfamilies used for discovery and failover). - It is not a fork of Redis. There is no RDB/AOF, no Lua, and no Redis modules. But beyond plain strings, the
four core collection types — hashes, lists, sets, and sorted sets — plus Streams (
XADD/XREAD, consumer groups, and blockingXREAD/XREADGROUP) are supported (see below); bitmaps, HyperLogLog and geo are not. Treat it as a fast, highly-available KV + collections + streams store that speaks Redis, not as a Redis replacement for every feature.
How a command actually travels
A Redis client connects to one node's RESP port. That node holds a co-located native Zaris client and proxies each command to the current owner of the key:
redis client ──RESP──▶ Zaris node A ──native Zaris protocol──▶ owner node B ──▶ reply
The important consequence: any node can serve any key. The client never needs Zaris's internal map — the node it happens to hit will route the command for it. Every operation is therefore a two-hop proxy, which is the main performance difference from talking to the native client directly.
Enabling it
The listener is off unless you ask for it. Configure it per node with environment variables:
| Variable | Purpose |
|---|---|
ZARIS_RESP_ENABLED | true to bind the RESP listener on this node. |
ZARIS_RESP_PORT | RESP data port (Redis default 6379). |
ZARIS_RESP_PASSWORD | Optional AUTH password; when set, clients must AUTH first. |
ZARIS_RESP_ENDPOINTS | nodeId=host:port,… for every node — lets any node hand a client the right address for Sentinel/Cluster discovery. |
ZARIS_RESP_SENTINEL_PORT | Bind a dedicated Sentinel listener on this port (enables the Sentinel model). |
ZARIS_RESP_MASTER_NAME | Sentinel master name a client asks for (default mymaster). |
ZARIS_RESP_CLUSTER_ENABLED | true to present the Redis Cluster protocol (CLUSTER SLOTS/NODES). |
A minimal single-endpoint setup needs only ZARIS_RESP_ENABLED and ZARIS_RESP_PORT. The Sentinel and Cluster
variables layer the two topology-aware models on top.
# minimal: expose RESP on 6379, clients connect and use it like a standalone Redis
ZARIS_RESP_ENABLED=true
ZARIS_RESP_PORT=6379
Then, from any language:
import redis
r = redis.Redis(host="your-node", port=6379)
r.set("greeting", "hello")
print(r.get("greeting")) # b'hello'
Choosing how clients connect and fail over
A single Zaris cluster has several nodes, and the node a client is connected to can fail. Redis offers three established ways for a client to survive that, and Zaris supports all three. They are mutually-exclusive choices — pick one for a given cluster. There is a full design write-up in the engineering docs; the summary and the practical guidance are below.
1. Load Balancer (recommended default)
Put a TCP load balancer (or a Kubernetes Service) in front of every node's RESP port, or list all the node
endpoints in your client. The client is "dumb" — it knows nothing about masters or slots. When the node it is
using dies, it reconnects and the LB (or the client's own endpoint list) steers it to a healthy node, which
proxies the key as usual.
# client given several endpoints; on failure it uses another
r = redis.Redis(host="zaris-lb", port=6379, socket_connect_timeout=3, retry_on_timeout=True)
- Failover window: roughly the client's TCP reconnect time — a few seconds.
- Why it's the default: simplest client, best availability, spreads load across nodes, and it never waits on
internal primary promotion (any node proxies the failed node's keys). Ideal on Kubernetes with one
Service.
2. Sentinel
Each node runs a Sentinel listener (ZARIS_RESP_SENTINEL_PORT). The client connects to the Sentinels, asks for
the master by name, and gets the current cluster leader's RESP endpoint; on a leadership change Zaris publishes
+switch-master and the client re-points automatically.
from redis.sentinel import Sentinel
s = Sentinel([("node0", 26379), ("node1", 26379)], socket_timeout=1.0)
master = s.master_for("mymaster", socket_timeout=1.0)
master.set("k", "v")
- Failover window: ~20–30 s (Sentinel down-detection plus leader re-election).
- Trade-off: there is only ever one active master endpoint, so all traffic funnels through the leader node — no load spread. Choose this only when you have existing Sentinel tooling or specifically want a single master.
3. Cluster
Enable ZARIS_RESP_CLUSTER_ENABLED. Zaris advertises CLUSTER SLOTS/CLUSTER NODES, and a cluster-aware client
routes each key directly to its owning node by hash slot (CRC16(key) % 16384), refreshing the map on failure.
from redis.cluster import RedisCluster
rc = RedisCluster(host="node0", port=6379)
rc.set("k", "v") # routed to the slot owner
- Failover window: ~20–30 s. The client is pinned to the slot owner, so on a primary loss it must wait for Zaris to promote the replica and then refresh the slot map — you pay the full promotion window here, unlike the LB model.
- Trade-off: load spreads across nodes by slot, but multi-key commands must stay within one slot (use
{hash-tags}to co-locate related keys, exactly as in real Redis Cluster). Read replicas are not advertised.
Which to pick
| If you want… | Use |
|---|---|
| The simplest setup and best availability (most apps) | Load Balancer |
| A drop-in for an existing Sentinel-based app, or one well-known master | Sentinel |
| A topology-aware client that routes by slot and spreads load | Cluster |
All three fully recover after a node is killed and after it rejoins; expect a brief burst of errors during the transition. In every case, configure your client the way you would against real Redis: don't abort on the first connect failure, allow retries, and set sane timeouts.
Gotchas
These come from Zaris being a distributed store with its own consistency model wearing a Redis protocol. None of them are bugs — they are the seams where the two systems differ.
- Two-hop proxy on every command. A RESP op is always client → node → owner. Throughput and latency are lower than the native Zaris client (which routes in one hop). For the hottest paths, prefer the native client.
- Failover latency is set by Zaris, not the protocol. A failed primary is only promoted after the node is declared lost (a ~30 s grace window). The Load Balancer model hides most of this because it never pins to an owner; Cluster and Sentinel clients are topology-pinned and will see a ~20–30 s window. Size your client timeouts and retries accordingly.
- Data structures use a whole-value model. Strings, hashes, lists, sets, and sorted sets are supported
(
HSET/HGETALL,LPUSH/LRANGE,SADD/SMEMBERS,ZADD/ZRANGE, plusTYPEandWRONGTYPEon a mismatched key) — validated through StackExchange.Redis, Jedis, and redis-rb. Internally each collection is stored as one value and each mutation is an atomic read-modify-write of the whole value: correct under concurrency, but O(collection size) per op — great for typical sizes, less so for very large collections or extreme single-key write rates. Not yet available: cross-key commands that span keys (SINTERSTORE,SUNIONacross keys…), list blocking pops (BLPOP…), bitmaps, HyperLogLog, and geo. - Streams are supported. The Redis Stream command family —
XADD,XLEN,XRANGE/XREVRANGE,XREAD,XDEL,XTRIM,XSETID,XINFO, the consumer-group commands (XGROUP,XREADGROUP,XACK,XPENDING,XCLAIM/XAUTOCLAIM), and blockingXREAD/XREADGROUP BLOCK— maps onto the native Zaris Streams API. See Streams for the model and the whole-value scaling caveat (bound growth withMAXLEN/MINID). - One logical keyspace.
SELECT/multiple numbered databases are not the Zaris model; treat it as DB 0. Keyspace-notification channels use the@0database tag for compatibility. - Cluster mode is masters-only. Replicas are not advertised as slaves, so there is no replica-read routing
(
READONLY). All reads and writes go to the primary of the slot. - Multi-key atomicity follows the slot rule.
MSET/MGET/transactions across keys that live in different partitions are not atomic unless the keys share a hash tag that co-locates them. This is the same discipline Redis Cluster imposes; apply it even in the LB model if you depend on cross-key atomicity. - Not-yet-implemented commands return an error, not a wrong answer. Lua (
EVAL),CLUSTER SHARDS(returns empty so clients fall back toSLOTS/NODES), and various admin/introspection commands are absent. If your client library issues one during startup, it will generally degrade gracefully; if it hard-depends on it, pin to a version that does not. - Separate ports for Sentinel and data. A Sentinel client refuses a node that reports
redis_mode:standalone, so the Sentinel model requires the dedicatedZARIS_RESP_SENTINEL_PORTin addition to the data port. - The endpoint is RESP2 only;
HELLO 3does not upgrade. Zaris answersHELLOwith a RESP2 reply (proto:2) and ignores a request to switch to RESP3. Modern clients that assume a RESP3 map reply during their connect handshake can break — notably redis-py 8+ (fails at connect) and redis-rb's native Sentinel driver (expects RESP3 maps fromSENTINEL). Use a RESP2-era client version, or discover the master with a rawSENTINEL get-master-addr-by-namecall and connect directly (see the table below). - Transactions cover a write-focused subset. Inside
MULTI/EXEC, plain key operations (SET/GET/DEL) andWATCH-based optimistic concurrency work. Arithmetic and expiry commands (INCR/DECR/EXPIRE/TTL) are not accepted inside a transaction, and a queued read does not observe an earlier queued write in the sameMULTI. Do arithmetic/expiry outside a transaction, and useWATCH+ a plainSETfor compare-and-set. This is enough for the common "atomic check-and-set" and lock patterns; it is not the full Redis transaction surface.
Language clients
The front-end uses StackExchange.Redis (.NET) as the reference and is validated for parity with the mainstream
Python (redis-py), Ruby (redis-rb), and Java (Jedis) clients across basic KV, expiry, counters,
keyspace notifications, transactions (the write-focused subset above), and Sentinel master discovery. Because the
endpoint is RESP2-only, a couple of clients need a specific version or connection style:
| Client | Status | Note |
|---|---|---|
| .NET — StackExchange.Redis | ✅ reference | All three failover models. |
| Java — Jedis (5.x) | ✅ works as-is | KV, keyspace notifications, transactions, and sentinelGetMasterAddrByName all native. |
Python — redis-py | ✅ use 5.x | redis-py 8+ assumes RESP3 at connect and fails against a RESP2 endpoint; pin to a 5.x release. redis.sentinel.Sentinel works natively. |
Ruby — redis-rb (6.x) | ✅ with one caveat | KV/notifications/transactions native. The gem's native Sentinel driver assumes RESP3; instead issue a raw SENTINEL get-master-addr-by-name and connect to the returned address. |
Otherwise connect exactly as you would to Redis, and choose one of the three failover models above.
See also
- Coming from Redis — mapping Redis commands to the native Zaris API.
- Data structures — the native hash/list/set/sorted-set API the RESP collection commands map onto.
- Pub/sub — native publish/subscribe behind
SUBSCRIBE/PUBLISH. - Streams — native Streams behind the RESP
X*commands. - Clustron vs Redis — the wider positioning of the two systems.
- Client resilience patterns — retries, timeouts, and failure handling on the native client.