This guide covers Mesh configuration options and how to customize the system for your use case.
Configure Mesh in config/config.exs:
config :mesh,
shards: 4096Mesh uses a hash strategy to determine which node owns each shard. The default strategy is EventualConsistency, which uses modulo-based routing for simple and fast process placement.
The default strategy distributes shards using modulo arithmetic:
# This is the default, you don't need to configure it
config :mesh,
shards: 4096,
hash_strategy: Mesh.Shards.HashStrategy.EventualConsistencyCharacteristics:
- Simple: Direct modulo calculation (
shard % node_count) - Fast: No external dependencies or complex data structures
- Deterministic: Same shard always maps to same node (given same topology)
- Eventually consistent: During topology changes, processes may temporarily exist on multiple nodes
When to use:
- Most production deployments
- When you need predictable performance
- When node count changes infrequently
- When you can tolerate temporary inconsistency during rebalancing
You can implement your own hash strategy by creating a module that implements the Mesh.Shards.HashStrategy behavior:
defmodule MyApp.CustomHashStrategy do
@behaviour Mesh.Shards.HashStrategy
@impl true
def owner_node(shard, capability, nodes) do
# Your custom logic here
# Must return the owner node for the given shard
# Example: weighted distribution
total_weight = length(nodes) * 10
weighted_index = rem(shard * 7, total_weight) # Use prime multiplier
node_index = div(weighted_index, 10)
Enum.at(nodes, node_index)
end
endThen configure Mesh to use your strategy:
config :mesh,
shards: 4096,
hash_strategy: MyApp.CustomHashStrategyThe Mesh.Shards.HashStrategy behavior requires implementing one callback:
@callback owner_node(
shard :: non_neg_integer(),
capability :: atom(),
nodes :: [node()]
) :: node()Parameters:
shard: The shard number (0 toshards - 1)capability: The capability being requested (e.g.,:game,:chat)nodes: List of available nodes that support the capability
Returns:
- The node that should own this shard
Here's an example that routes shards based on geographic regions:
defmodule MyApp.RegionAwareStrategy do
@behaviour Mesh.Shards.HashStrategy
@impl true
def owner_node(shard, capability, nodes) do
# Get node regions from application config
regions = Application.get_env(:my_app, :node_regions, %{})
# Determine preferred region based on shard
preferred_region = case rem(shard, 3) do
0 -> :us_east
1 -> :us_west
2 -> :eu_west
end
# Filter nodes by preferred region
regional_nodes = Enum.filter(nodes, fn node ->
Map.get(regions, node) == preferred_region
end)
# Fall back to all nodes if no regional nodes available
available_nodes = if regional_nodes == [], do: nodes, else: regional_nodes
# Use modulo within the filtered nodes
Enum.at(available_nodes, rem(shard, length(available_nodes)))
end
endConfiguration:
config :mesh,
shards: 4096,
hash_strategy: MyApp.RegionAwareStrategy
config :my_app,
node_regions: %{
:"game1@us-east" => :us_east,
:"game2@us-west" => :us_west,
:"game3@eu-west" => :eu_west
}Route different capabilities using different strategies:
defmodule MyApp.CapabilityBasedStrategy do
@behaviour Mesh.Shards.HashStrategy
@impl true
def owner_node(shard, capability, nodes) do
case capability do
# Critical capabilities: use first N nodes for better isolation
capability when capability in [:payment, :auth] ->
critical_nodes = Enum.take(nodes, min(2, length(nodes)))
Enum.at(critical_nodes, rem(shard, length(critical_nodes)))
# Regular capabilities: distribute across all nodes
_ ->
Enum.at(nodes, rem(shard, length(nodes)))
end
end
endThe number of shards determines the granularity of process distribution:
config :mesh,
shards: 4096 # DefaultGuidelines:
| Cluster Size | Recommended Shards | Distribution |
|---|---|---|
| 2-4 nodes | 2048-4096 | Good |
| 5-10 nodes | 4096-8192 | Excellent |
| 10+ nodes | 8192-16384 | Optimal |
Trade-offs:
- More shards: Better distribution, more memory overhead
- Fewer shards: Less memory, coarser distribution
Mesh integrates with libcluster for automatic node discovery:
config :libcluster,
topologies: [
gossip: [
strategy: Cluster.Strategy.Gossip,
config: [
port: 45892,
if_addr: "0.0.0.0",
multicast_if: "0.0.0.0"
]
]
]Pass topologies to Mesh supervisor:
children = [
{Mesh.Supervisor, topologies: Application.get_env(:libcluster, :topologies)}
]For Kubernetes deployments:
config :libcluster,
topologies: [
k8s: [
strategy: Cluster.Strategy.Kubernetes.DNS,
config: [
service: "mesh-headless",
application_name: "mesh",
polling_interval: 10_000
]
]
]-
Choose strategy based on needs
- Default
EventualConsistencyworks many use cases - Custom strategies for special routing requirements (regions, isolation, strong consistency)
- Default
-
Keep it simple
- Complex strategies can introduce performance bottlenecks
- Test thoroughly before deploying custom strategies
-
Document your strategy
- If using custom strategy, document why and how it works
- Include expected behavior during topology changes
-
Monitor distribution
- Track process distribution across nodes
- Watch for hot spots or imbalances
-
Plan for growth
- Choose shard count based on expected cluster size
- Consider overhead of rebalancing when changing configuration
- Learn about Sharding internals
- Explore Clustering for multi-node setup
- See Processes for implementing actors