or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

allocation-strategies.mdconfiguration.mdcore-extension.mdexternal-allocation.mdindex.mdmessage-routing.mdmonitoring.md
tile.json

allocation-strategies.mddocs/

Allocation Strategies

Shard allocation strategies control how shards are distributed across cluster nodes and when rebalancing occurs. Akka Cluster Sharding provides built-in strategies and allows custom implementations.

Core Strategy Interface

ShardAllocationStrategy Trait

trait ShardAllocationStrategy extends NoSerializationVerificationNeeded {
  def allocateShard(
    requester: ActorRef,
    shardId: ShardId,
    currentShardAllocations: Map[ActorRef, immutable.IndexedSeq[ShardId]]
  ): Future[ActorRef]
  
  def rebalance(
    currentShardAllocations: Map[ActorRef, immutable.IndexedSeq[ShardId]],
    rebalanceInProgress: Set[ShardId]
  ): Future[Set[ShardId]]
}

Key Methods:

  • allocateShard: Decides where to place a new shard when first accessed
  • rebalance: Periodically called to determine which shards should be moved

Startable Allocation Strategy

For strategies that need initialization:

trait StartableAllocationStrategy extends ShardAllocationStrategy {
  def start(): Unit
}

Java API Base Class

abstract class AbstractShardAllocationStrategy extends ShardAllocationStrategy {
  // Java-friendly base class for custom implementations
}

Built-in Allocation Strategies

Least Shard Allocation Strategy (Recommended)

Modern implementation with absolute and relative limits:

object ShardAllocationStrategy {
  def leastShardAllocationStrategy(
    absoluteLimit: Int,
    relativeLimit: Double
  ): ShardAllocationStrategy
}

// Also available via ShardCoordinator
object ShardCoordinator {
  def leastShardAllocationStrategy(
    absoluteLimit: Int,
    relativeLimit: Double
  ): ShardAllocationStrategy
}

Parameters:

  • absoluteLimit: Maximum number of shards to rebalance per round
  • relativeLimit: Fraction of total shards to rebalance per round (< 1.0)

Usage Example:

val strategy = ShardCoordinator.leastShardAllocationStrategy(
  absoluteLimit = 20,      // Max 20 shards per rebalance
  relativeLimit = 0.1      // Max 10% of total shards
)

ClusterSharding(system).start(
  typeName = "MyEntity",
  entityProps = Props[MyEntityActor](),
  settings = ClusterShardingSettings(system),
  extractEntityId = myExtractEntityId,
  extractShardId = myExtractShardId,
  allocationStrategy = strategy,
  handOffStopMessage = PoisonPill
)

Legacy Least Shard Allocation Strategy

Legacy implementation with threshold-based rebalancing:

class LeastShardAllocationStrategy(
  rebalanceThreshold: Int,
  maxSimultaneousRebalance: Int
) extends ShardAllocationStrategy

Parameters:

  • rebalanceThreshold: Minimum difference in shard count to trigger rebalancing
  • maxSimultaneousRebalance: Maximum concurrent rebalancing operations

Usage Example:

val strategy = new ShardCoordinator.LeastShardAllocationStrategy(
  rebalanceThreshold = 10,     // Start rebalancing when difference >= 10
  maxSimultaneousRebalance = 3 // Max 3 concurrent rebalances
)

Consistent Hashing Allocation Strategy

Alternative strategy using consistent hashing:

class ConsistentHashingShardAllocationStrategy(
  virtualNodesFactor: Int,
  absoluteLimit: Int,
  relativeLimit: Double
) extends ShardAllocationStrategy

Usage Example:

val strategy = new ConsistentHashingShardAllocationStrategy(
  virtualNodesFactor = 10,   // Virtual nodes per physical node
  absoluteLimit = 20,        // Max shards per rebalance round
  relativeLimit = 0.1        // Max 10% of shards per round
)

External Allocation Strategy

For external control of shard placement:

class ExternalShardAllocationStrategy(
  system: ActorSystem,
  typeName: String
) extends ShardAllocationStrategy

This strategy delegates allocation decisions to an external system via the ExternalShardAllocation extension.

Usage Example:

import akka.cluster.sharding.external.ExternalShardAllocationStrategy

val strategy = new ExternalShardAllocationStrategy(system, "MyEntity")

ClusterSharding(system).start(
  typeName = "MyEntity",
  entityProps = Props[MyEntityActor](),
  settings = ClusterShardingSettings(system),
  extractEntityId = myExtractEntityId,
  extractShardId = myExtractShardId,
  allocationStrategy = strategy,
  handOffStopMessage = PoisonPill
)

