or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cluster-management.mdcluster-routing.mdconfiguration.mdevent-system.mdindex.mdmember-management.mdsplit-brain-resolution.md
tile.json

member-management.mddocs/

Member Management

Member management in Akka Cluster centers around the Member class which represents cluster nodes and their current state. Members progress through well-defined lifecycle stages and can be organized by roles and data centers.

Member Representation

Member Class

class Member private[cluster] (
  val uniqueAddress: UniqueAddress,
  private[cluster] val upNumber: Int,
  val status: MemberStatus,
  val roles: Set[String],
  val appVersion: Version
) extends Serializable {
  def address: Address
  def dataCenter: DataCenter
  def hasRole(role: String): Boolean
  def getRoles: java.util.Set[String]
  def isOlderThan(other: Member): Boolean
  def copy(status: MemberStatus): Member
  def copyUp(upNumber: Int): Member
  
  // Standard methods
  override def hashCode: Int
  override def equals(other: Any): Boolean
  override def toString: String
}

Unique Address

class UniqueAddress(val address: Address, val longUid: Long) 
    extends Product with Serializable with Ordered[UniqueAddress] {
  def address: Address
  def longUid: Long
  
  @deprecated("Use longUid instead", since = "2.4.11")
  def uid: Int = longUid.toInt
  
  def compare(that: UniqueAddress): Int
  override def productArity: Int = 2
  override def productElement(n: Int): Any
  override def canEqual(that: Any): Boolean
  override def hashCode: Int
  override def equals(other: Any): Boolean
  override def toString: String
}

Member Properties

val member: Member = cluster.selfMember

// Core identification
val address: Address = member.address                    // Network address
val uniqueAddress: UniqueAddress = member.uniqueAddress  // Address + unique ID
val uid: Long = member.uniqueAddress.uid                 // Unique identifier

// Member metadata
val status: MemberStatus = member.status                 // Current lifecycle status
val roles: Set[String] = member.roles                    // Assigned roles
val dataCenter: String = member.dataCenter               // Data center assignment
val appVersion: Version = member.appVersion              // Application version

println(s"Member: ${address}, Status: ${status}, DC: ${dataCenter}")

Member Status Lifecycle

Status Enumeration

sealed abstract class MemberStatus

case object Joining extends MemberStatus
case object WeaklyUp extends MemberStatus  
case object Up extends MemberStatus
case object Leaving extends MemberStatus
case object Exiting extends MemberStatus
case object Down extends MemberStatus
case object Removed extends MemberStatus
case object PreparingForShutdown extends MemberStatus
case object ReadyForShutdown extends MemberStatus

object MemberStatus {
  // Java API accessors
  def joining(): MemberStatus = Joining
  def weaklyUp(): MemberStatus = WeaklyUp
  def up(): MemberStatus = Up
  def leaving(): MemberStatus = Leaving
  def exiting(): MemberStatus = Exiting
  def down(): MemberStatus = Down
  def removed(): MemberStatus = Removed
  def shuttingDown(): MemberStatus = PreparingForShutdown  // Java name mapping
  def shutDown(): MemberStatus = ReadyForShutdown          // Java name mapping
}

Status Transitions

Normal lifecycle progression:

Joining → WeaklyUp → Up → Leaving → Exiting → Removed

Failure scenarios:

Any Status → Down → Removed

Coordinated shutdown:

Up → PreparingForShutdown → ReadyForShutdown → Exiting → Removed

Complete allowed transitions:

From PreparingForShutdown: ReadyForShutdown, Removed, Leaving, Down
From ReadyForShutdown: Removed, Leaving, Down

Status Descriptions

  • Joining: Node is attempting to join cluster
  • WeaklyUp: Node is up but cluster hasn't reached minimum size
  • Up: Node is fully operational cluster member
  • Leaving: Node is gracefully leaving cluster
  • Exiting: Node is in exit process, will be removed
  • Down: Node marked as failed/unreachable
  • Removed: Node completely removed from cluster
  • PreparingForShutdown: Node preparing for coordinated shutdown
  • ReadyForShutdown: Node ready for coordinated shutdown

Role-Based Operations

Role Assignment

Roles are configured during system startup and cannot be changed at runtime:

akka.cluster.roles = ["frontend", "backend", "database"]

Role Checking

val member: Member = cluster.selfMember

// Check for specific role
if (member.hasRole("backend")) {
  // Backend-specific logic
  startBackendServices()
}

// Check multiple roles
val isFrontend = member.hasRole("frontend")
val isDatabase = member.hasRole("database")

// Get all roles
val allRoles: Set[String] = member.roles
println(s"Node roles: ${allRoles.mkString(", ")}")

// Java API
val rolesJava: java.util.Set[String] = member.getRoles

Role-Based Member Filtering

val currentState = cluster.state

// Find all backend members
val backendMembers = currentState.members.filter(_.hasRole("backend"))

// Find available (Up or WeaklyUp) frontend members  
val availableFrontends = currentState.members
  .filter(m => m.hasRole("frontend") && 
               (m.status == MemberStatus.Up || m.status == MemberStatus.WeaklyUp))

// Count members by role
val membersByRole = currentState.members.groupBy(_.roles).view.mapValues(_.size)
println(s"Members by role: $membersByRole")

Member Comparison and Ordering

Age Comparison

// Compare member age (same data center only)
def isOlderThan(other: Member): Boolean

Usage:

val member1: Member = // ...
val member2: Member = // ...

