Main API for using Redis Cluster.
For a higher-level guide, see the Cluster guide.
The design of this module follows the same low-level philosophy as the rest of Redix:
it builds on existing Redix connections (one per cluster node), uses ETS for fast slot
lookups, and mostly mirrors the Redix API.
Usage
You can start connections to a Redis Cluster similarly to how you'd start a single
Redix connection, but specifying a list of seed nodes and a name:
{:ok, cluster} = Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000"]
)
Redix.Cluster.command(:my_cluster, ["SET", "mykey", "myvalue"])
#=> {:ok, "OK"}
Redix.Cluster.command(:my_cluster, ["GET", "mykey"])
#=> {:ok, "myvalue"}Like single-node Redix connections, the cluster starts even if no seed node is
currently reachable: the topology is discovered in the background, retrying with
exponential backoff. Commands issued while the initial discovery attempt is in
flight wait for it to complete (just like a single Redix connection postpones
commands while it's connecting), so the common "start the cluster, issue a
command right away" pattern works without retries. If that first attempt fails,
commands return {:error, %Redix.ConnectionError{reason: :closed}} until a seed
node becomes reachable. Pass sync_connect: true to start_link/1 to block
until the topology has been discovered instead.
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"],
["SET", "key2", "b"],
["GET", "key1"],
["GET", "key2"]
])
#=> {: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:
Redix.Cluster.transaction_pipeline(:my_cluster, [
["SET", "{user:1}.name", "Alice"],
["SET", "{user:1}.email", "alice@example.com"]
])Reading from replicas
By default all commands are routed to primaries. To allow reads from replicas,
start the cluster with read_from_replicas: true (which opens and supervises a
connection to each replica, issuing READONLY on it) and pass a :route option
per call:
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 command/3 for the full list of :route values.
Limitations
- Only database
0is supported (Redis Cluster does not supportSELECT). - Pub/Sub is not supported through the cluster interface (yet).
- The
:noreply_*functions are not supported in cluster mode.
Telemetry
Redix.Cluster emits cluster-specific Telemetry events for topology changes
and redirections. See Redix.Telemetry for details.
Summary
Types
A node endpoint.
Functions
Returns a child spec for use in supervision trees.
Issues a command on the Redis Cluster.
Same as command/3 but raises on errors.
Issues a pipeline of commands on the Redis Cluster.
Same as pipeline/3 but raises on errors.
Starts a connection to a Redis Cluster.
Stops the cluster and all its connections.
Executes a MULTI/EXEC transaction on the Redis Cluster.
Same as transaction_pipeline/3 but raises on errors.
Types
@type endpoint() :: String.t() | [host: String.t(), port: :inet.port_number()]
A node endpoint.
It can be either a Redis URI string (such as "redis://localhost:7000")
or a keyword list with :host and :port keys
(such as [host: "localhost", port: 7000]).
Only the host and port of a seed are used to discover the cluster, with one
exception: the rediss:// scheme enables TLS for every node connection.
Credentials (user:pass@) and a non-zero database in a seed URI are not
supported and raise. See start_link/1.
Functions
@spec child_spec(keyword()) :: Supervisor.child_spec()
Returns a child spec for use in supervision trees.
Examples
children = [
{Redix.Cluster, name: :my_cluster, nodes: ["redis://localhost:7000"]}
]
@spec command(atom(), Redix.command(), keyword()) :: {:ok, Redix.Protocol.redis_value()} | {:error, atom() | Redix.Error.t() | Redix.ConnectionError.t()}
Issues a command on the Redis Cluster.
The command is routed to the correct node based on the key's hash slot.
If the command has no key (PING), it is sent to a random node.
Returns {:ok, response} or {:error, reason}.
Options
:timeout- request timeout in milliseconds. Defaults to5_000.:route- where to send the command. One of::primary(default) - always route to the slot's primary.:replica- route to a replica for the slot, failing with a connection error if none is reachable.:prefer_replica- route to a replica if one is reachable, otherwise fall back to the primary.
:replicaand:prefer_replicarequire the cluster to have been started withread_from_replicas: true. Use them only for reads: a write routed to a replica is transparently redirected (viaMOVED) to the primary. Keyless commands, such asPINGorINFO, always go to a random primary, even when called withroute: :replica | :prefer_replica.
Examples
Redix.Cluster.command(:my_cluster, ["SET", "mykey", "foo"])
#=> {:ok, "OK"}
Redix.Cluster.command(:my_cluster, ["GET", "mykey"])
#=> {:ok, "foo"}
Redix.Cluster.command(:my_cluster, ["GET", "mykey"], route: :prefer_replica)
#=> {:ok, "foo"}
@spec command!(atom(), Redix.command(), keyword()) :: Redix.Protocol.redis_value()
Same as command/3 but raises on errors.
@spec pipeline(atom(), [Redix.command()], keyword()) :: {:ok, [Redix.Protocol.redis_value()]} | {:error, atom() | Redix.Error.t() | Redix.ConnectionError.t()}
Issues a pipeline of commands on the Redis Cluster.
Commands are grouped by target node based on key hash slots, sent in parallel to the respective nodes, and results are reassembled in the original order.
Returns {:ok, results} or {:error, reason}.
If commands is an empty list ([]) then an ArgumentError exception is
raised right away, matching Redix.pipeline/3.
Partial failures across nodes
When a pipeline spans multiple nodes and only some of those nodes fail (for
example, it times out or its task crashes), the call still returns {:ok, results}. The positions of the commands routed to a failed node are filled
with the relevant error value (a %Redix.ConnectionError{}, or a
%Redix.Error{} for a Redis-level error) at their original indices, while the
results from nodes that succeeded stay visible. This extends Redix's "errors
are values" philosophy to the cross-node connection-error case, so a failure
affecting one node no longer discards the work that committed on the others.
This is a deliberate extension of Redix.pipeline/3, which fails the whole call
with {:error, reason} on a connection error. There is no partial-failure analog
for a single node, so a pipeline that targets a single node (all keys in one slot
group) still returns {:error, reason} on a connection error, matching
Redix.pipeline/3.
Options
:timeout- request timeout in milliseconds. Defaults to5_000.:route- where to send the pipeline. Seecommand/3for the accepted values. The option applies to the whole pipeline: every command uses the same routing choice.
Examples
Redix.Cluster.pipeline(:my_cluster, [["SET", "a", "1"], ["SET", "b", "2"]])
#=> {:ok, ["OK", "OK"]}
@spec pipeline!(atom(), [Redix.command()], keyword()) :: [ Redix.Protocol.redis_value() ]
Same as pipeline/3 but raises on errors.
@spec start_link(keyword()) :: Supervisor.on_start()
Starts a connection to a Redis Cluster.
Options
These are cluster-specific options:
:name(atom/0) - Required. An atom to register the cluster process under. All internal resources (ETS tables, Registry, connection supervisor) are named deterministically based on this name. You use this name to issue commands:Redix.Cluster.start_link(name: :my_cluster, nodes: [...]) Redix.Cluster.command(:my_cluster, ["GET", "key"]):nodes(non-empty list ofendpoint/0) - Required. A non-empty list of seed nodes to connect to. Only one reachable node is needed: the full cluster topology is discovered automatically viaCLUSTER SLOTS.:sync_connect(boolean/0) - Whether to discover the initial cluster topology before or afterstart_link/1returns. Whenfalse(the default),start_link/1returns right away and the topology is fetched in the background, retrying with exponential backoff (driven by the:backoff_initialand:backoff_maxoptions) until a seed node answers. Commands issued while the initial discovery attempt is in flight wait for it to complete (up to their:timeout); once that attempt fails, they return{:error, %Redix.ConnectionError{reason: :closed}}until a seed node becomes reachable. Whentrue,start_link/1blocks until the topology has been fetched, and returns an error if no seed node is reachable. This mirrors the:sync_connectoption ofRedix.start_link/1. The default value isfalse.:topology_refresh_interval(timeout/0) - How often (in milliseconds) to refresh the cluster topology. The default value is30000.:read_from_replicas(boolean/0) - iftrue, the cluster also opens (and supervises) a connection to every replica node discovered viaCLUSTER SLOTS, issuingREADONLYon each so it can serve reads. This is required to useroute: :replicaorroute: :prefer_replica(seecommand/3). Defaults tofalse, in which case only primaries are connected. The default value isfalse.
All other standard Redix connection options (:password, :ssl, :socket_opts,
:timeout, and so on) are passed through to each underlying node connection. The
exceptions are :sentinel and :exit_on_disconnection, which are not supported in
cluster mode (the cluster supervises node connections and handles disconnections
itself).
TLS and credentials in seed URIs
The full topology is discovered from a seed node, and every node connection (the seeds and the nodes discovered from them) is made with a single shared configuration. Only the host and port of a seed are used to reach it, so the other parts of a seed URI are handled specially rather than silently dropped:
The
rediss://scheme enables TLS for every node connection (equivalent tossl: true), since TLS in a cluster is all-or-nothing. Combining arediss://seed with an explicitssl: falseraises an error, so arediss://seed is never silently downgraded to plain TCP.Credentials in a seed URI (
redis://user:pass@host) raise an error: every node is authenticated with the shared configuration, so pass:usernameand:passwordas connection options instead.A non-zero database in a seed URI raises an error, as a cluster only supports database
0.
Examples
Redix.Cluster.start_link(
name: :my_cluster,
nodes: ["redis://localhost:7000", "redis://localhost:7001"]
)
Redix.Cluster.start_link(
name: :my_cluster,
nodes: [[host: "redis1.example.com", port: 6379]],
password: "secret"
)
Stops the cluster and all its connections.
Examples
Redix.Cluster.stop(:my_cluster)
@spec transaction_pipeline(atom(), [Redix.command()], keyword()) :: {:ok, [Redix.Protocol.redis_value()]} | {:error, atom() | Redix.Error.t() | Redix.ConnectionError.t()}
Executes a MULTI/EXEC transaction on the Redis Cluster.
All commands must target the same hash slot (use hash tags to ensure this).
Returns {:error, %Redix.Error{message: "CROSSSLOT" <> _}} if commands
span multiple slots. At least one command must contain a key to route the
transaction on; a pipeline of only keyless commands (such as PING) returns
an error since there's no slot to send it to.
Transactions always run on the slot's primary; passing route: anything other
than :primary raises an ArgumentError.
Like command/3 and pipeline/3, a "transaction" follows MOVED/ASK
redirections: since all commands target one slot, a redirect means the whole
transaction belongs on another node, so the entire MULTI/EXEC is re-run
there (bounded by the internal redirection limit). A MOVED also triggers a
reactive topology refresh, so a transaction-only workload self-heals after a
failover instead of failing until the next periodic refresh.
Options
:timeout- request timeout in milliseconds. Defaults to5_000.
Examples
Redix.Cluster.transaction_pipeline(:my_cluster, [
["SET", "{user:1}.name", "Alice"],
["SET", "{user:1}.email", "alice@example.com"]
])
#=> {:ok, ["OK", "OK"]}
@spec transaction_pipeline!(atom(), [Redix.command()], keyword()) :: [ Redix.Protocol.redis_value() ]
Same as transaction_pipeline/3 but raises on errors.