lib/redix_clustered_clone.ex

defmodule RedixClusteredClone do
  @moduledoc """
  Wrapper on top of RedixClustered to clone writes to a separate cluster
  """

  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, opts, name: supervisor_name(opts))
  end

  def init(opts) do
    {:ok, clone} = Keyword.fetch(opts, :clone)

    clone_opts =
      clone
      |> Keyword.put(:name, cloned_cluster_name(opts))
      |> Keyword.put_new(:prefix, Keyword.get(opts, :prefix))

    children = [
      %{id: :primary, start: {RedixClustered, :start_link, [opts]}},
      %{id: :clone, start: {RedixClustered, :start_link, [clone_opts]}}
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end

  def supervisor_name(opts), do: :"#{RedixClustered.cluster_name(opts)}_super"

  def cloned_cluster_name(o) when is_list(o), do: cloned_cluster_name(Keyword.get(o, :name))
  def cloned_cluster_name(nil), do: :clone
  def cloned_cluster_name(name), do: :"#{name}_clone"

  # pass through direct calls
  defdelegate command(cmd), to: RedixClustered
  defdelegate command(name, cmd), to: RedixClustered
  defdelegate pipeline(cmds), to: RedixClustered
  defdelegate pipeline(name, cmds), to: RedixClustered

  # pass through read commands
  defdelegate get(key), to: RedixClustered
  defdelegate get(name, key), to: RedixClustered
  defdelegate get_ttl(key), to: RedixClustered
  defdelegate get_ttl(name, key), to: RedixClustered
  defdelegate ttl(key), to: RedixClustered
  defdelegate ttl(name, key), to: RedixClustered
  defdelegate pttl(key), to: RedixClustered
  defdelegate pttl(name, key), to: RedixClustered
  defdelegate hgetall(key), to: RedixClustered
  defdelegate hgetall(name, key), to: RedixClustered
  defdelegate scan(pattern), to: RedixClustered
  defdelegate scan(name, pattern), to: RedixClustered

  # duplicate write commands to clone cluster
  def expire(key, ttl), do: expire(nil, key, ttl)

  def expire(name, key, ttl) do
    RedixClustered.expire(cloned_cluster_name(name), key, ttl)
    RedixClustered.expire(name, key, ttl)
  end

  def pexpire(key, ttl), do: pexpire(nil, key, ttl)

  def pexpire(name, key, ttl) do
    RedixClustered.pexpire(cloned_cluster_name(name), key, ttl)
    RedixClustered.pexpire(name, key, ttl)
  end

  def set(key, val), do: set(nil, key, val)

  def set(name, key, val) do
    RedixClustered.set(cloned_cluster_name(name), key, val)
    RedixClustered.set(name, key, val)
  end

  def setex(key, ttl, val), do: setex(nil, key, ttl, val)

  def setex(name, key, ttl, val) do
    RedixClustered.setex(cloned_cluster_name(name), key, ttl, val)
    RedixClustered.setex(name, key, ttl, val)
  end

  def psetex(key, ttl, val), do: psetex(nil, key, ttl, val)

  def psetex(name, key, ttl, val) do
    RedixClustered.psetex(cloned_cluster_name(name), key, ttl, val)
    RedixClustered.psetex(name, key, ttl, val)
  end

  def setnx_expire_get(key, ttl, val), do: setnx_expire_get(nil, key, ttl, val)

  def setnx_expire_get(name, key, ttl, val) do
    RedixClustered.setnx_expire_get(cloned_cluster_name(name), key, ttl, val)
    RedixClustered.setnx_expire_get(name, key, ttl, val)
  end

  def del(key), do: del(nil, key)

  def del(name, key) do
    RedixClustered.del(cloned_cluster_name(name), key)
    RedixClustered.del(name, key)
  end

  def hincrby(key, flds) when is_map(flds), do: hincrby(nil, key, flds)
  def hincrby(key, flds, ttl) when is_map(flds), do: hincrby(nil, key, flds, ttl)
  def hincrby(key, "" <> fld, num), do: hincrby(nil, key, fld, num)

  def hincrby(name, key, flds) do
    RedixClustered.hincrby(cloned_cluster_name(name), key, flds)
    RedixClustered.hincrby(name, key, flds)
  end

  def hincrby(name, key, flds, ttl) do
    RedixClustered.hincrby(cloned_cluster_name(name), key, flds, ttl)
    RedixClustered.hincrby(name, key, flds, ttl)
  end

  def nuke(pattern), do: nuke(nil, pattern)

  def nuke(name, pattern) do
    RedixClustered.nuke(cloned_cluster_name(name), pattern)
    RedixClustered.nuke(name, pattern)
  end
end