# `Redix.Cluster`
[🔗](https://github.com/whatyouhide/redix/blob/v1.8.0/lib/redix/cluster.ex#L1)

Main API for using [Redis Cluster](https://redis.io/docs/management/scaling/).

For a higher-level guide, see the [Cluster guide](cluster.html).

The design of this module follows the same low-level philosophy as the rest of Redix:
it builds on existing `Redix` connections, uses ETS for fast slot lookups, and mostly
mirrors the `Redix` API. By default it opens one connection per cluster node, but that's
tunable via options.

## 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, but if that first attempt fails,
commands return an error 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](https://redis.io/docs/reference/cluster-spec/#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
connections to each replica, issuing `READONLY` on them) 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 `0` is supported (Redis Cluster does not support `SELECT`).
  * Pub/Sub is not supported through the cluster interface.
  * 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.

# `endpoint`
*since 1.6.0* 

```elixir
@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`.

# `child_spec`
*since 1.6.0* 

```elixir
@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"]}
    ]

# `command`
*since 1.6.0* 

```elixir
@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 to `5_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.

    `:replica` and `:prefer_replica` require the cluster to have been started with
    `read_from_replicas: true`. Use them only for reads: a write routed to a replica
    is transparently redirected (via `MOVED`) to the primary. Keyless commands, such
    as `PING` or `INFO`, always go to a random primary, even when called with
    `route: :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"}

# `command!`
*since 1.6.0* 

```elixir
@spec command!(atom(), Redix.command(), keyword()) :: Redix.Protocol.redis_value()
```

Same as `command/3` but raises on errors.

# `pipeline`
*since 1.6.0* 

```elixir
@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 to `5_000`.

  * `:route` - where to send the pipeline. See `command/3` for 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"]}

# `pipeline!`
*since 1.6.0* 

```elixir
@spec pipeline!(atom(), [Redix.command()], keyword()) :: [
  Redix.Protocol.redis_value()
]
```

Same as `pipeline/3` but raises on errors.

# `start_link`
*since 1.6.0* 

```elixir
@spec start_link(keyword()) :: Supervisor.on_start()
```

Starts a connection to a Redis Cluster.

## Options

These are cluster-specific options:

* `:name` (`t:atom/0`) - Required. An atom to register the cluster process under. All internal resources 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 of `t:endpoint/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 via `CLUSTER SLOTS`.

* `:sync_connect` (`t:boolean/0`) - Whether to discover the initial cluster topology *before* or *after*
  `start_link/1` returns. When `false` (the default), `start_link/1` returns right
  away and the topology is fetched in the background, retrying with exponential
  backoff (driven by the `:backoff_initial` and `:backoff_max` options) 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. When `true`, `start_link/1` blocks until the topology has been
  fetched, and returns an error if no seed node is reachable. This mirrors the
  `:sync_connect` option of `Redix.start_link/1`. The default value is `false`.

* `:topology_refresh_interval` (`t:timeout/0`) - How often (in milliseconds) to refresh the cluster topology. The default value is `30000`.

* `:primary_pool_size` (`t:pos_integer/0`) - The number of connections to open to **each** primary node in the cluster. During
  normal routing, commands from one caller process use the same connection in a node's
  pool while that connection is available. Redirects keep the original caller choice.
  If the selected connection is unavailable, Redix uses another live pool member.
  Workloads with few caller processes might not use all pool members. Redis is
  single-threaded anyways, so use this only if it makes sense for your workload (and
  possibly after benchmarking!). *Available since 1.7.0*. The default value is `1`.

* `:replica_pool_size` (`t:pos_integer/0`) - The number of connections to open to **each** replica node when `read_from_replicas: true`
  is set. This is independent from `:primary_pool_size`, because primary and replica
  traffic can need different pool sizes. Redis is single-threaded anyways, so use
  this only if it makes sense for your workload (and possibly after benchmarking!).
  *Available since 1.7.0*. The default value is `1`.

* `:read_from_replicas` (`t:boolean/0`) - If `true`, the cluster also opens (and supervises) `:replica_pool_size`
  connections to every replica node discovered via `CLUSTER SLOTS`, issuing
  `READONLY` on each so it can serve reads. This is required to use
  `route: :replica` or `route: :prefer_replica` (see `command/3`). Defaults to
  `false`, in which case only primaries are connected. The default value is `false`.

* `:address_mapper` - A `(host, port -> {host, port})` function applied to every node address the
  cluster announces, both in `CLUSTER SLOTS` replies and in `MOVED`/`ASK`
  redirections, before connecting to it. Seed `:nodes` are dialed as given. Use
  this when the addresses the cluster advertises are not reachable from the
  client, such as a cluster running in Docker that announces container IPs, or
  a NAT'd deployment:

      address_mapper: fn _host, port -> {"127.0.0.1", port} end

  *Available since 1.8.0.*

  The default value is `nil`.

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).

## Resources

Each cluster uses:

  * Two ETS tables.
  * One `Registry` (which starts another ETS table).
  * `:primary_pool_size` connections for each primary node.
  * `:replica_pool_size` connections for each replica node when
    `read_from_replicas: true` is set.

## 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
    to `ssl: true`), since TLS in a cluster is all-or-nothing. Combining a
    `rediss://` seed with an explicit `ssl: false` raises an error,
    so a `rediss://` 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 `:username`
    and `:password` as 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"
    )

# `stop`
*since 1.6.0* 

```elixir
@spec stop(atom(), timeout()) :: :ok
```

Stops the cluster and all its connections.

## Examples

    Redix.Cluster.stop(:my_cluster)

# `transaction_pipeline`
*since 1.6.0* 

```elixir
@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 to `5_000`.

## Examples

    Redix.Cluster.transaction_pipeline(:my_cluster, [
      ["SET", "{user:1}.name", "Alice"],
      ["SET", "{user:1}.email", "alice@example.com"]
    ])
    #=> {:ok, ["OK", "OK"]}

# `transaction_pipeline!`
*since 1.6.0* 

```elixir
@spec transaction_pipeline!(atom(), [Redix.command()], keyword()) :: [
  Redix.Protocol.redis_value()
]
```

Same as `transaction_pipeline/3` but raises on errors.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
