An Elixir library for executing Remote Procedure Calls across distributed BEAM nodes with a built-in load balancer. It wraps Erlang's :erpc module with structured error handling and provides a pluggable node selection layer powered by OTP's :pg process groups.
Why this exists
Node.list/0 keeps crashed or unreachable nodes around until the net kernel's heartbeat notices, and any RPC routed there hangs until it times out. rpc_load_balancer selects targets from :pg process groups instead: when a node dies its group members vanish immediately, so the balancer only ever picks nodes with a live, registered process — and callers get {:error, %ErrorMessage{code: :service_unavailable}} rather than a silent timeout when nothing is available.
Features
- RPC wrappers —
call/5andcast/5around:erpcwithErrorMessageerror tuples - Distributed load balancer — automatic node discovery and registration via
:pg - Eight selection algorithms — Random, Round Robin, Weighted Round Robin, Least Connections, Power of Two, Hash Ring, Least CPU, Call Direct
- Named load balancer modules —
use RpcLoadBalancerbinds a configuration to a module with its owncall/5,cast/5,select_node/1, andchild_spec/1 - Custom algorithms — implement the
SelectionAlgorithmbehaviour to add your own - Node filtering — restrict which nodes join a balancer with string or regex patterns, plus filter-relative exclusions via
excluded_node_patterns - Connection tracking — lock-free
:countersfor connection-aware algorithms - Random-node helpers —
call_on_random_node/5andcast_on_random_node/5for name-based node filtering with built-in retry - Retry on no route — load-balanced calls and random-node helpers back off and retry when the pool is empty
- Graceful draining — in-flight call tracking and connection draining on shutdown
- Telemetry & metrics —
:telemetryspans on every call/cast, node-selection events, and ready-madeTelemetry.Metricsdefinitions inRpcLoadBalancer.Metrics
Installation
Add rpc_load_balancer to your list of dependencies in mix.exs:
def deps do
[
{:rpc_load_balancer, "~> 0.3"}
]
endQuick Start
# Direct RPC
{:ok, result} =
RpcLoadBalancer.call(
:"worker@host",
MyModule,
:some_fun,
["arg"],
timeout: :timer.seconds(5)
)
# Load-balanced RPC — the node argument is ignored, the balancer picks one
{:ok, _pid} = RpcLoadBalancer.start_link(name: :my_balancer)
{:ok, result} =
RpcLoadBalancer.call(node(), MyModule, :my_fun, [arg], load_balancer: :my_balancer)Or bind a configuration to a module:
defmodule MyApp.LoadBalancer do
use RpcLoadBalancer,
selection_algorithm: RpcLoadBalancer.LoadBalancer.SelectionAlgorithm.RoundRobin
end
children = [MyApp.LoadBalancer]
{:ok, result} = MyApp.LoadBalancer.call(node(), MyModule, :my_fun, [arg])Documentation
This project's documentation follows the Diátaxis framework:
Tutorials
- Getting Started — learn the library by building a load-balanced RPC setup step by step
How-To Guides
- Define a Named Load Balancer Module
- Write a Custom Selection Algorithm
- Use Hash-Based Routing
- Filter Which Nodes Join a Balancer
- Use Connection-Tracking Algorithms
- Configure Weighted Round Robin
- Route by CPU Load with LeastCpu
- Control Retry Behaviour
- Collect Telemetry and Metrics
- Testing with CallDirect
Reference
- Full API Reference — types, functions, callbacks, and internal modules
Explanation
- Architecture and Design Decisions — how the components fit together and why