Redix supports Redis Cluster through the Redix.Cluster module.
Overview
Redis Cluster distributes data across multiple Redis nodes using 16384 hash slots. Each primary (master) node is responsible for a subset of these slots. Redix.Cluster takes care of these things transparently:
- Routing commands to the correct node based on key hash slots.
- Handling
MOVEDandASKredirections during resharding. - Maintaining a topology map of the cluster.
- Splitting pipelines across nodes and reassembling results.
Getting Started
The API for Redix.Cluster will feel familiar to folks used to Redix.
# Start a cluster connection with a name and one or more seed nodes.
{:ok, _pid} =
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000", "redis://localhost:7001"]
)
# Issue commands across the cluster.
Redix.Cluster.command(:my_cluster, ["SET", "mykey", "myvalue"])
#=> {:ok, "OK"}
Redix.Cluster.command(:my_cluster, ["GET", "mykey"])
#=> {:ok, "myvalue"}Startup Behavior
Like single-node Redix connections, starting a cluster does not require any node to be reachable. By default, Redix.Cluster.start_link/1 returns right away and discovers the cluster topology in the background, retrying with exponential backoff (controlled by the :backoff_initial and :backoff_max options) until a seed node answers. This means a Redix.Cluster in your supervision tree won't crash-loop your application at boot if Redis comes up after your app.
Commands issued while the initial discovery attempt is in flight wait for it to complete (up to their :timeout), just like a single Redix connection postpones commands while it's connecting (so starting a cluster and issuing commands right away works without retries). Once that first attempt fails (no seed node is reachable), commands stop waiting and return an error until a node becomes reachable.
If you'd prefer to block until the topology has been discovered—and fail fast if no seed node is reachable—pass sync_connect: true:
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000"],
sync_connect: true
)Connection Pools
By default, Redix opens one connection to each node. Set :primary_pool_size to open
more connections to each primary:
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000"],
primary_pool_size: 5
)A larger pool can help when many processes send commands to the same node. This can occur with a serverless or proxy service that sends all commands through one endpoint.
Replica pools have a separate size. This avoids opening a large number of connections to every replica when only primary traffic needs more capacity:
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000"],
primary_pool_size: 5,
replica_pool_size: 2,
read_from_replicas: true
)During normal routing, Redix uses the caller process to select a pool member. One caller process uses the same member for a node while that member is available. Redirects keep the original caller choice. If the selected member is unavailable, Redix uses another live member. A workload with only a few caller processes might not use all members.
Pipelines
Pipelines that span multiple hash slots are transparently split across nodes, executed in parallel, and reassembled in the original order:
Redix.Cluster.pipeline(:my_cluster, [
["SET", "key1", "a"], # Maybe executes on node 1
["SET", "key2", "b"], # Maybe executes on node 2
["GET", "key1"], # Same, node 1
["GET", "key2"] # Same, node 2
])
#=> {:ok, ["OK", "OK", "a", "b"]}Transactions
MULTI/EXEC transactions require all keys to be in the same hash slot. Use hash tags to ensure this:
# These keys all hash to the same slot because only the "{user:1}" part of the key is hashed:
Redix.Cluster.transaction_pipeline(:my_cluster, [
["SET", "{user:1}.name", "Alice"],
["SET", "{user:1}.email", "alice@example.com"]
])
#=> {:ok, ["OK", "OK"]}If commands span multiple slots, a CROSSSLOT error is returned.
Redirections
Redis Cluster handles slot migrations transparently:
MOVED: the slot has permanently moved to another node. Redix updates its topology map and retries the command on the new node.ASK: the slot is being migrated. Redix sendsASKINGfollowed by the command to the target node without updating the topology map.
Up to five redirections are followed before returning an error.
Reading from Replicas
By default, all commands are routed to primary nodes. To read from replicas, start the cluster with read_from_replicas: true and pass the :route option to Redix.Cluster.command/3 or Redix.Cluster.pipeline/3:
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000"],
read_from_replicas: true
)
# Read from a replica for the key's slot, failing if none is reachable.
Redix.Cluster.command(:my_cluster, ["GET", "mykey"], route: :replica)
# Prefer a replica but fall back to the primary if none is reachable.
Redix.Cluster.command(:my_cluster, ["GET", "mykey"], route: :prefer_replica)See the Redix.Cluster module documentation for details and present limitations.