Capability-based distributed process management for Elixir.
Mesh distributes GenServer processes across BEAM nodes using hashing and capability-based routing. Processes are lazily activated on-demand and automatically placed on the correct node based on their ID and capability type.
Mesh solves the distributed process placement problem. Instead of manually managing where processes run, you define capabilities (like :game, :chat, :payment) and let Mesh handle the routing using a hash ring with 4096 shards.
Key characteristics:
- Deterministic placement: Same ID always routes to same node
- Lazy activation: Processes created on first invocation
- Zero coordination: No distributed locks or consensus
- High throughput: 16,000+ process creations/s, 30,000+ requests/s
- Library design: You control your supervision tree
Use cases: game servers with regional routing, multi-tenant systems with workload isolation, microservice patterns where different node types handle different request types.
Capabilities are labels that identify what type of processes a node handles. Examples: :game, :chat, :payment. Nodes register supported capabilities, and Mesh routes requests accordingly.
Mesh.register_capabilities([:game, :chat])The hash ring uses hashing with 4096 shards to distribute processes:
process_id → hash(process_id) → shard (0..4095) → owner_node
Same ID always maps to the same shard. Shards are distributed across nodes using the configured hash strategy (default: modulo-based routing).
NOTE: The default hash strategy (
EventualConsistency) uses eventual consistency for process placement. Shards are used purely for routing decisions - they do not provide state guarantees or transactions. Each process manages its own state independently. During network partitions or topology changes, the same process ID may temporarily exist on multiple nodes until the system converges. You can implement custom hash strategies with different consistency guarantees - see Configuration.
Processes are created lazily on first invocation. When you call a non-existent process, Mesh:
- Determines target node via hash ring
- Starts the process on that node
- Caches the PID for fast subsequent lookups
- Forwards the message and returns the response
This eliminates manual lifecycle management.
Add to mix.exs:
def deps do
[
{:mesh, "~> 0.1"},
{:libcluster, "~> 3.3"}
]
endStart Mesh in your application:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
topologies = [
gossip: [
strategy: Cluster.Strategy.Gossip,
config: [port: 45892, if_addr: "0.0.0.0"]
]
]
children = [
{Mesh.Supervisor, topologies: topologies}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endDefine a GenServer:
defmodule MyApp.Counter do
use GenServer
def start_link(process_id) do
GenServer.start_link(__MODULE__, process_id)
end
def init(process_id) do
{:ok, %{id: process_id, count: 0}}
end
def handle_call({:actor_call, _payload}, _from, state) do
new_count = state.count + 1
{:reply, {:ok, new_count}, %{state | count: new_count}}
end
endRegister capabilities and invoke:
Mesh.register_capabilities([:counter])
{:ok, pid, result} = Mesh.call(%Mesh.Request{
module: MyApp.Counter,
id: "counter_1",
payload: %{},
capability: :counter
})Mesh.call/1 - Synchronous invocation
{:ok, pid, response} = Mesh.call(%Mesh.Request{
module: MyApp.Counter,
id: "counter_1",
payload: %{action: :increment},
capability: :counter
})Mesh.cast/1 - Fire-and-forget invocation
:ok = Mesh.cast(%Mesh.Request{
module: MyApp.Counter,
id: "counter_1",
payload: %{action: :reset},
capability: :counter
})Mesh.register_capabilities/1 - Register supported capabilities
Mesh.register_capabilities([:game, :chat, :payment])Single-node development:
iex -S mix
iex> Mesh.register_capabilities([:game, :chat])Multi-node with Gossip:
# Terminal 1
iex --sname game@localhost --cookie mesh -S mix
iex> Mesh.register_capabilities([:game])
# Terminal 2
iex --sname chat@localhost --cookie mesh -S mix
iex> Mesh.register_capabilities([:chat])Kubernetes deployment:
config :libcluster,
topologies: [
k8s: [
strategy: Cluster.Strategy.Kubernetes.DNS,
config: [
service: "mesh-headless",
application_name: "mesh"
]
]
]Run benchmarks:
mix run scripts/benchmark_singlenode.exs
elixir --name [email protected] --cookie mesh -S mix run scripts/benchmark_multinode.exs- Quickstart Guide - Get started in 5 minutes
- Configuration - Configure hash strategies and sharding
- Clustering - Multi-node setup
- Sharding - Process distribution internals
- Implementing Processes - Building stateful actors
Using Make:
make help # Show all commands
make all # Install, compile, test
make dev # Start IEx session
make test # Run tests
make benchmark # Run single-node benchmark