Load Balancer Reference

Copy Markdown View Source

Complete API reference for all public modules in rpc_load_balancer.

RpcLoadBalancer

Top-level module providing RPC wrappers around :erpc.

Types

@spec call(node(), module(), atom(), [any()], timeout: timeout()) :: ErrorMessage.t_res(any())
@spec cast(node(), module(), atom(), [term()]) :: :ok | {:error, ErrorMessage.t()}

Functions

call(node, module, fun, args, opts \\ [timeout: :timer.seconds(10)])

Executes a synchronous RPC call on the given node. Wraps :erpc.call/5.

Returns:

  • {:ok, result} on success
  • {:error, %ErrorMessage{code: :request_timeout}} on timeout
  • {:error, %ErrorMessage{code: :service_unavailable}} on connection failure
  • {:error, %ErrorMessage{code: :bad_request}} on bad arguments

Options:

  • :timeout — call timeout in milliseconds (default: 10_000)

cast(node, module, fun, args)

Executes an asynchronous RPC cast on the given node. Wraps :erpc.cast/4.

Returns:

  • :ok on success
  • {:error, %ErrorMessage{}} on failure

RpcLoadBalancer.LoadBalancer

GenServer-based load balancer that registers nodes via :pg and selects them using a pluggable algorithm.

Types

@type name :: atom() | module()
@type node_match_list :: [String.t() | Regex.t()] | :all
@type option ::
        {:node_match_list, node_match_list()}
        | {:selection_algorithm, module()}
        | {:algorithm_opts, keyword()}
@type opts :: [GenServer.option() | option()]

Functions

start_link(opts \\ [])

Starts a load balancer GenServer.

Options:

  • :name — registered name for the balancer (auto-generated if omitted)
  • :selection_algorithm — module implementing SelectionAlgorithm (default: SelectionAlgorithm.Random)
  • :algorithm_opts — keyword list forwarded to the algorithm's init/2 callback (default: [])
  • :node_match_list — controls which nodes join the :pg group (default: :all)
    • :all — every node joins
    • [String.t() | Regex.t()] — only nodes matching at least one entry join

Returns: GenServer.on_start()

select_node(load_balancer_name, opts \\ [])

Selects a node from the balancer's registered members using the configured algorithm.

Options: forwarded to the algorithm's choose_from_nodes/3 (e.g., key: "user:123" for HashRing)

Returns:

  • {:ok, node()} on success
  • {:error, %ErrorMessage{code: :service_unavailable}} when no nodes are registered

release_node(load_balancer_name, node)

Decrements the connection counter for the given node. Only meaningful for connection-tracking algorithms (LeastConnections, PowerOfTwo). No-op for other algorithms.

Returns: :ok

call(load_balancer_name, module, fun, args, opts \\ [])

Selects a node, executes a synchronous RPC call, then releases the node.

Options:

  • :key — forwarded to the selection algorithm (used by HashRing)
  • :timeout — forwarded to RpcLoadBalancer.call/5

Returns: ErrorMessage.t_res(any())

cast(load_balancer_name, module, fun, args, opts \\ [])

Selects a node and executes an asynchronous RPC cast.

Options:

  • :key — forwarded to the selection algorithm

Returns: :ok | {:error, ErrorMessage.t()}

select_nodes(load_balancer_name, count, opts \\ [])

Selects multiple nodes from the balancer's registered members. Algorithms that implement choose_nodes/4 (e.g., HashRing) provide consistent multi-node selection. Others fall back to randomly shuffled nodes.

Options: forwarded to the algorithm's choose_nodes/4 (e.g., key: "user:123" for HashRing)

Returns:

  • {:ok, [node()]} — up to count distinct nodes
  • {:error, %ErrorMessage{code: :service_unavailable}} when no nodes are registered

get_members(load_balancer_name)

Returns the deduplicated list of nodes registered in the :pg group for this balancer.

Returns:

  • {:ok, [node()]} when members exist
  • {:error, %ErrorMessage{code: :service_unavailable}} when the group is empty

pg_group_name()

Returns the :pg scope atom used by all load balancers: :rpc_load_balancer.


RpcLoadBalancer.LoadBalancer.SelectionAlgorithm

Behaviour definition and dispatch layer for selection algorithms.

Callbacks

Required

@callback choose_from_nodes(load_balancer_name(), [node()], opts :: keyword()) :: node()

Called to pick one node from the available list. Receives the balancer name, the current node list, and any caller-provided options.

Optional

@callback init(load_balancer_name(), opts :: keyword()) :: :ok

Called once during balancer startup. Receives algorithm_opts from start_link/1.

@callback choose_nodes(load_balancer_name(), [node()], pos_integer(), opts :: keyword()) :: [node()]

Called by LoadBalancer.select_nodes/3 to pick multiple distinct nodes for a given key. Used for replica selection. Algorithms that don't implement this fall back to returning randomly shuffled nodes.

@callback on_node_change(load_balancer_name(), {:joined | :left, [node()]}) :: :ok

Called when the :pg group membership changes.

@callback release_node(load_balancer_name(), node()) :: :ok

Called after an RPC call completes to clean up per-node state.


Built-in Algorithms

All algorithms live under RpcLoadBalancer.LoadBalancer.SelectionAlgorithm.*.

Random

Picks a random node using Enum.random/1. No state, no configuration.

RoundRobin

Cycles through nodes using an atomic ETS counter (CounterCache). The counter auto-resets after 10,000,000 to prevent overflow.

LeastConnections

Tracks active connections per node with ETS counters. Always picks the node with the lowest count. Increments on selection, decrements on release_node/2.

Implements: init/2, choose_from_nodes/3, on_node_change/2, release_node/2

PowerOfTwo

Samples two random nodes and picks the one with fewer active connections. Same counter infrastructure as LeastConnections but with O(1) selection cost instead of O(n).

Implements: init/2, choose_from_nodes/3, on_node_change/2, release_node/2

HashRing

Consistent hash ring powered by libring. Each physical node is sharded into weight points (default: 128) distributed across a 2^32 continuum using SHA-256. Key lookup finds the next highest shard on the ring via gb_tree. Falls back to random selection when no key is given. The ring is stored in ETS and lazily rebuilt when topology changes.

Supports replica selection via choose_nodes/4 using HashRing.key_to_nodes/3 — returns multiple distinct nodes for a given key, walking the ring from the primary shard.

Algorithm options:

  • :weight — number of shards per physical node (default: 128)

Implements: init/2, choose_from_nodes/3, choose_nodes/4, on_node_change/2

WeightedRoundRobin

Expands the node list by duplicating each node according to its weight, then cycles through with an atomic counter. Weights are passed via algorithm_opts: [weights: %{node => integer}]. Nodes without an explicit weight default to 1.

Implements: init/2, choose_from_nodes/3


Internal Modules

These modules are not part of the public API but are documented here for contributors.

RpcLoadBalancer.LoadBalancer.Pg

Starts and wraps the :pg scope (:rpc_load_balancer). Started as a child of the application supervisor.

RpcLoadBalancer.LoadBalancer.AlgorithmCache

ETS cache (via elixir_cache) that maps load_balancer_name -> algorithm_module. Configured with read_concurrency: true and write_concurrency: true.

RpcLoadBalancer.LoadBalancer.CounterCache

ETS cache (via elixir_cache) used for atomic counters (round robin indices, connection counts) and weight storage. Configured with read_concurrency: true and write_concurrency: true.