Cluster sharding distributes stateful actors across an Akka cluster. Each sharded actor is an entity, grouped into logical shards and located automatically from its ID. A partition key routes messages to the entity and lets the cluster spread memory and work across nodes while keeping at most one instance of an entity active in the cluster. section 8.1 section 9.1
Entities relocate as nodes join, leave, or fail. This isolates actors on healthy nodes from failures elsewhere and restores capacity by moving affected entities to healthy members. Relocation restarts an entity, however, so in-memory state is lost unless a durable mechanism such as Akka Persistence preserves it. section 9.1
A minimal sharded entity needs the following pieces:
- An EntityTypeKey identifying the entity type. The type key and entity ID together distinguish an entity, allowing different entity types to reuse the same ID. section 9.1.2
- A behavior constructed from the EntityContext entity ID. EntityContext supplies the type key and ID, rather than normal ActorContext facilities such as logging, spawning, scheduling, or watch. section 9.1.2
- A ClusterSharding instance initialized with an Entity definition, producing a shard region that accepts ShardingEnvelope messages. section 9.1.2
- Cluster configuration, including a cluster actor provider, remote host and port, seed nodes, and serialization bindings for the example protocol. section 9.1.3
Shipping containers
Model each shipping container as an entity whose ID is the container ID. Its behavior starts with no cargo, accepts AddCargo, and replies to GetCargos. Initialize sharding with the container type key, then send ShardingEnvelope(containerId, AddCargo(cargo)) to the shard region. No ActorRef is required and no entity must be spawned manually: sending to a missing entity creates it automatically. The client uses an EntityRef-like API while sharding selects and manages the entity location. section 9.1.2 section 9.1.3
Listing 9.1 Container behavior with containerId and TypeKey
object Container {
val typeKey =
EntityTypeKey[Container.Command]("container-type-key") #1
sealed trait Command
final case class AddCargo(cargo: Cargo) extends Command #2
with CborSerializable #2
final case class GetCargos(replyTo: ActorRef[List[Cargo]]) #2
extends Command with CborSerializable #2
def apply(containerId: String): Behavior[Command] = { #3
ready(List())
}
def ready(cargos: List[Cargo]): Behavior[Command] = { #4
Behaviors.receiveMessage[Command] {
case AddCargo(cargo) =>
ready(cargo +: cargos)
case GetCargos(replyTo) =>
replyTo ! cargos
Behaviors.same
}
}
}
The container behavior shows the essential entity ingredients: a typed key, serializable commands, an ID-based constructor, and state carried forward by the behavior. section 9.1.2
Avoid bypassing sharding by sending directly to an entity ActorRef unless that trade-off is understood. Direct messages are not registered as activity by passivation, the mechanism that adds or removes entities from memory according to activity. Use the shard region for normal entity communication. section 9.1.3
Sharding fits workloads with many independently addressed actors. A wallet service, for example, can distribute millions of wallets across nodes by ID and add nodes as demand grows. Pairing sharding with persistence makes balances durable through process or infrastructure failures. section 12.1.1
Sharding can create as many entities as demand requires. This differs from ShardedDaemonProcess, which distributes behaviors too but requires its instance count up front and does not provide a shard-region proxy for direct entity messaging. section 10.3.3
