# Strategies

A strategy maps keys to shard names. Configure it with `strategy:`.

| Strategy | Use when |
| --- | --- |
| `Shardex.Strategy.Hash` (default) | fixed shard count |
| `Shardex.Strategy.JumpHash` | the shard count grows: appending a shard moves only ~1/n keys |
| `{Shardex.Strategy.Static, map: %{key => shard}, default: shard}` | small fixed maps, tests |
| your module or function | assignments live elsewhere (e.g. a database) |
| `{Shardex.Strategy.Cached, inner: ..., ttl: ms}` | cache an expensive strategy |

Hash strategies hash over the **full** shard list, so a shard in maintenance
returns `{:error, :maintenance}` for its keys instead of remapping them.

## Writing a strategy

Only `route_many/2` is required. Return `{:ok, shard}` or `{:error, reason}`
per key; keys you leave out are `{:error, :unassigned}`. Shardex validates the
shard names you return (unknown names, including strings, become
`{:error, {:unknown_shard, name}}`).

```elixir
defmodule MyApp.ShardDirectory do
  @behaviour Shardex.Strategy

  @impl true
  def route_many(org_ids, ctx) do
    known = Map.new(ctx.shards, &{Atom.to_string(&1), &1})

    MyApp.Metadata.shard_names_for(org_ids)   # %{org_id => "shard_1"}
    |> Map.new(fn {org_id, name} ->
      case Map.fetch(known, name) do
        {:ok, shard} -> {org_id, {:ok, shard}}
        :error -> {org_id, {:error, {:unknown_shard, name}}}
      end
    end)
  end
end

strategy: {Shardex.Strategy.Cached, inner: MyApp.ShardDirectory, ttl: :timer.minutes(10)}
```

Optional callbacks: `init/2` (build state, runs in the Coordinator),
`route/2` (a faster single-key path), `on_topology_change/2`, `trace?/0`.

A function works too: `strategy: fn key, ctx -> {:ok, hd(ctx.shards)} end`.

When an assignment changes, call `MyApp.Shards.invalidate(org_id)` **after**
the change is visible to the inner strategy (e.g. after the directory
transaction commits). Invalidation is not atomic with lookups: a lookup that
missed the cache and queried the inner strategy just before the change can
write the old assignment back after `invalidate`, and it stays cached until
`:ttl` expires. Pick a `:ttl` that bounds how long a stale route is
acceptable, or move keys only while their shard is drained.
