Placement
In a cluster a top-level actor name is unique across every node. Where the actor runs is placement, and there are two ways to decide it:
spawn(name, props)keeps the actor on the calling node, as always. The name is now claimed cluster-wide.spawnOn(name, props, { strategy })places the actor on the node a strategy chooses, and returns a routed handle to it.
Either way, a name → owning node record goes into the distributed registry, and any node can then reach the actor by name.
Cluster-wide unique names
spawn("orders") on two nodes at once is a race for the one name. The winner builds the actor; the loser is refused with ErrActorAlreadyExists, exactly as a duplicate spawn is refused on a single node. Handing the loser a handle to an actor built from someone else's Props would turn a race into silent misconfiguration, so the loser is told, not quietly aliased. A caller that wants the existing actor looks it up with actorOf; a caller that wants idempotent creation uses a singleton.
const ref = await system.spawn("orders", Props.create(Orders, "eu"));
// On another node, at the same instant:
await system.spawn("orders", Props.create(Orders, "us")); // rejects: ErrActorAlreadyExistsPlacing on a chosen node
spawnOn places an actor on a node the caller does not run on, choosing the node by a strategy carried in the options, not a raw address, so call sites express intent, not topology.
const ref = await system.spawnOn("worker-42", Props.create(Worker, config), { strategy: "leastLoad" });The options are the SpawnOnOptions type, whose strategy is a PlacementStrategy. On a system created without clustering, spawnOn and spawnSingleton reject with ErrClusteringDisabled.
The owner is the one that claims the name and builds the actor, so a spawnOn that loses the name race is refused with ErrActorAlreadyExists just like a spawn. The returned PID is a routed handle: tell, ask, request, watch, forward, and pipeTo on it deliver to whichever node owns the actor.
The owner does the single claim; the caller never pre-claims. That is what keeps two callers racing the same name from both writing a record. When the strategy picks the calling node itself, the same claim-and-build happens locally with no network hop.
Strategies
The strategies are a closed set of four. A raw address is deliberately not a strategy.
| Strategy | Chooses | Notes |
|---|---|---|
roundRobin | The next member in rotation | The default. Rides an atomic cluster counter, so every node advances the same rotation and load spreads with no coordinator on the path. |
random | A uniformly random live member | |
local | The calling node | Opt a call site out of distribution without changing its shape. |
leastLoad | The member owning the fewest actors | Tallies every host's load in one registry scan, so choosing costs one scan whatever the cluster's size. |
Every strategy considers only live, ready, non-draining members, so a node still joining or already leaving is never chosen.
What crosses to the owner
spawnOn builds the actor on the owning node, so the construction crosses the wire as a recipe, exactly as a remote spawn does:
- The actor class must be registered under the same name on both nodes (
registerActor(Worker)at module scope). An unregistered class rejects withActorNotRegisteredError. - Constructor arguments cross the wire codec, validated structured-cloneable. The
reentrancy,passivationStrategy, andrelocatableoptions travel; live-object options (mailbox,supervisor) are node-local and refused, as in everyPropsspawn.
Because the recipe is what a survivor also needs to rebuild the actor after a crash, a placed actor stores it for relocation automatically.
Opting an actor out of relocation
By default a placed actor is recreated on a survivor when its node departs. Set relocatable: false to bind it to its node instead, so it dies with the node rather than moving:
await system.spawnOn("gpu-worker", Props.create(GpuWorker), { strategy: "leastLoad", relocatable: false });The system-wide default is the cluster.relocation option; the per-actor relocatable wins over it. See relocation.