# Redis Cluster

Redix supports [Redis Cluster](https://redis.io/technology/redis-enterprise-cluster-architecture/) through the `Redix.Cluster` module.

## Overview

Redis Cluster distributes data across multiple Redis nodes [using **16384 hash slots**](https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/#key-distribution-model). 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 `MOVED` and `ASK` redirections 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`.

```elixir
# 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`:

```elixir
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:

```elixir
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:

```elixir
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:

```elixir
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](https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/#hash-tags) to ensure this:

```elixir
# 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 sends `ASKING` followed 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`:

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