try {
  if (member1.isOlderThan(member2)) {
    println(s"${member1.address} is older than ${member2.address}")
  }
} catch {
  case _: IllegalArgumentException =>
    println("Cannot compare members from different data centers")
}

Member Ordering

object Member {
  val ordering: Ordering[Member]
  val ageOrdering: Ordering[Member]  
  val addressOrdering: Ordering[Address]
  val none: Set[Member]
  
  // Utility methods
  def highestPriorityOf(m1: Member, m2: Member): Member
}

Usage:

val members: Set[Member] = cluster.state.members.toSet

// Default ordering (by address)
val sortedMembers = members.toSeq.sorted(Member.ordering)

// Age-based ordering (oldest first)
val membersByAge = members.toSeq.sorted(Member.ageOrdering)

// Address-based ordering
val addresses = members.map(_.address).toSeq.sorted(Member.addressOrdering)

Data Center Support

Data Center Assignment

Data centers are assigned via special roles with the dc- prefix:

akka.cluster {
  roles = ["dc-east", "backend"]
  multi-data-center.self-data-center = "east"
}

Data Center Operations

val member: Member = cluster.selfMember

// Get data center
val dataCenter: String = member.dataCenter
println(s"Member data center: $dataCenter")

// Filter by data center
val currentState = cluster.state
val localMembers = currentState.members.filter(_.dataCenter == cluster.selfDataCenter)
val remoteMembers = currentState.members.filter(_.dataCenter != cluster.selfDataCenter)

// Group by data center
val membersByDC = currentState.members.groupBy(_.dataCenter)
membersByDC.foreach { case (dc, members) =>
  println(s"Data center '$dc': ${members.size} members")
}

Member State Changes

Creating Member Copies

def copy(status: MemberStatus): Member
def copyUp(upNumber: Int): Member

Usage (typically internal to Akka):

val member: Member = // existing member
val updatedMember = member.copy(MemberStatus.Up)
val upMember = member.copyUp(upNumber = 42)

Allowed Status Transitions

Only specific status transitions are allowed:

  • From Joining to WeaklyUp or Up
  • From WeaklyUp to Up
  • From Up to Leaving or Down or PreparingForShutdown
  • From Leaving to Exiting or Down
  • From Exiting to Removed
  • From Down to Removed
  • From PreparingForShutdown to ReadyForShutdown or Down
  • From ReadyForShutdown to Exiting or Down

Application Version Management

Version Compatibility

Members carry application version information for compatibility checking:

val member: Member = cluster.selfMember
val version: Version = member.appVersion

println(s"App version: ${version.version}")

// Version comparison (if needed)
val otherVersion = Version("1.2.0")
if (version >= otherVersion) {
  // Compatible version
}

Dynamic Version Setting

import scala.concurrent.Future
import akka.util.Version

// Set version before joining
val versionFuture: Future[Version] = loadVersionFromExternalSystem()
cluster.setAppVersionLater(versionFuture)
cluster.joinSeedNodes(seedNodes)

Member Information Access

Self Member Access

val cluster = Cluster(system)

// Current node as member
val selfMember: Member = cluster.selfMember
val selfAddress: Address = cluster.selfAddress  
val selfUniqueAddress: UniqueAddress = cluster.selfUniqueAddress
val selfRoles: Set[String] = cluster.selfRoles
val selfDataCenter: String = cluster.selfDataCenter

// Java API for roles
val selfRolesJava: java.util.Set[String] = cluster.getSelfRoles

Cluster Member Access

val state: CurrentClusterState = cluster.state

// All members
val allMembers: immutable.SortedSet[Member] = state.members

// Filter by status
val upMembers = allMembers.filter(_.status == MemberStatus.Up)
val joiningMembers = allMembers.filter(_.status == MemberStatus.Joining)

// Find specific member
val memberOpt = allMembers.find(_.address == targetAddress)

// Unreachable members
val unreachableMembers: Set[Member] = state.unreachable

Member Lifecycle Monitoring

Complete Member Tracking Example

import akka.actor.{Actor, ActorLogging}
import akka.cluster.{Cluster, Member, MemberStatus}
import akka.cluster.ClusterEvent._
import scala.collection.mutable

class MemberTracker extends Actor with ActorLogging {
  val cluster = Cluster(context.system)
  val members = mutable.Map[Address, Member]()
  
  override def preStart(): Unit = {
    cluster.subscribe(self, classOf[MemberEvent])
  }
  
  override def postStop(): Unit = {
    cluster.unsubscribe(self)
  }
  
  def receive = {
    case state: CurrentClusterState =>
      members.clear()
      state.members.foreach { member =>
        members(member.address) = member
        logMemberInfo(member, "Initial")
      }
      
    case MemberJoined(member) =>
      members(member.address) = member
      logMemberInfo(member, "Joined")
      
    case MemberUp(member) =>
      members(member.address) = member
      logMemberInfo(member, "Up")
      
    case MemberLeft(member) =>
      members(member.address) = member
      logMemberInfo(member, "Left")
      
    case MemberRemoved(member, previousStatus) =>
      members.remove(member.address)
      log.info("Member removed: {} (was: {})", member.address, previousStatus)
      
    case event: MemberEvent =>
      members(event.member.address) = event.member
      logMemberInfo(event.member, event.getClass.getSimpleName)
  }
  
  def logMemberInfo(member: Member, event: String): Unit = {
    log.info("{}: {} - Status: {}, Roles: {}, DC: {}, Version: {}", 
             event, member.address, member.status, 
             member.roles.mkString(","), member.dataCenter, member.appVersion)
  }
}