View Source Setting up a Cluster

Horde doesn't provide functionality to set up your cluster, we recommend you use libcluster for this purpose.

There are three strategies you can use to integrate libcluster with Horde:

Automatic Cluster Membership

When starting a Horde.Registry or Horde.DynamicSupervisor, setting the members option to have a value of :auto will automate membership management. In this mode, all visible nodes will be initially added to the cluster. In addition, any new nodes that become visible will be automatically added and any nodes that shut down will be automatically removed.

Static Cluster Membership

If you will not be adding or removing members from the cluster dynamically, then you can set up libcluster and tell Horde about the members of your cluster. For example, if you run your cluster on bare metal hardware and have a fixed number of servers.

supervisor_members = [
  {MyHordeSupervisor, :node1},
  {MyHordeSupervisor, :node2},
  {MyHordeSupervisor, :node3},
  {MyHordeSupervisor, :node4}
]

registry_members = [
  {MyHordeRegistry, :node1},
  {MyHordeRegistry, :node2},
  {MyHordeRegistry, :node3},
  {MyHordeRegistry, :node4}
]

children = [
  {Horde.Registry, name: MyHordeRegistry, keys: :unique, members: registry_members},
  {Horde.DynamicSupervisor, name: MyHordeSupervisor, strategy: :one_for_one, members: supervisor_members},
  ...
]

This is the simplest approach. You tell Horde which members are supposed to be in the cluster, and if they are available, Horde will include them in the cluster.

Dynamic Cluster Membership

If you will be adding and removing nodes from your cluster constantly, and don't want to repackage your application every time you do this, then you will need to perform a couple of extra steps (assuming your needs cannot be met by the :auto setting).

In this scenario, you will need to implement a module-based Supervisor

defmodule MyHordeSupervisor do
  use Horde.DynamicSupervisor

  def start_link(_) do
    Horde.DynamicSupervisor.start_link(
      __MODULE__,
      [strategy: :one_for_one],
      name: __MODULE__
    )
  end

  def init(init_arg) do
    [members: members()]
    |> Keyword.merge(init_arg)
    |> Horde.DynamicSupervisor.init()
  end

  defp members() do
    [Node.self() | Node.list()]
    |> Enum.map(fn node -> {__MODULE__, node} end)
  end
end

Now every time MyHordeSupervisor gets started or restarted, it will compute the members based on the currently connected members.

In this scenario, you may also want to implement a module-based Registry

defmodule MyHordeRegistry do
  use Horde.Registry

  def start_link(_) do
    Horde.Registry.start_link(__MODULE__, [keys: :unique], name: __MODULE__)
  end

  def init(init_arg) do
    [members: members()]
    |> Keyword.merge(init_arg)
    |> Horde.Registry.init()
  end

  defp members() do
    [Node.self() | Node.list()]
    |> Enum.map(fn node -> {__MODULE__, node} end)
  end
end

Now every time MyHordeRegistry gets started or restarted, it will compute the members based on the currently connected members.

We also need a separate process that will listen for {:nodeup, node} and {:nodedown, node} events and adjust the members of the Horde cluster accordingly. Put this in your supervision tree underneath MyHordeSupervisor.

defmodule NodeListener do
  use GenServer

  def start_link(_), do: GenServer.start_link(__MODULE__, [])

  def init(_) do
    :net_kernel.monitor_nodes(true, node_type: :visible)
    {:ok, nil}
  end

  def handle_info({:nodeup, _node, _node_type}, state) do
    set_members(MyHordeRegistry)
    set_members(MyHordeSupervisor)
    {:noreply, state}
  end

  def handle_info({:nodedown, _node, _node_type}, state) do
    set_members(MyHordeRegistry)
    set_members(MyHordeSupervisor)
    {:noreply, state}
  end

  defp set_members(name) do
    members =
      [Node.self() | Node.list()]
      |> Enum.map(fn node -> {name, node} end)

    :ok = Horde.Cluster.set_members(name, members)
  end
end

Note that the funcionality provided in this example is essentially the same as the members: :auto setting, however setting it up yourself allows greater flexability to modify it if :auto mode doesn't meet your requirements.