Beethoven.DistrServer behaviour (Beethoven v0.3.9)

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.

Supports all GenServer logic and callbacks except for init/1. Use of this callback directly will cause unexpected errors and behaviour. The entry point for the DistrServer is entry_point/1. entry_point/1 is identical to init/1 in terms of both input and return types.

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'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

Breakdown

  • use DistrServer, subscribe?: boolean() -> This implements the DistrServer behaviour and mnesia tool functions.

    • :subscribe? Utilizing this required parameter will tell the compiler if the DistrServer should automatically subscribe to the Mnesia table it is entangled with. Subscribing to the table will copy it to the local memory of the node running this DistrServer.
  • DistrServer.start_link/1 -> Similar to GenServer.start_link/1. Supports all the same arguments and arty.

  • Callback: config/0 -> Provides the configuration that will be used by the DistrServer's Mnesia table. See the section below on config/0 for more information

  • Callback: create_action/1 -> Logic ran if the DistrServer has to create the Mnesia table. This callback is optional, but with it, you can pre-fill the mnesia table with data as needed. If the DistrServer boots and the table already exists in the cluster, this function will not be called.

  • Callback: entry_point/1 -> Similar to GenServer.init/1. Supports all the same arguments and arty. With DistrServer, the init/1 callback for GenServers is used in the creation of the Mnesia table.


config/0

config/0 is a callback that requires the returns of a specific map.

Example

  def config() do
  %{
    tableName: module() | atom(),
    columns: list(atom()),
    indexes: list(atom()),
    dataType: :set | :ordered_set | :bag | :duplicate_bag,
    copyType: :multi | :local
  }
end
  • :tableName The name that will be used when creating the Mnesia table.

  • :columns The columns that will be used to create the Mnesia table. The first item in the list will be considered the key for the record

  • :indexes Defines additional columns that need to be indexed. By default, the key for the record is indexed. Adding the key column to the list will result in an error for Mnesia.

  • :dataType This is the type of Mnesia table we are creating. The types allowed are the same ones supported by Mnesia.

  • :copyType This defines if the DistrServer should copy the table to the local memory of the node. By default, if the DistrServer creates the Mnesia table, it will always be saved to memory. This option comes into effect when the DistrServer is joining an existing cluster where this role is already hosted. If your DistrServer is set to subscribe to the mnesia table, it will be copied to memory as is required by Mnesia. If you are not subscribing, your options are :local and :multi.

    • :local will only keep the table in the memory of the original node. This mode is dangerous as losing the original node will result in both data loss, and potential corruption. In the event of a failover in this mode, the failing over DistrServer will not run create_action/1 as the table will be marked as created.

    • :multi will copy the table to the local memory of any node running the DistrServer. This mode is recommended for most use cases. Please check into how Mnesia operations (transaction or dirty) work so you use this most efficiently. Failure to do so may lead to poor write performance.

Summary

Types

Copy options for the Mnesia table.

Configuration for the DistrServer instance.

Callbacks

-Callback required-

-Callback required-

-Callback required-

Functions

Sends a cast to the provided DistrServer. Similar to GenServer.call/2 and GenServer.call/3

Sends a cast to the provided DistrServer. Similar to GenServer.cast/2

Converts the DistrConfig() into the tableConfig() type.

Starts a DistrServer process under the supervisor tree. Similar to GenServer.start_link/2 and GenServer.start_link/3

Types

copyTypes()

@type copyTypes() :: :local | :multi

Copy options for the Mnesia table.

  • :local -> Copies are only on the table-creating-node.
  • :multi -> Copies are pushed to ALL nodes in the cluster.

distrConfig()

@type distrConfig() :: %{
  tableName: atom(),
  columns: [atom()],
  indexes: [atom()],
  dataType: atom(),
  copyType: copyTypes()
}

Configuration for the DistrServer instance.

  • :tableName -> Atomic name for the table.
  • :columns -> List of atoms representing the names of columns in the name.
  • :indexes -> List of table columns that should be indexed. Note: Indexing a column will slow writes to it, but make read operations consistent regardless of the table's size.
  • :dataType -> Data type for the Mnesia table.
  • :copyType -> How the new table will be copied across the Beethoven cluster.

Callbacks

config()

@callback config() :: distrConfig()

-Callback required-

DistrServer configuration. See distrConfig() type for more information on the return.

create_action(tableConfig)

@callback create_action(tableConfig :: Beethoven.MnesiaTools.tableConfig()) :: :ok

-Callback required-

Callback that is triggered when the process creates the Mnesia Table for the cluster.

entry_point(var)

@callback entry_point(var :: any()) ::
  {:ok, state :: any()}
  | {:ok, state :: any(),
     timeout() | :hibernate | {:continue, continue_arg :: term()}}
  | :ignore
  | {:stop, reason :: term()}

-Callback required-

Entry point for the DistrServer process. Similar to init/1 for GenServers.

Functions

call(server, request, timeout \\ 5000)

@spec call(GenServer.server(), any(), timeout()) :: any()

Sends a cast to the provided DistrServer. Similar to GenServer.call/2 and GenServer.call/3

cast(server, request)

@spec cast(GenServer.server(), any()) :: :ok

Sends a cast to the provided DistrServer. Similar to GenServer.cast/2

distr_to_table_conf(distrConfig)

@spec distr_to_table_conf(distrConfig()) :: Beethoven.MnesiaTools.tableConfig()

Converts the DistrConfig() into the tableConfig() type.

start_link(module, init_args, options \\ [])

@spec start_link(module(), any(), GenServer.options()) :: GenServer.on_start()

Starts a DistrServer process under the supervisor tree. Similar to GenServer.start_link/2 and GenServer.start_link/3