Skip to main content

Overview

Placement answers one question: which node runs an actor or grain. Actors are placed once, at spawn time. Grains are placed at activation time, every time they go from inactive to active. When cluster mode is disabled, all placement options are ignored and everything runs on the local node.

Actor placement

Local spawns

Spawn always creates the actor on the calling node. Cluster mode does not change that; it only writes the actor’s record to the cluster registry synchronously, so a successful Spawn means the actor is immediately resolvable by name via ActorOf and reachable from any node.

Spawning on an explicit node

WithHostAndPort targets a specific remote node by its remoting address. The spawn request is sent to that node and a remote PID is returned.
Requirements:
  • Remoting must be enabled on the calling system; otherwise Spawn returns ErrRemotingDisabled.
  • The actor kind must be registered on the target node, through ClusterConfig.WithKinds in cluster mode or ActorSystem.Register in remoting-only setups; otherwise the target rejects the request with ErrTypeNotRegistered.

Strategy-based placement with SpawnOn

In cluster mode, SpawnOn selects a target node with a placement strategy and creates the actor there. The candidate set is the current cluster membership, including the calling node. Outside cluster mode, SpawnOn behaves exactly like Spawn.
The returned PID is a live local PID when the actor landed on the calling node and a lightweight remote PID otherwise. Both are transparent for messaging; use pid.IsLocal() / pid.IsRemote() when the physical location matters.
Strategies see the topology at spawn time. RoundRobin gives an even spread over a stable membership, but actors do not move when nodes join later; redistribution only happens through relocation when a node leaves.

What travels with a remote spawn

When SpawnOn or WithHostAndPort places an actor on another node, the spawn request carries the supervisor, passivation strategy, serializable dependencies, stashing flag, reentrancy policy, and init timeout; they are restored on the target node. The mailbox does not travel: a remotely placed actor uses the target system’s default mailbox, so WithMailbox only takes effect for local placements.

Role-constrained placement

Nodes advertise roles through ClusterConfig.WithRoles. Roles let you dedicate node pools to workload classes (“api”, “entity”, “projection”) and scale them independently.
WithRole on the spawn side restricts the candidate set to nodes advertising that role. The placement strategy then applies among the matching nodes only.
  • If no node in the cluster advertises the role, SpawnOn fails with an error instead of falling back to an unsuitable node.
  • Omit WithRole to allow placement on any node.
  • Without clustering, the role option is ignored and the actor spawns locally.
WithPlacement(actor.Local) always resolves to the calling node, even when combined with WithRole and the calling node does not advertise the role. Use RoundRobin, Random, or LeastLoad with role-constrained spawns.

Singletons

Singleton placement is not strategy-based. A singleton runs on the cluster coordinator, or on the oldest node advertising the role given with WithSingletonRole. See Singletons.

Cross data center placement

WithDataCenter places the actor in another data center. The target is a random endpoint among the remoting addresses that data center advertised at registration; the actor kind must be registered on the target data center’s systems.
See Multi Datacenter for control plane setup and failure modes.

Placement is not permanent

When a node leaves the cluster, its relocatable actors are recreated on surviving nodes; role constraints are honored during that redistribution. Use WithRelocationDisabled to pin an actor to the node it was placed on, accepting that it is lost if that node goes down. See Relocation.

Grain placement

Grains are virtual actors: you never spawn them explicitly, so placement happens when an identity is activated. The activation strategy and role are passed as options to GrainOf.

The current owner always wins

If the grain is already active anywhere in the cluster, every call routes to that node and the activation strategy is never consulted. Strategies only decide where an inactive grain comes to life. Ownership is claimed in the cluster registry with an atomic put-if-absent, so two nodes racing to activate the same identity converge on a single owner; the loser routes to the winner. See the activation guarantee.

Activation strategies

In a single-node cluster every strategy resolves to the local node.
The defaults differ between the two models: SpawnOn spreads actors with RoundRobin, while grains default to LocalActivation. Unless you set a strategy, a grain activates on whichever node first touches its identity.

Role-constrained activation

WithActivationRole restricts activation to nodes advertising the role, with the same semantics as actors: the strategy applies among matching nodes, activation fails with an error when no node advertises the role, and the option is ignored without clustering.

Activation options are call-site properties

Grain options, including the activation strategy and role, take effect on the activation performed by the GrainOf call that carries them. A grain that passivated and is later reactivated by a bare TellGrain or AskGrain on a stored identity comes back with the default configuration. When placement must be enforced on every activation, resolve the identity through GrainOf with your options before messaging.

Kind registration on host nodes

Remote activation, recreation, and relocation construct the grain as a zero value from its registered kind. Every node that can host the grain must know the kind: GrainOf auto-registers it on the calling node, and other nodes register it at startup with RegisterGrainKind or ClusterConfig.WithGrains. Activation on a node that does not know the kind fails.

Relocation

Grain placement after a node departure follows the virtual actor model:
  • Lazy relocation (default): the departed node’s grain directory entries are cleaned and each grain reactivates on a surviving node the next time it is addressed. The next activation runs the placement flow again.
  • WithGrainEagerRelocation: the grain is reactivated immediately on a surviving node when its host departs. Use it for grains that must stay warm, such as those driving timers or background work.
  • WithGrainDisableRelocation: the grain is pinned to its host. If the node is lost, the in-memory instance is gone; addressing the identity again creates a fresh instance elsewhere, so persist state externally when it matters.
WithGrainEagerRelocation and WithGrainDisableRelocation are mutually exclusive; configuring both fails with ErrGrainRelocationConflict. See Relocation for the full flow.

Choosing a strategy

  • Keep RoundRobin for fleets of long-lived worker actors; it gives an even spread with negligible placement cost.
  • Use Local / LocalActivation when the workload needs node-local resources or the caller should own it.
  • Use LeastLoad / LeastLoadActivation for heavyweight, long-lived workloads where one extra round of metric queries at placement time is cheap compared to running on a busy node.
  • Use Random when you want spreading without the shared counter round-trip and can tolerate short-term imbalance.
  • Use roles to fence workload classes onto dedicated node pools; combine them with any non-local strategy.

See also