Beethoven

A Decentralized failover and peer-to-peer node finder for Elixir. Allows Elixir nodes to find each other automatically. Once connected, they can coordinate to delegate roles and tasks between the nodes in the cluster. Written using only the Elixir and Erlang standard library.

How to use

Within mix.exs, add the below to deps/0.

defp deps do
    [
        ...
        {:beethoven, "~> 0.0"}
        ...
    ]
end

Add :beethoven to :extra_applications for your application/0 config within mix.exs.

def application do
[
    mod: {..., []},
    extra_applications: [:logger, :runtime_tools, :beethoven]
]
end

Add dependencies via mix in a shell session.

$> mix Deps.get

Then within config.exs, or your preferred config file, input the below config.

# Configuration for the Beethoven stack
config :beethoven,
  use_az_net: false,
  cluster_net: "127.0.0.0",
  cluster_net_mask: "29",
  listener_port: 3000,
  common_random_backoff: 150..300,
  roles: [
    # {<AtomName>, <Module>, <Initial Args>, <InstanceCount>}
    {:test, Beethoven.TestRole, [arg1: "arg1"], 2},
    {:testd, Beethoven.TestdRole, [arg1: "arg1"], 2}
  ]

This configuration uses the 2 test servers, Beethoven.TestRole and Beethoven.TestdRole.

Lets break down the configuration as a whole:

Role definition

Defining a role for Beethoven to track uses this following schema:

# {<AtomName>, <Module>, <Initial Args>, <InstanceCount>}
{:test, Beethoven.TestRole, [arg1: "arg1"], 2}

Notes on InstanceCount

Now, once you start your Mix Application, your role should spawn once Beethoven.RoleServer has initialized.

Features

DistrServer

Distr(ibuted)Server is a modified GenServer that allows for seamless integration with a dedicated Mnesia table. This was specially built for operation in a Beethoven environment.

The idea is that the brain of the GenServer can be set with Mnesia and not the GenServer's internal state. This allows for the compute and state of the GenServer to be distributed across the Beethoven cluster.

Example

defmodule Test do

  use DistrServer, subscribe?: true

  # Standard OTP entry point for starting the PID.
  def start_link(init_args) do
    DistrServer.start_link(__MODULE__, init_args, name: __MODULE__)
  end

  # The configuration for the DistrServer&#39;s Mnesia table.
  @impl true
  def config() do
    %{
      tableName: TestTracker,
      columns: [:col1, :col2, :last_change],
      indexes: [],
      dataType: :set,
      copyType: :multi
    }
  end

  # This is ran when the table is newly created.
  @impl true
  def create_action(_tableConfig) do
    ...
    :ok
  end

  # Similar to GenServer.init/1.
  @impl true
  def entry_point(_init_args) do
    ...
    {:ok, :ok} # all init/1 returns are supported.
  end

end

See documentation for Beethoven.DistrServer for more information.

Allocator

A signal aggregator to determine the busyness of a given Beethoven node. By default, CPU, RAM, and Beethoven roles hosted are signaled for busyness.

These signals can be created using the signal() macro provided in Allocator.Agent

Example

def test_func() do
  # Returns the least-busy node in the beethoven cluster.
  # Wrapper for `Beethoven.Allocator.allocate()`
  nodeName = Beethoven.allocate()

  # Returns sorted list of all active Beethoven roles.
  # List is sorted from least-to-most busy.
  nodeList = Beethoven.Allocator.allocation_list()
end

Allocator.Agent

A behavior that provides a macro for just-in-time telemetry signal creation. Signals defined a :name, :weight, and :type value. These values combine together to create 0-1 arity functions to streamline sending signals to Allocator.

Example

defmodule Signals do
  use Beethoven.Allocator.Agent

  signal(name: :ram, weight: 10.0, type: :percent)
  signal(name: :http_requests, weight: 5.0, type: :count)
end

# Generates
Signals.percent_ram(data) # Data is now the value of the metric in `:ets`
Signals.increment_http_requests_count() # +1.0
Signals.decrement_http_requests_count() # -1.0

Any issues?

Please run your app with the environmental variable MIX_ENV set to dev, and create an issue on the Github repo for Beethoven.