Custom Allocation Strategy Implementation

Scala Implementation

class CustomAllocationStrategy extends ShardAllocationStrategy {
  
  override def allocateShard(
    requester: ActorRef,
    shardId: ShardId,
    currentShardAllocations: Map[ActorRef, IndexedSeq[ShardId]]
  ): Future[ActorRef] = {
    // Find region with least shards
    val selectedRegion = currentShardAllocations.minBy(_._2.size)._1
    Future.successful(selectedRegion)
  }
  
  override def rebalance(
    currentShardAllocations: Map[ActorRef, IndexedSeq[ShardId]],
    rebalanceInProgress: Set[ShardId]
  ): Future[Set[ShardId]] = {
    if (rebalanceInProgress.nonEmpty) {
      // Don't rebalance if already in progress
      Future.successful(Set.empty)
    } else {
      // Find shards to rebalance based on custom logic
      val (mostLoaded, leastLoaded) = findMostAndLeastLoaded(currentShardAllocations)
      val shardsToMove = selectShardsToMove(mostLoaded, leastLoaded)
      Future.successful(shardsToMove.toSet)
    }
  }
  
  private def findMostAndLeastLoaded(
    allocations: Map[ActorRef, IndexedSeq[ShardId]]
  ): (ActorRef, ActorRef) = {
    val mostLoaded = allocations.maxBy(_._2.size)._1
    val leastLoaded = allocations.minBy(_._2.size)._1
    (mostLoaded, leastLoaded)
  }
  
  private def selectShardsToMove(
    from: ActorRef,
    to: ActorRef
  ): List[ShardId] = {
    // Custom logic to select which shards to move
    List.empty // Placeholder
  }
}

Java Implementation

public class JavaCustomAllocationStrategy extends AbstractShardAllocationStrategy {
  
  @Override
  public CompletionStage<ActorRef> allocateShard(
      ActorRef requester,
      String shardId,
      Map<ActorRef, ImmutableList<String>> currentShardAllocations) {
    
    // Find region with minimum shard count
    ActorRef selectedRegion = currentShardAllocations.entrySet().stream()
        .min(Map.Entry.comparingByValue((a, b) -> Integer.compare(a.size(), b.size())))
        .map(Map.Entry::getKey)
        .orElse(requester);
    
    return CompletableFuture.completedFuture(selectedRegion);
  }
  
  @Override
  public CompletionStage<Set<String>> rebalance(
      Map<ActorRef, ImmutableList<String>> currentShardAllocations,
      Set<String> rebalanceInProgress) {
    
    if (!rebalanceInProgress.isEmpty()) {
      return CompletableFuture.completedFuture(Collections.emptySet());
    }
    
    // Custom rebalancing logic
    return CompletableFuture.completedFuture(Collections.emptySet());
  }
}

Strategy Selection Guidelines

Use Least Shard Allocation Strategy When:

  • You want even distribution of shards across nodes
  • New nodes should gradually take load from existing nodes
  • You need predictable rebalancing behavior
  • You don't have specific placement requirements

Use Consistent Hashing Strategy When:

  • You want to minimize shard movement when nodes join/leave
  • You have stable cluster topology
  • You prefer locality over perfect balance
  • Network partitions are a concern

Use External Allocation Strategy When:

  • You need centralized control over shard placement
  • You have complex business logic for placement decisions
  • You're integrating with external orchestration systems
  • You need real-time control over shard locations

Use Custom Strategy When:

  • Built-in strategies don't meet your requirements
  • You have domain-specific placement logic
  • You need to integrate with custom monitoring/metrics
  • You have unique performance characteristics

Configuration Integration

Strategies can be configured in application.conf:

akka.cluster.sharding {
  least-shard-allocation-strategy {
    # New algorithm parameters
    rebalance-absolute-limit = 20
    rebalance-relative-limit = 0.1
    
    # Legacy algorithm parameters (deprecated)
    rebalance-threshold = 10
    max-simultaneous-rebalance = 3
  }
}

Performance Considerations

Allocation Performance

  • allocateShard should return quickly (< 100ms)
  • Avoid blocking operations in allocation logic
  • Cache expensive computations if possible

Rebalancing Performance

  • rebalance is called periodically (default: 10s intervals)
  • Don't return too many shards for simultaneous rebalancing
  • Consider cluster load when making rebalancing decisions

Resource Usage

  • Strategies should be stateless or use minimal state
  • Avoid excessive memory allocation in hot paths
  • Be careful with futures and thread pool usage