diff --git a/akka-cluster-sharding-typed/src/main/scala/akka/cluster/sharding/typed/ClusterShardingSettings.scala b/akka-cluster-sharding-typed/src/main/scala/akka/cluster/sharding/typed/ClusterShardingSettings.scala index fd41d4ca3f..d7dbddf1e4 100644 --- a/akka-cluster-sharding-typed/src/main/scala/akka/cluster/sharding/typed/ClusterShardingSettings.scala +++ b/akka-cluster-sharding-typed/src/main/scala/akka/cluster/sharding/typed/ClusterShardingSettings.scala @@ -165,89 +165,90 @@ object ClusterShardingSettings { @ApiMayChange final class PassivationStrategySettings private ( - val strategy: String, - val idleSettings: PassivationStrategySettings.IdleSettings, - val leastRecentlyUsedSettings: PassivationStrategySettings.LeastRecentlyUsedSettings, - val mostRecentlyUsedSettings: PassivationStrategySettings.MostRecentlyUsedSettings, - val leastFrequentlyUsedSettings: PassivationStrategySettings.LeastFrequentlyUsedSettings, + val idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + val activeEntityLimit: Option[Int], + val replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], private[akka] val oldSettingUsed: Boolean) { def this( - strategy: String, - idleSettings: PassivationStrategySettings.IdleSettings, - leastRecentlyUsedSettings: PassivationStrategySettings.LeastRecentlyUsedSettings, - mostRecentlyUsedSettings: PassivationStrategySettings.MostRecentlyUsedSettings, - leastFrequentlyUsedSettings: PassivationStrategySettings.LeastFrequentlyUsedSettings) = - this( - strategy, - idleSettings, - leastRecentlyUsedSettings, - mostRecentlyUsedSettings, - leastFrequentlyUsedSettings, - oldSettingUsed = false) + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings]) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed = false) import PassivationStrategySettings._ - def withIdleStrategy(settings: IdleSettings): PassivationStrategySettings = - copy(strategy = "idle", idleSettings = settings, oldSettingUsed = false) + def withIdleEntityPassivation(settings: IdleSettings): PassivationStrategySettings = + copy(idleEntitySettings = Some(settings), oldSettingUsed = false) - def withLeastRecentlyUsedStrategy(settings: LeastRecentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "least-recently-used", leastRecentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: FiniteDuration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout)) - def withMostRecentlyUsedStrategy(settings: MostRecentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "most-recently-used", mostRecentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: FiniteDuration, interval: FiniteDuration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout).withInterval(interval)) - def withLeastFrequentlyUsedStrategy(settings: LeastFrequentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "least-frequently-used", leastFrequentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: java.time.Duration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout)) + + def withIdleEntityPassivation( + timeout: java.time.Duration, + interval: java.time.Duration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout).withInterval(interval)) + + def withActiveEntityLimit(limit: Int): PassivationStrategySettings = + copy(activeEntityLimit = Some(limit)) + + def withReplacementPolicy(settings: PolicySettings): PassivationStrategySettings = + copy(replacementPolicySettings = Some(settings)) + + def withLeastRecentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(LeastRecentlyUsedSettings.defaults) + + def withMostRecentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(MostRecentlyUsedSettings.defaults) + + def withLeastFrequentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(LeastFrequentlyUsedSettings.defaults) private[akka] def withOldIdleStrategy(timeout: FiniteDuration): PassivationStrategySettings = - copy(strategy = "idle", idleSettings = idleSettings.withTimeout(timeout), oldSettingUsed = true) + copy( + idleEntitySettings = Some(new IdleSettings(timeout, None)), + activeEntityLimit = None, + replacementPolicySettings = None, + oldSettingUsed = true) private def copy( - strategy: String, - idleSettings: IdleSettings = idleSettings, - leastRecentlyUsedSettings: LeastRecentlyUsedSettings = leastRecentlyUsedSettings, - mostRecentlyUsedSettings: MostRecentlyUsedSettings = mostRecentlyUsedSettings, - leastFrequentlyUsedSettings: LeastFrequentlyUsedSettings = leastFrequentlyUsedSettings, + idleEntitySettings: Option[IdleSettings] = idleEntitySettings, + activeEntityLimit: Option[Int] = activeEntityLimit, + replacementPolicySettings: Option[PolicySettings] = replacementPolicySettings, oldSettingUsed: Boolean = oldSettingUsed): PassivationStrategySettings = - new PassivationStrategySettings( - strategy, - idleSettings, - leastRecentlyUsedSettings, - mostRecentlyUsedSettings, - leastFrequentlyUsedSettings, - oldSettingUsed) + new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed) } object PassivationStrategySettings { import ClassicShardingSettings.{ PassivationStrategySettings => ClassicPassivationStrategySettings } val defaults = new PassivationStrategySettings( - strategy = "idle", - IdleSettings.defaults, - LeastRecentlyUsedSettings.defaults, - MostRecentlyUsedSettings.defaults, - LeastFrequentlyUsedSettings.defaults, + idleEntitySettings = None, + activeEntityLimit = None, + replacementPolicySettings = None, oldSettingUsed = false) - val disabled: PassivationStrategySettings = defaults.copy(strategy = "none") + val disabled: PassivationStrategySettings = defaults def apply(classic: ClassicShardingSettings.PassivationStrategySettings) = new PassivationStrategySettings( - classic.strategy, - IdleSettings(classic.idleSettings), - LeastRecentlyUsedSettings(classic.leastRecentlyUsedSettings), - MostRecentlyUsedSettings(classic.mostRecentlyUsedSettings), - LeastFrequentlyUsedSettings(classic.leastFrequentlyUsedSettings), + classic.idleEntitySettings.map(IdleSettings.apply), + classic.activeEntityLimit, + classic.replacementPolicySettings.map(PolicySettings.apply), classic.oldSettingUsed) def toClassic(settings: PassivationStrategySettings): ClassicPassivationStrategySettings = new ClassicPassivationStrategySettings( - strategy = settings.strategy, - IdleSettings.toClassic(settings.idleSettings), - LeastRecentlyUsedSettings.toClassic(settings.leastRecentlyUsedSettings), - MostRecentlyUsedSettings.toClassic(settings.mostRecentlyUsedSettings), - LeastFrequentlyUsedSettings.toClassic(settings.leastFrequentlyUsedSettings)) + settings.idleEntitySettings.map(IdleSettings.toClassic), + settings.activeEntityLimit, + settings.replacementPolicySettings.map(PolicySettings.toClassic), + settings.oldSettingUsed) object IdleSettings { val defaults: IdleSettings = new IdleSettings(timeout = 2.minutes, interval = None) @@ -273,21 +274,34 @@ object ClusterShardingSettings { new IdleSettings(timeout, interval) } + object PolicySettings { + def apply(classic: ClassicPassivationStrategySettings.PolicySettings): PolicySettings = classic match { + case classic: ClassicPassivationStrategySettings.LeastRecentlyUsedSettings => + LeastRecentlyUsedSettings(classic) + case classic: ClassicPassivationStrategySettings.MostRecentlyUsedSettings => + MostRecentlyUsedSettings(classic) + case classic: ClassicPassivationStrategySettings.LeastFrequentlyUsedSettings => + LeastFrequentlyUsedSettings(classic) + } + + def toClassic(settings: PolicySettings): ClassicPassivationStrategySettings.PolicySettings = settings match { + case settings: LeastRecentlyUsedSettings => LeastRecentlyUsedSettings.toClassic(settings) + case settings: MostRecentlyUsedSettings => MostRecentlyUsedSettings.toClassic(settings) + case settings: LeastFrequentlyUsedSettings => LeastFrequentlyUsedSettings.toClassic(settings) + } + } + + sealed trait PolicySettings + object LeastRecentlyUsedSettings { - val defaults: LeastRecentlyUsedSettings = - new LeastRecentlyUsedSettings(limit = 100000, segmentedSettings = None, idleSettings = None) + val defaults: LeastRecentlyUsedSettings = new LeastRecentlyUsedSettings(segmentedSettings = None) def apply(classic: ClassicPassivationStrategySettings.LeastRecentlyUsedSettings): LeastRecentlyUsedSettings = - new LeastRecentlyUsedSettings( - classic.limit, - classic.segmentedSettings.map(SegmentedSettings.apply), - classic.idleSettings.map(IdleSettings.apply)) + new LeastRecentlyUsedSettings(classic.segmentedSettings.map(SegmentedSettings.apply)) def toClassic(settings: LeastRecentlyUsedSettings): ClassicPassivationStrategySettings.LeastRecentlyUsedSettings = new ClassicPassivationStrategySettings.LeastRecentlyUsedSettings( - settings.limit, - settings.segmentedSettings.map(SegmentedSettings.toClassic), - settings.idleSettings.map(IdleSettings.toClassic)) + settings.segmentedSettings.map(SegmentedSettings.toClassic)) object SegmentedSettings { def apply(classic: ClassicPassivationStrategySettings.LeastRecentlyUsedSettings.SegmentedSettings) @@ -315,122 +329,58 @@ object ClusterShardingSettings { } } - final class LeastRecentlyUsedSettings( - val limit: Int, - val segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings], - val idleSettings: Option[IdleSettings]) { + final class LeastRecentlyUsedSettings(val segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings]) + extends PolicySettings { + import LeastRecentlyUsedSettings.SegmentedSettings - def withLimit(limit: Int): LeastRecentlyUsedSettings = copy(limit = limit) + def withSegmented(levels: Int): LeastRecentlyUsedSettings = + copy(segmentedSettings = Some(new SegmentedSettings(levels, Nil))) - def withSegmented(levels: Int): LeastRecentlyUsedSettings = withSegmented(levels, Nil) + def withSegmented(proportions: immutable.Seq[Double]): LeastRecentlyUsedSettings = + copy(segmentedSettings = Some(new SegmentedSettings(proportions.size, proportions))) - def withSegmented(levels: Int, proportions: immutable.Seq[Double]): LeastRecentlyUsedSettings = - copy(segmentedSettings = Some(new LeastRecentlyUsedSettings.SegmentedSettings(levels, proportions))) + def withSegmentedProportions(proportions: java.util.List[java.lang.Double]): LeastRecentlyUsedSettings = + withSegmented(immutableSeq(proportions).map(_.toDouble)) - def withSegmentedProportions( - levels: Int, - proportions: java.util.List[java.lang.Double]): LeastRecentlyUsedSettings = - withSegmented(levels, immutableSeq(proportions).map(_.toDouble)) - - def withIdle(timeout: FiniteDuration): LeastRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): LeastRecentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): LeastRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): LeastRecentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings] = segmentedSettings, - idleSettings: Option[IdleSettings] = idleSettings): LeastRecentlyUsedSettings = - new LeastRecentlyUsedSettings(limit, segmentedSettings, idleSettings) + private def copy(segmentedSettings: Option[SegmentedSettings]): LeastRecentlyUsedSettings = + new LeastRecentlyUsedSettings(segmentedSettings) } object MostRecentlyUsedSettings { - val defaults: MostRecentlyUsedSettings = new MostRecentlyUsedSettings(limit = 100000, idleSettings = None) + val defaults: MostRecentlyUsedSettings = new MostRecentlyUsedSettings - def apply(classic: ClassicPassivationStrategySettings.MostRecentlyUsedSettings): MostRecentlyUsedSettings = - new MostRecentlyUsedSettings(classic.limit, classic.idleSettings.map(IdleSettings.apply)) + def apply(classic: ClassicPassivationStrategySettings.MostRecentlyUsedSettings): MostRecentlyUsedSettings = { + val _ = classic // currently not used + new MostRecentlyUsedSettings + } - def toClassic(settings: MostRecentlyUsedSettings): ClassicPassivationStrategySettings.MostRecentlyUsedSettings = - new ClassicPassivationStrategySettings.MostRecentlyUsedSettings( - settings.limit, - settings.idleSettings.map(IdleSettings.toClassic)) + def toClassic(settings: MostRecentlyUsedSettings): ClassicPassivationStrategySettings.MostRecentlyUsedSettings = { + val _ = settings // currently not used + new ClassicPassivationStrategySettings.MostRecentlyUsedSettings + } } - final class MostRecentlyUsedSettings(val limit: Int, val idleSettings: Option[IdleSettings]) { - - def withLimit(limit: Int): MostRecentlyUsedSettings = copy(limit = limit) - - def withIdle(timeout: FiniteDuration): MostRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): MostRecentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): MostRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): MostRecentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - idleSettings: Option[IdleSettings] = idleSettings): MostRecentlyUsedSettings = - new MostRecentlyUsedSettings(limit, idleSettings) - } + final class MostRecentlyUsedSettings extends PolicySettings object LeastFrequentlyUsedSettings { - val defaults: LeastFrequentlyUsedSettings = - new LeastFrequentlyUsedSettings(limit = 100000, dynamicAging = false, idleSettings = None) + val defaults: LeastFrequentlyUsedSettings = new LeastFrequentlyUsedSettings(dynamicAging = false) def apply(classic: ClassicPassivationStrategySettings.LeastFrequentlyUsedSettings): LeastFrequentlyUsedSettings = - new LeastFrequentlyUsedSettings( - classic.limit, - classic.dynamicAging, - classic.idleSettings.map(IdleSettings.apply)) + new LeastFrequentlyUsedSettings(classic.dynamicAging) def toClassic( settings: LeastFrequentlyUsedSettings): ClassicPassivationStrategySettings.LeastFrequentlyUsedSettings = - new ClassicPassivationStrategySettings.LeastFrequentlyUsedSettings( - settings.limit, - settings.dynamicAging, - settings.idleSettings.map(IdleSettings.toClassic)) + new ClassicPassivationStrategySettings.LeastFrequentlyUsedSettings(settings.dynamicAging) } - final class LeastFrequentlyUsedSettings( - val limit: Int, - val dynamicAging: Boolean, - val idleSettings: Option[IdleSettings]) { - - def withLimit(limit: Int): LeastFrequentlyUsedSettings = copy(limit = limit) + final class LeastFrequentlyUsedSettings(val dynamicAging: Boolean) extends PolicySettings { def withDynamicAging(): LeastFrequentlyUsedSettings = withDynamicAging(enabled = true) def withDynamicAging(enabled: Boolean): LeastFrequentlyUsedSettings = copy(dynamicAging = enabled) - def withIdle(timeout: FiniteDuration): LeastFrequentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): LeastFrequentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): LeastFrequentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): LeastFrequentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - dynamicAging: Boolean = dynamicAging, - idleSettings: Option[IdleSettings] = idleSettings): LeastFrequentlyUsedSettings = - new LeastFrequentlyUsedSettings(limit, dynamicAging, idleSettings) + private def copy(dynamicAging: Boolean): LeastFrequentlyUsedSettings = + new LeastFrequentlyUsedSettings(dynamicAging) } private[akka] def oldDefault(idleTimeout: FiniteDuration): PassivationStrategySettings = @@ -743,14 +693,15 @@ final class ClusterShardingSettings( rememberEntitiesStoreMode: ClusterShardingSettings.RememberEntitiesStoreMode): ClusterShardingSettings = copy(rememberEntitiesStoreMode = rememberEntitiesStoreMode) - @deprecated("See passivationStrategySettings.idleTimeout instead", since = "2.6.18") - def passivateIdleEntityAfter: FiniteDuration = passivationStrategySettings.idleSettings.timeout + @deprecated("See passivationStrategySettings.idleEntitySettings instead", since = "2.6.18") + def passivateIdleEntityAfter: FiniteDuration = + passivationStrategySettings.idleEntitySettings.fold(Duration.Zero)(_.timeout) - @deprecated("Use withIdlePassivationStrategy instead", since = "2.6.18") + @deprecated("Use withPassivationStrategy instead", since = "2.6.18") def withPassivateIdleEntityAfter(duration: FiniteDuration): ClusterShardingSettings = copy(passivationStrategySettings = passivationStrategySettings.withOldIdleStrategy(duration)) - @deprecated("Use withIdlePassivationStrategy instead", since = "2.6.18") + @deprecated("Use withPassivationStrategy instead", since = "2.6.18") def withPassivateIdleEntityAfter(duration: java.time.Duration): ClusterShardingSettings = copy(passivationStrategySettings = passivationStrategySettings.withOldIdleStrategy(duration.asScala)) @@ -760,24 +711,6 @@ final class ClusterShardingSettings( def withNoPassivationStrategy(): ClusterShardingSettings = copy(passivationStrategySettings = ClusterShardingSettings.PassivationStrategySettings.disabled) - def withIdlePassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.IdleSettings): ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withIdleStrategy(settings)) - - def withLeastRecentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings) - : ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withLeastRecentlyUsedStrategy(settings)) - - def withMostRecentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.MostRecentlyUsedSettings): ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withMostRecentlyUsedStrategy(settings)) - - def withLeastFrequentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings) - : ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withLeastFrequentlyUsedStrategy(settings)) - def withShardRegionQueryTimeout(duration: FiniteDuration): ClusterShardingSettings = copy(shardRegionQueryTimeout = duration) diff --git a/akka-cluster-sharding/src/main/resources/reference.conf b/akka-cluster-sharding/src/main/resources/reference.conf index 8594f74dcd..ed74d7da01 100644 --- a/akka-cluster-sharding/src/main/resources/reference.conf +++ b/akka-cluster-sharding/src/main/resources/reference.conf @@ -28,7 +28,7 @@ akka.cluster.sharding { # Default is ddata for backwards compatibility. remember-entities-store = "ddata" - # Deprecated: use the `passivation.idle.timeout` for 'idle' `passivation.strategy` instead. + # Deprecated: use the `passivation.default-idle-strategy.idle-entity.timeout` setting instead. # Set this to a time duration to have sharding passivate entities when they have not # received any message in this length of time. Set to 'off' to disable. # It is always disabled if `remember-entities` is enabled. @@ -36,87 +36,84 @@ akka.cluster.sharding { # Automatic entity passivation settings. passivation { - # Passivation strategy to use. Possible values are: - # - "idle" - # - "least-recently-used" - # - "most-recently-used" - # - "least-frequently-used" + + # Automatic passivation strategy to use. # Set to "none" or "off" to disable automatic passivation. + # Set to "default-strategy" to switch to the recommended default strategy with an active entity limit. + # See the strategy-defaults section for possible passivation strategy settings and default values. # Passivation strategies are always disabled if `remember-entities` is enabled. - strategy = "idle" + strategy = "default-idle-strategy" - # Idle passivation strategy. - # Passivate entities when they have not received a message for a specified length of time. - idle { - # Passivate idle entities after the timeout. - timeout = 120s - - # Check idle entities every interval. Set to "default" to use half the timeout by default. - interval = default + # Default passivation strategy without active entity limit; time out idle entities after 2 minutes. + default-idle-strategy { + idle-entity.timeout = 120s } - # Least recently used passivation strategy. - # Passivate the least recently used entities when the number of active entities in a shard region - # reaches a limit. The per-region limit is divided evenly among the active shards in a region. - least-recently-used { - # Limit of active entities in a shard region. - limit = 100000 + # Recommended default strategy for automatic passivation with an active entity limit. + # Configured with a segmented least recently used (SLRU) replacement policy. + default-strategy { + # Default limit of 100k active entities in a shard region (in a cluster node). + active-entity-limit = 100000 - # Optionally use a "segmented" least recently used strategy. - # Disabled when segmented.levels are set to "none" or "off". - segmented { - # Number of segmented levels. - levels = none - - # Fractional proportions for the segmented levels. - # If empty then segments are divided evenly by the number of levels. - proportions = [] + # Segmented LRU replacement policy with an 80% "protected" level by default. + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } } + } - # Optionally passivate entities when they have not received a message for a specified length of time. - idle { - # Passivate idle entities after the timeout. Set to "off" to disable. - timeout = off + strategy-defaults { + # Passivate entities when they have not received a message for a specified length of time. + idle-entity { + # Passivate idle entities after the timeout. Set to "none" or "off" to disable. + timeout = none # Check idle entities every interval. Set to "default" to use half the timeout by default. interval = default } - } - # Most recently used passivation strategy. - # Passivate the most recently used entities when the number of active entities in a shard region - # reaches a limit. The per-region limit is divided evenly among the active shards in a region. - most-recently-used { # Limit of active entities in a shard region. - limit = 100000 + # Passivate entities when the number of active entities in a shard region reaches this limit. + # The per-region limit is divided evenly among the active shards in a region. + # Set to "none" or "off" to disable limit-based automatic passivation, to only use idle entity timeouts. + active-entity-limit = none - # Optionally passivate entities when they have not received a message for a specified length of time. - idle { - # Passivate idle entities after the timeout. Set to "off" to disable. - timeout = off + # Entity replacement settings, for when the active entity limit is reached. + replacement { + # Entity replacement policy to use when the active entity limit is reached. Possible values are: + # - "least-recently-used" + # - "most-recently-used" + # - "least-frequently-used" + # Set to "none" or "off" to disable the replacement policy and ignore the active entity limit. + policy = none - # Check idle entities every interval. Set to "default" to use half the timeout by default. - interval = default - } - } + # Least recently used entity replacement policy. + least-recently-used { + # Optionally use a "segmented" least recently used strategy. + # Disabled when segmented.levels are set to "none" or "off". + segmented { + # Number of segmented levels. + levels = none - # Least frequently used passivation strategy. - # Passivate the least frequently used entities when the number of active entities in a shard region - # reaches a limit. The per-region limit is divided evenly among the active shards in a region. - least-frequently-used { - # Limit of active entities in a shard region. - limit = 100000 + # Fractional proportions for the segmented levels. + # If empty then segments are divided evenly by the number of levels. + proportions = [] + } + } - # New frequency counts will be "dynamically aged" when enabled. - dynamic-aging = off + # Most recently used entity replacement policy. + most-recently-used {} - # Optionally passivate entities when they have not received a message for a specified length of time. - idle { - # Passivate idle entities after the timeout. Set to "off" to disable. - timeout = off - - # Check idle entities every interval. Set to "default" to use half the timeout by default. - interval = default + # Least frequently used entity replacement policy. + least-frequently-used { + # New frequency counts will be "dynamically aged" when enabled. + dynamic-aging = off + } } } } diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala index 0663b7fafb..e43dfd804a 100644 --- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala +++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala @@ -90,7 +90,7 @@ object ClusterShardingSettings { val coordinatorSingletonSettings = ClusterSingletonManagerSettings(config.getConfig("coordinator-singleton")) - val passivationStrategySettings = PassivationStrategySettings(config) + val passivationStrategySettings = PassivationStrategySettings.fromSharding(config) val lease = config.getString("use-lease") match { case s if s.isEmpty => None @@ -130,71 +130,75 @@ object ClusterShardingSettings { if (role == "") None else Option(role) @ApiMayChange - final class PassivationStrategySettings private ( - val strategy: String, - val idleSettings: PassivationStrategySettings.IdleSettings, - val leastRecentlyUsedSettings: PassivationStrategySettings.LeastRecentlyUsedSettings, - val mostRecentlyUsedSettings: PassivationStrategySettings.MostRecentlyUsedSettings, - val leastFrequentlyUsedSettings: PassivationStrategySettings.LeastFrequentlyUsedSettings, + final class PassivationStrategySettings private[akka] ( + val idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + val activeEntityLimit: Option[Int], + val replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], private[akka] val oldSettingUsed: Boolean) { def this( - strategy: String, - idleSettings: PassivationStrategySettings.IdleSettings, - leastRecentlyUsedSettings: PassivationStrategySettings.LeastRecentlyUsedSettings, - mostRecentlyUsedSettings: PassivationStrategySettings.MostRecentlyUsedSettings, - leastFrequentlyUsedSettings: PassivationStrategySettings.LeastFrequentlyUsedSettings) = - this( - strategy, - idleSettings, - leastRecentlyUsedSettings, - mostRecentlyUsedSettings, - leastFrequentlyUsedSettings, - oldSettingUsed = false) + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings]) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed = false) import PassivationStrategySettings._ - def withIdleStrategy(settings: IdleSettings): PassivationStrategySettings = - copy(strategy = "idle", idleSettings = settings, oldSettingUsed = false) + def withIdleEntityPassivation(settings: IdleSettings): PassivationStrategySettings = + copy(idleEntitySettings = Some(settings), oldSettingUsed = false) - def withLeastRecentlyUsedStrategy(settings: LeastRecentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "least-recently-used", leastRecentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: FiniteDuration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout)) - def withMostRecentlyUsedStrategy(settings: MostRecentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "most-recently-used", mostRecentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: FiniteDuration, interval: FiniteDuration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout).withInterval(interval)) - def withLeastFrequentlyUsedStrategy(settings: LeastFrequentlyUsedSettings): PassivationStrategySettings = - copy(strategy = "least-frequently-used", leastFrequentlyUsedSettings = settings) + def withIdleEntityPassivation(timeout: java.time.Duration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout)) + + def withIdleEntityPassivation( + timeout: java.time.Duration, + interval: java.time.Duration): PassivationStrategySettings = + withIdleEntityPassivation(IdleSettings.defaults.withTimeout(timeout).withInterval(interval)) + + def withActiveEntityLimit(limit: Int): PassivationStrategySettings = + copy(activeEntityLimit = Some(limit)) + + def withReplacementPolicy(settings: PolicySettings): PassivationStrategySettings = + copy(replacementPolicySettings = Some(settings)) + + def withLeastRecentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(LeastRecentlyUsedSettings.defaults) + + def withMostRecentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(MostRecentlyUsedSettings.defaults) + + def withLeastFrequentlyUsedReplacement(): PassivationStrategySettings = + withReplacementPolicy(LeastFrequentlyUsedSettings.defaults) private[akka] def withOldIdleStrategy(timeout: FiniteDuration): PassivationStrategySettings = - copy(strategy = "idle", idleSettings = idleSettings.withTimeout(timeout), oldSettingUsed = true) + copy( + idleEntitySettings = Some(new IdleSettings(timeout, None)), + activeEntityLimit = None, + replacementPolicySettings = None, + oldSettingUsed = true) private def copy( - strategy: String, - idleSettings: IdleSettings = idleSettings, - leastRecentlyUsedSettings: LeastRecentlyUsedSettings = leastRecentlyUsedSettings, - mostRecentlyUsedSettings: MostRecentlyUsedSettings = mostRecentlyUsedSettings, - leastFrequentlyUsedSettings: LeastFrequentlyUsedSettings = leastFrequentlyUsedSettings, + idleEntitySettings: Option[IdleSettings] = idleEntitySettings, + activeEntityLimit: Option[Int] = activeEntityLimit, + replacementPolicySettings: Option[PolicySettings] = replacementPolicySettings, oldSettingUsed: Boolean = oldSettingUsed): PassivationStrategySettings = - new PassivationStrategySettings( - strategy, - idleSettings, - leastRecentlyUsedSettings, - mostRecentlyUsedSettings, - leastFrequentlyUsedSettings, - oldSettingUsed) + new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed) } object PassivationStrategySettings { val defaults = new PassivationStrategySettings( - strategy = "idle", - IdleSettings.defaults, - LeastRecentlyUsedSettings.defaults, - MostRecentlyUsedSettings.defaults, - LeastFrequentlyUsedSettings.defaults, + idleEntitySettings = None, + activeEntityLimit = None, + replacementPolicySettings = None, oldSettingUsed = false) - val disabled: PassivationStrategySettings = defaults.copy(strategy = "none") + val disabled: PassivationStrategySettings = defaults object IdleSettings { val defaults: IdleSettings = new IdleSettings(timeout = 2.minutes, interval = None) @@ -207,9 +211,11 @@ object ClusterShardingSettings { new IdleSettings(timeout, interval) } - def optional(config: Config): Option[IdleSettings] = { - if (toRootLowerCase(config.getString("timeout")) == "off") None else Some(IdleSettings(config)) - } + def optional(config: Config): Option[IdleSettings] = + toRootLowerCase(config.getString("timeout")) match { + case "off" | "none" => None + case _ => Some(IdleSettings(config)) + } } final class IdleSettings(val timeout: FiniteDuration, val interval: Option[FiniteDuration]) { @@ -226,15 +232,29 @@ object ClusterShardingSettings { new IdleSettings(timeout, interval) } + object PolicySettings { + def apply(config: Config): PolicySettings = + toRootLowerCase(config.getString("policy")) match { + case "least-recently-used" => LeastRecentlyUsedSettings(config.getConfig("least-recently-used")) + case "most-recently-used" => MostRecentlyUsedSettings(config.getConfig("most-recently-used")) + case "least-frequently-used" => LeastFrequentlyUsedSettings(config.getConfig("least-frequently-used")) + } + + def optional(config: Config): Option[PolicySettings] = + toRootLowerCase(config.getString("policy")) match { + case "off" | "none" => None + case _ => Some(PolicySettings(config)) + } + } + + sealed trait PolicySettings + object LeastRecentlyUsedSettings { - val defaults: LeastRecentlyUsedSettings = - new LeastRecentlyUsedSettings(limit = 100000, segmentedSettings = None, idleSettings = None) + val defaults: LeastRecentlyUsedSettings = new LeastRecentlyUsedSettings(segmentedSettings = None) def apply(config: Config): LeastRecentlyUsedSettings = { - val limit = config.getInt("limit") - val idleSettings = IdleSettings.optional(config.getConfig("idle")) val segmentedSettings = SegmentedSettings.optional(config.getConfig("segmented")) - new LeastRecentlyUsedSettings(limit, segmentedSettings, idleSettings) + new LeastRecentlyUsedSettings(segmentedSettings) } object SegmentedSettings { @@ -266,133 +286,78 @@ object ClusterShardingSettings { } } - final class LeastRecentlyUsedSettings( - val limit: Int, - val segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings], - val idleSettings: Option[IdleSettings]) { + final class LeastRecentlyUsedSettings(val segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings]) + extends PolicySettings { + import LeastRecentlyUsedSettings.SegmentedSettings - def withLimit(limit: Int): LeastRecentlyUsedSettings = copy(limit = limit) + def withSegmented(levels: Int): LeastRecentlyUsedSettings = + copy(segmentedSettings = Some(new SegmentedSettings(levels, Nil))) - def withSegmented(levels: Int): LeastRecentlyUsedSettings = withSegmented(levels, Nil) + def withSegmented(proportions: immutable.Seq[Double]): LeastRecentlyUsedSettings = + copy(segmentedSettings = Some(new SegmentedSettings(proportions.size, proportions))) - def withSegmented(levels: Int, proportions: immutable.Seq[Double]): LeastRecentlyUsedSettings = - copy(segmentedSettings = Some(new LeastRecentlyUsedSettings.SegmentedSettings(levels, proportions))) + def withSegmentedProportions(proportions: java.util.List[java.lang.Double]): LeastRecentlyUsedSettings = + withSegmented(immutableSeq(proportions).map(_.toDouble)) - def withSegmentedProportions( - levels: Int, - proportions: java.util.List[java.lang.Double]): LeastRecentlyUsedSettings = - withSegmented(levels, immutableSeq(proportions).map(_.toDouble)) - - def withIdle(timeout: FiniteDuration): LeastRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): LeastRecentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): LeastRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): LeastRecentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - segmentedSettings: Option[LeastRecentlyUsedSettings.SegmentedSettings] = segmentedSettings, - idleSettings: Option[IdleSettings] = idleSettings): LeastRecentlyUsedSettings = - new LeastRecentlyUsedSettings(limit, segmentedSettings, idleSettings) + private def copy(segmentedSettings: Option[SegmentedSettings]): LeastRecentlyUsedSettings = + new LeastRecentlyUsedSettings(segmentedSettings) } object MostRecentlyUsedSettings { - val defaults: MostRecentlyUsedSettings = new MostRecentlyUsedSettings(limit = 100000, idleSettings = None) + val defaults: MostRecentlyUsedSettings = new MostRecentlyUsedSettings def apply(config: Config): MostRecentlyUsedSettings = { - val limit = config.getInt("limit") - val idleSettings = IdleSettings.optional(config.getConfig("idle")) - new MostRecentlyUsedSettings(limit, idleSettings) + val _ = config // not used + new MostRecentlyUsedSettings } } - final class MostRecentlyUsedSettings(val limit: Int, val idleSettings: Option[IdleSettings]) { - - def withLimit(limit: Int): MostRecentlyUsedSettings = copy(limit = limit) - - def withIdle(timeout: FiniteDuration): MostRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): MostRecentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): MostRecentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): MostRecentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - idleSettings: Option[IdleSettings] = idleSettings): MostRecentlyUsedSettings = - new MostRecentlyUsedSettings(limit, idleSettings) - } + final class MostRecentlyUsedSettings extends PolicySettings object LeastFrequentlyUsedSettings { - val defaults: LeastFrequentlyUsedSettings = - new LeastFrequentlyUsedSettings(limit = 100000, dynamicAging = false, idleSettings = None) + val defaults: LeastFrequentlyUsedSettings = new LeastFrequentlyUsedSettings(dynamicAging = false) def apply(config: Config): LeastFrequentlyUsedSettings = { - val limit = config.getInt("limit") val dynamicAging = config.getBoolean("dynamic-aging") - val idleSettings = IdleSettings.optional(config.getConfig("idle")) - new LeastFrequentlyUsedSettings(limit, dynamicAging, idleSettings) + new LeastFrequentlyUsedSettings(dynamicAging) } } - final class LeastFrequentlyUsedSettings( - val limit: Int, - val dynamicAging: Boolean, - val idleSettings: Option[IdleSettings]) { - - def withLimit(limit: Int): LeastFrequentlyUsedSettings = copy(limit = limit) + final class LeastFrequentlyUsedSettings(val dynamicAging: Boolean) extends PolicySettings { def withDynamicAging(): LeastFrequentlyUsedSettings = withDynamicAging(enabled = true) def withDynamicAging(enabled: Boolean): LeastFrequentlyUsedSettings = copy(dynamicAging = enabled) - def withIdle(timeout: FiniteDuration): LeastFrequentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, None))) - - def withIdle(timeout: java.time.Duration): LeastFrequentlyUsedSettings = - withIdle(timeout.asScala) - - def withIdle(timeout: FiniteDuration, interval: FiniteDuration): LeastFrequentlyUsedSettings = - copy(idleSettings = Some(new IdleSettings(timeout, Some(interval)))) - - def withIdle(timeout: java.time.Duration, interval: java.time.Duration): LeastFrequentlyUsedSettings = - withIdle(timeout.asScala, interval.asScala) - - private def copy( - limit: Int = limit, - dynamicAging: Boolean = dynamicAging, - idleSettings: Option[IdleSettings] = idleSettings): LeastFrequentlyUsedSettings = - new LeastFrequentlyUsedSettings(limit, dynamicAging, idleSettings) + private def copy(dynamicAging: Boolean): LeastFrequentlyUsedSettings = + new LeastFrequentlyUsedSettings(dynamicAging) } def apply(config: Config): PassivationStrategySettings = { - val settings = - new PassivationStrategySettings( - strategy = toRootLowerCase(config.getString("passivation.strategy")), - idleSettings = IdleSettings(config.getConfig("passivation.idle")), - leastRecentlyUsedSettings = LeastRecentlyUsedSettings(config.getConfig("passivation.least-recently-used")), - mostRecentlyUsedSettings = MostRecentlyUsedSettings(config.getConfig("passivation.most-recently-used")), - leastFrequentlyUsedSettings = LeastFrequentlyUsedSettings( - config.getConfig("passivation.least-frequently-used"))) + toRootLowerCase(config.getString("strategy")) match { + case "off" | "none" => PassivationStrategySettings.disabled + case strategyName => + val strategyDefaults = config.getConfig("strategy-defaults") + val strategyConfig = config.getConfig(strategyName).withFallback(strategyDefaults) + val idleEntitySettings = IdleSettings.optional(strategyConfig.getConfig("idle-entity")) + val activeEntityLimit = strategyConfig.getString("active-entity-limit") match { + case "off" | "none" => None + case _ => Some(strategyConfig.getInt("active-entity-limit")) + } + val replacementPolicySettings = PolicySettings.optional(strategyConfig.getConfig("replacement")) + new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings) + } + } + + def fromSharding(shardingConfig: Config): PassivationStrategySettings = { // default to old setting if it exists (defined in application.conf), overriding the new settings - if (config.hasPath("passivate-idle-entity-after")) { + if (shardingConfig.hasPath("passivate-idle-entity-after")) { val timeout = - if (toRootLowerCase(config.getString("passivate-idle-entity-after")) == "off") Duration.Zero - else config.getDuration("passivate-idle-entity-after", MILLISECONDS).millis - settings.withOldIdleStrategy(timeout) + if (toRootLowerCase(shardingConfig.getString("passivate-idle-entity-after")) == "off") Duration.Zero + else shardingConfig.getDuration("passivate-idle-entity-after", MILLISECONDS).millis + oldDefault(timeout) } else { - settings + PassivationStrategySettings(shardingConfig.getConfig("passivation")) } } @@ -417,9 +382,10 @@ object ClusterShardingSettings { extends PassivationStrategy private[akka] object LeastRecentlyUsedPassivationStrategy { - def apply(settings: PassivationStrategySettings.LeastRecentlyUsedSettings): LeastRecentlyUsedPassivationStrategy = { - val limit = settings.limit - val idle = settings.idleSettings.map(IdlePassivationStrategy.apply) + def apply( + settings: PassivationStrategySettings.LeastRecentlyUsedSettings, + limit: Int, + idle: Option[IdlePassivationStrategy]): LeastRecentlyUsedPassivationStrategy = { settings.segmentedSettings match { case Some(segmented) => val proportions = @@ -438,21 +404,15 @@ object ClusterShardingSettings { idle: Option[IdlePassivationStrategy]) extends PassivationStrategy - private[akka] object MostRecentlyUsedPassivationStrategy { - def apply(settings: PassivationStrategySettings.MostRecentlyUsedSettings): MostRecentlyUsedPassivationStrategy = - MostRecentlyUsedPassivationStrategy(settings.limit, settings.idleSettings.map(IdlePassivationStrategy.apply)) - } - private[akka] case class MostRecentlyUsedPassivationStrategy(limit: Int, idle: Option[IdlePassivationStrategy]) extends PassivationStrategy private[akka] object LeastFrequentlyUsedPassivationStrategy { def apply( - settings: PassivationStrategySettings.LeastFrequentlyUsedSettings): LeastFrequentlyUsedPassivationStrategy = - LeastFrequentlyUsedPassivationStrategy( - settings.limit, - settings.dynamicAging, - settings.idleSettings.map(IdlePassivationStrategy.apply)) + settings: PassivationStrategySettings.LeastFrequentlyUsedSettings, + limit: Int, + idle: Option[IdlePassivationStrategy]): LeastFrequentlyUsedPassivationStrategy = + LeastFrequentlyUsedPassivationStrategy(limit, settings.dynamicAging, idle) } private[akka] case class LeastFrequentlyUsedPassivationStrategy( @@ -467,22 +427,28 @@ object ClusterShardingSettings { */ @InternalApi private[akka] object PassivationStrategy { - def apply(settings: ClusterShardingSettings): PassivationStrategy = { + def apply(settings: ClusterShardingSettings): PassivationStrategy = if (settings.rememberEntities) { NoPassivationStrategy - } else - settings.passivationStrategySettings.strategy match { - case "idle" if settings.passivationStrategySettings.idleSettings.timeout > Duration.Zero => - IdlePassivationStrategy(settings.passivationStrategySettings.idleSettings) - case "least-recently-used" => - LeastRecentlyUsedPassivationStrategy(settings.passivationStrategySettings.leastRecentlyUsedSettings) - case "most-recently-used" => - MostRecentlyUsedPassivationStrategy(settings.passivationStrategySettings.mostRecentlyUsedSettings) - case "least-frequently-used" => - LeastFrequentlyUsedPassivationStrategy(settings.passivationStrategySettings.leastFrequentlyUsedSettings) - case _ => NoPassivationStrategy + } else { + val idle = settings.passivationStrategySettings.idleEntitySettings match { + case Some(idleSettings) if idleSettings.timeout > Duration.Zero => Some(IdlePassivationStrategy(idleSettings)) + case _ => None } - } + settings.passivationStrategySettings.activeEntityLimit match { + case Some(limit) => + settings.passivationStrategySettings.replacementPolicySettings match { + case Some(settings: PassivationStrategySettings.LeastRecentlyUsedSettings) => + LeastRecentlyUsedPassivationStrategy(settings, limit, idle) + case Some(_: PassivationStrategySettings.MostRecentlyUsedSettings) => + MostRecentlyUsedPassivationStrategy(limit, idle) + case Some(settings: PassivationStrategySettings.LeastFrequentlyUsedSettings) => + LeastFrequentlyUsedPassivationStrategy(settings, limit, idle) + case _ => idle.getOrElse(NoPassivationStrategy) + } + case _ => idle.getOrElse(NoPassivationStrategy) + } + } } class TuningParameters( @@ -872,14 +838,15 @@ final class ClusterShardingSettings( def withStateStoreMode(stateStoreMode: String): ClusterShardingSettings = copy(stateStoreMode = stateStoreMode) - @deprecated("See passivationStrategySettings.idleTimeout instead", since = "2.6.18") - def passivateIdleEntityAfter: FiniteDuration = passivationStrategySettings.idleSettings.timeout + @deprecated("See passivationStrategySettings.idleEntitySettings instead", since = "2.6.18") + def passivateIdleEntityAfter: FiniteDuration = + passivationStrategySettings.idleEntitySettings.fold(Duration.Zero)(_.timeout) - @deprecated("Use withIdlePassivationStrategy instead", since = "2.6.18") + @deprecated("Use withPassivationStrategy instead", since = "2.6.18") def withPassivateIdleAfter(duration: FiniteDuration): ClusterShardingSettings = copy(passivationStrategySettings = passivationStrategySettings.withOldIdleStrategy(duration)) - @deprecated("Use withIdlePassivationStrategy instead", since = "2.6.18") + @deprecated("Use withPassivationStrategy instead", since = "2.6.18") def withPassivateIdleAfter(duration: java.time.Duration): ClusterShardingSettings = copy(passivationStrategySettings = passivationStrategySettings.withOldIdleStrategy(duration.asScala)) @@ -889,24 +856,6 @@ final class ClusterShardingSettings( def withNoPassivationStrategy(): ClusterShardingSettings = copy(passivationStrategySettings = ClusterShardingSettings.PassivationStrategySettings.disabled) - def withIdlePassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.IdleSettings): ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withIdleStrategy(settings)) - - def withLeastRecentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings) - : ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withLeastRecentlyUsedStrategy(settings)) - - def withMostRecentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.MostRecentlyUsedSettings): ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withMostRecentlyUsedStrategy(settings)) - - def withLeastFrequentlyUsedPassivationStrategy( - settings: ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings) - : ClusterShardingSettings = - copy(passivationStrategySettings = passivationStrategySettings.withLeastFrequentlyUsedStrategy(settings)) - def withShardRegionQueryTimeout(duration: FiniteDuration): ClusterShardingSettings = copy(shardRegionQueryTimeout = duration) diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala index b012fa6f88..9b5a9675a1 100644 --- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala +++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala @@ -674,7 +674,8 @@ private[akka] class ShardRegion( if (settings.passivationStrategySettings.oldSettingUsed) { log.warning( "The `akka.cluster.sharding.passivate-idle-entity-after` setting and associated methods are deprecated. " + - "See automatic passivation strategies and use the `akka.cluster.sharding.passivation.idle.timeout` setting.") + "Use the `akka.cluster.sharding.passivation.default-idle-strategy.idle-entity.timeout` setting instead. " + + "See the documentation and reference config for more information on automatic passivation strategies.") } if (settings.rememberEntities) { log.debug("{}: Entities will not be passivated automatically because 'rememberEntities' is enabled.", typeName) diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala index 231d75e6b7..53b79c4517 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala @@ -35,10 +35,8 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow timeout for (default) idle passivation strategy to be configured (via config)" in { settings(""" #passivation-idle-timeout - akka.cluster.sharding { - passivation { - idle.timeout = 3 minutes - } + akka.cluster.sharding.passivation { + default-idle-strategy.idle-entity.timeout = 3 minutes } #passivation-idle-timeout """).passivationStrategy shouldBe ClusterShardingSettings.IdlePassivationStrategy( @@ -48,8 +46,8 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow timeout for (default) idle passivation strategy to be configured (via factory method)" in { defaultSettings - .withIdlePassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.IdleSettings.defaults.withTimeout(42.seconds)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults.withIdleEntityPassivation(timeout = 42.seconds)) .passivationStrategy shouldBe ClusterShardingSettings.IdlePassivationStrategy( timeout = 42.seconds, interval = 21.seconds) @@ -57,9 +55,9 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow timeout and interval for (default) idle passivation strategy to be configured (via config)" in { settings(""" - akka.cluster.sharding { - passivation { - idle { + akka.cluster.sharding.passivation { + default-idle-strategy { + idle-entity { timeout = 3 minutes interval = 1 minute } @@ -72,25 +70,71 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow timeout and interval for (default) idle passivation strategy to be configured (via factory method)" in { defaultSettings - .withIdlePassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.IdleSettings.defaults - .withTimeout(42.seconds) - .withInterval(42.millis)) + .withPassivationStrategy(ClusterShardingSettings.PassivationStrategySettings.defaults + .withIdleEntityPassivation(timeout = 42.seconds, interval = 42.millis)) .passivationStrategy shouldBe ClusterShardingSettings.IdlePassivationStrategy( timeout = 42.seconds, interval = 42.millis) } - "allow least recently used passivation strategy to be configured (via config)" in { + "allow new default passivation strategy to be enabled (via config)" in { settings(""" - #passivation-least-recently-used - akka.cluster.sharding { - passivation { - strategy = least-recently-used - least-recently-used.limit = 1000000 + #passivation-new-default-strategy + akka.cluster.sharding.passivation { + strategy = default-strategy + } + #passivation-new-default-strategy + """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + limit = 100000, + segmented = List(0.2, 0.8), + idle = None) + } + + "allow new default passivation strategy limit to be configured (via config)" in { + settings(""" + #passivation-new-default-strategy-configured + akka.cluster.sharding.passivation { + strategy = default-strategy + default-strategy { + active-entity-limit = 1000000 } } - #passivation-least-recently-used + #passivation-new-default-strategy-configured + """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + limit = 1000000, + segmented = List(0.2, 0.8), + idle = None) + } + + "allow new default passivation strategy with idle timeout to be configured (via config)" in { + settings(""" + #passivation-new-default-strategy-with-idle + akka.cluster.sharding.passivation { + strategy = default-strategy + default-strategy { + idle-entity.timeout = 30.minutes + } + } + #passivation-new-default-strategy-with-idle + """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + limit = 100000, + segmented = List(0.2, 0.8), + idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 30.minutes, interval = 15.minutes))) + } + + "allow least recently used passivation strategy to be configured (via config)" in { + settings(""" + #custom-passivation-strategy + #lru-policy + akka.cluster.sharding.passivation { + strategy = custom-lru-strategy + custom-lru-strategy { + active-entity-limit = 1000000 + replacement.policy = least-recently-used + } + } + #lru-policy + #custom-passivation-strategy """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 1000000, segmented = Nil, @@ -99,8 +143,10 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least recently used passivation strategy to be configured (via factory method)" in { defaultSettings - .withLeastRecentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults.withLimit(42000)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withLeastRecentlyUsedReplacement()) .passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 42000, segmented = Nil, @@ -109,20 +155,23 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow segmented least recently used passivation strategy to be configured (via config)" in { settings(""" - #passivation-segmented-least-recently-used - akka.cluster.sharding { - passivation { - strategy = least-recently-used - least-recently-used { - limit = 1000000 - segmented { - levels = 2 - proportions = [0.2, 0.8] + #slru-policy + akka.cluster.sharding.passivation { + strategy = custom-slru-strategy + custom-slru-strategy { + active-entity-limit = 1000000 + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } } } } } - #passivation-segmented-least-recently-used + #slru-policy """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 1000000, segmented = List(0.2, 0.8), @@ -131,17 +180,20 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow 4-level segmented least recently used passivation strategy to be configured (via config)" in { settings(""" - #passivation-s4-least-recently-used - akka.cluster.sharding { - passivation { - strategy = least-recently-used - least-recently-used { - limit = 1000000 - segmented.levels = 4 + #s4lru-policy + akka.cluster.sharding.passivation { + strategy = custom-s4lru-strategy + custom-s4lru-strategy { + active-entity-limit = 1000000 + replacement { + policy = least-recently-used + least-recently-used { + segmented.levels = 4 + } } } } - #passivation-s4-least-recently-used + #s4lru-policy """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 1000000, segmented = List(0.25, 0.25, 0.25, 0.25), @@ -150,10 +202,10 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow segmented least recently used passivation strategy to be configured (via factory method)" in { defaultSettings - .withLeastRecentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults - .withLimit(42000) - .withSegmented(levels = 4, proportions = List(0.4, 0.3, 0.2, 0.1))) + .withPassivationStrategy(ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withReplacementPolicy(ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults + .withSegmented(proportions = List(0.4, 0.3, 0.2, 0.1)))) .passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 42000, segmented = List(0.4, 0.3, 0.2, 0.1), @@ -162,17 +214,14 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least recently used passivation strategy with idle timeout to be configured (via config)" in { settings(""" - #passivation-least-recently-used-with-idle - akka.cluster.sharding { - passivation { - strategy = least-recently-used - least-recently-used { - limit = 1000000 - idle.timeout = 30.minutes - } + akka.cluster.sharding.passivation { + strategy = custom-lru-with-idle + custom-lru-with-idle { + active-entity-limit = 1000000 + replacement.policy = least-recently-used + idle-entity.timeout = 30.minutes } } - #passivation-least-recently-used-with-idle """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 1000000, segmented = Nil, @@ -181,10 +230,11 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least recently used passivation strategy with idle timeout to be configured (via factory method)" in { defaultSettings - .withLeastRecentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults - .withLimit(42000) - .withIdle(timeout = 42.minutes)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withLeastRecentlyUsedReplacement() + .withIdleEntityPassivation(timeout = 42.minutes)) .passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( limit = 42000, segmented = Nil, @@ -193,14 +243,15 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow most recently used passivation strategy to be configured (via config)" in { settings(""" - #passivation-most-recently-used - akka.cluster.sharding { - passivation { - strategy = most-recently-used - most-recently-used.limit = 1000000 + #mru-policy + akka.cluster.sharding.passivation { + strategy = custom-mru-strategy + custom-mru-strategy { + active-entity-limit = 1000000 + replacement.policy = most-recently-used } } - #passivation-most-recently-used + #mru-policy """).passivationStrategy shouldBe ClusterShardingSettings.MostRecentlyUsedPassivationStrategy( limit = 1000000, idle = None) @@ -208,8 +259,10 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow most recently used passivation strategy to be configured (via factory method)" in { defaultSettings - .withMostRecentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.MostRecentlyUsedSettings.defaults.withLimit(42000)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withMostRecentlyUsedReplacement()) .passivationStrategy shouldBe ClusterShardingSettings.MostRecentlyUsedPassivationStrategy( limit = 42000, idle = None) @@ -217,17 +270,14 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow most recently used passivation strategy with idle timeout to be configured (via config)" in { settings(""" - #passivation-most-recently-used-with-idle - akka.cluster.sharding { - passivation { - strategy = most-recently-used - most-recently-used { - limit = 1000000 - idle.timeout = 30.minutes - } + akka.cluster.sharding.passivation { + strategy = custom-mru-with-idle + custom-mru-with-idle { + active-entity-limit = 1000000 + replacement.policy = most-recently-used + idle-entity.timeout = 30.minutes } } - #passivation-most-recently-used-with-idle """).passivationStrategy shouldBe ClusterShardingSettings.MostRecentlyUsedPassivationStrategy( limit = 1000000, idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 30.minutes, interval = 15.minutes))) @@ -235,10 +285,11 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow most recently used passivation strategy with idle timeout to be configured (via factory method)" in { defaultSettings - .withMostRecentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.MostRecentlyUsedSettings.defaults - .withLimit(42000) - .withIdle(timeout = 42.minutes)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withMostRecentlyUsedReplacement() + .withIdleEntityPassivation(timeout = 42.minutes)) .passivationStrategy shouldBe ClusterShardingSettings.MostRecentlyUsedPassivationStrategy( limit = 42000, idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 42.minutes, interval = 21.minutes))) @@ -246,14 +297,15 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy to be configured (via config)" in { settings(""" - #passivation-least-frequently-used - akka.cluster.sharding { - passivation { - strategy = least-frequently-used - least-frequently-used.limit = 1000000 + #lfu-policy + akka.cluster.sharding.passivation { + strategy = custom-lfu-strategy + custom-lfu-strategy { + active-entity-limit = 1000000 + replacement.policy = least-frequently-used } } - #passivation-least-frequently-used + #lfu-policy """).passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 1000000, dynamicAging = false, @@ -262,8 +314,10 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy to be configured (via factory method)" in { defaultSettings - .withLeastFrequentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings.defaults.withLimit(42000)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withLeastFrequentlyUsedReplacement()) .passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 42000, dynamicAging = false, @@ -272,17 +326,14 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy with idle timeout to be configured (via config)" in { settings(""" - #passivation-least-frequently-used-with-idle - akka.cluster.sharding { - passivation { - strategy = least-frequently-used - least-frequently-used { - limit = 1000000 - idle.timeout = 30.minutes - } + akka.cluster.sharding.passivation { + strategy = custom-lfu-with-idle + custom-lfu-with-idle { + active-entity-limit = 1000000 + replacement.policy = least-frequently-used + idle-entity.timeout = 30.minutes } } - #passivation-least-frequently-used-with-idle """).passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 1000000, dynamicAging = false, @@ -291,10 +342,11 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy with idle timeout to be configured (via factory method)" in { defaultSettings - .withLeastFrequentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings.defaults - .withLimit(42000) - .withIdle(timeout = 42.minutes)) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withLeastFrequentlyUsedReplacement() + .withIdleEntityPassivation(timeout = 42.minutes)) .passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 42000, dynamicAging = false, @@ -303,17 +355,20 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy with dynamic aging to be configured (via config)" in { settings(""" - #passivation-least-frequently-used-with-dynamic-aging - akka.cluster.sharding { - passivation { - strategy = least-frequently-used - least-frequently-used { - limit = 1000 - dynamic-aging = on + #lfuda-policy + akka.cluster.sharding.passivation { + strategy = custom-lfu-with-dynamic-aging + custom-lfu-with-dynamic-aging { + active-entity-limit = 1000 + replacement { + policy = least-frequently-used + least-frequently-used { + dynamic-aging = on + } } } } - #passivation-least-frequently-used-with-dynamic-aging + #lfuda-policy """).passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 1000, dynamicAging = true, @@ -322,10 +377,12 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "allow least frequently used passivation strategy with dynamic aging to be configured (via factory method)" in { defaultSettings - .withLeastFrequentlyUsedPassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings.defaults - .withLimit(42000) - .withDynamicAging()) + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withReplacementPolicy( + ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings.defaults + .withDynamicAging())) .passivationStrategy shouldBe ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy( limit = 42000, dynamicAging = true, @@ -346,14 +403,14 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { "disable automatic passivation if idle timeout is set to zero (via config)" in { settings(""" - akka.cluster.sharding.passivation.idle.timeout = 0 + akka.cluster.sharding.passivation.default-idle-strategy.idle-entity.timeout = 0 """).passivationStrategy shouldBe ClusterShardingSettings.NoPassivationStrategy } "disable automatic passivation if idle timeout is set to zero (via factory method)" in { defaultSettings - .withIdlePassivationStrategy( - ClusterShardingSettings.PassivationStrategySettings.IdleSettings.defaults.withTimeout(Duration.Zero)) + .withPassivationStrategy(ClusterShardingSettings.PassivationStrategySettings.defaults.withIdleEntityPassivation( + timeout = Duration.Zero)) .passivationStrategy shouldBe ClusterShardingSettings.NoPassivationStrategy } @@ -367,7 +424,7 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { settings(""" akka.cluster.sharding { passivate-idle-entity-after = 5 minutes - passivation-strategy = least-recently-used + passivation.strategy = default-strategy } """).passivationStrategy shouldBe ClusterShardingSettings.IdlePassivationStrategy( timeout = 5.minutes, diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/EntityPassivationSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/EntityPassivationSpec.scala index ed4d00fdeb..56c9528924 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/EntityPassivationSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/EntityPassivationSpec.scala @@ -36,7 +36,6 @@ object EntityPassivationSpec { akka.cluster.sharding { passivation { strategy = none - idle.timeout = 1s } } """).withFallback(config) @@ -84,8 +83,9 @@ abstract class AbstractEntityPassivationSpec(config: Config, expectedEntities: I import EntityPassivationSpec._ val settings: ClusterShardingSettings = ClusterShardingSettings(system) - val configuredIdleTimeout: FiniteDuration = settings.passivationStrategySettings.idleSettings.timeout - val configuredLeastRecentlyUsedLimit: Int = settings.passivationStrategySettings.leastRecentlyUsedSettings.limit + val configuredIdleTimeout: FiniteDuration = + settings.passivationStrategySettings.idleEntitySettings.fold(Duration.Zero)(_.timeout) + val configuredActiveEntityLimit: Int = settings.passivationStrategySettings.activeEntityLimit.getOrElse(0) val probes: Map[Int, TestProbe] = (1 to expectedEntities).map(id => id -> TestProbe()).toMap val probeRefs: Map[String, ActorRef] = probes.map { case (id, probe) => id.toString -> probe.ref } @@ -137,7 +137,7 @@ class DisabledEntityPassivationSpec val region = start() region ! Envelope(shard = 1, id = 1, message = "A") expectReceived(id = 1, message = "A") - expectNoMessage(id = 1, configuredIdleTimeout * 2) + expectNoMessage(id = 1, 1.second) } } } diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/IdleSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/IdleSpec.scala index 2b37e01b3e..56228c4593 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/IdleSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/IdleSpec.scala @@ -13,8 +13,7 @@ object IdleSpec { val config: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = idle - idle.timeout = 1s + default-idle-strategy.idle-entity.timeout = 1s } } """).withFallback(EntityPassivationSpec.config) diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastFrequentlyUsedSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastFrequentlyUsedSpec.scala index a9ab95de06..3d4a39eaba 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastFrequentlyUsedSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastFrequentlyUsedSpec.scala @@ -14,8 +14,11 @@ object LeastFrequentlyUsedSpec { val config: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-frequently-used - least-frequently-used.limit = 10 + strategy = lfu + lfu { + active-entity-limit = 10 + replacement.policy = least-frequently-used + } } } """).withFallback(EntityPassivationSpec.config) @@ -23,10 +26,15 @@ object LeastFrequentlyUsedSpec { val dynamicAgingConfig: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-frequently-used - least-frequently-used { - limit = 10 - dynamic-aging = on + strategy = lfuda + lfuda { + active-entity-limit = 10 + replacement { + policy = least-frequently-used + least-frequently-used { + dynamic-aging = on + } + } } } } @@ -35,17 +43,18 @@ object LeastFrequentlyUsedSpec { val idleConfig: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-frequently-used - least-frequently-used { - limit = 3 - idle.timeout = 1s + strategy = lfu-idle + lfu-idle { + active-entity-limit = 3 + replacement.policy = least-frequently-used + idle-entity.timeout = 1s } } } """).withFallback(EntityPassivationSpec.config) } -class LeastFrequentlyUsedEntityPassivationSpec +class LeastFrequentlyUsedSpec extends AbstractEntityPassivationSpec(LeastFrequentlyUsedSpec.config, expectedEntities = 40) { import EntityPassivationSpec.Entity.Envelope @@ -196,7 +205,7 @@ class LeastFrequentlyUsedEntityPassivationSpec } } -class LeastFrequentlyUsedWithDynamicAgingEntityPassivationSpec +class LeastFrequentlyUsedWithDynamicAgingSpec extends AbstractEntityPassivationSpec(LeastFrequentlyUsedSpec.dynamicAgingConfig, expectedEntities = 21) { import EntityPassivationSpec.Entity.Envelope @@ -273,7 +282,7 @@ class LeastFrequentlyUsedWithDynamicAgingEntityPassivationSpec } } -class LeastFrequentlyUsedWithIdleEntityPassivationSpec +class LeastFrequentlyUsedWithIdleSpec extends AbstractEntityPassivationSpec(LeastFrequentlyUsedSpec.idleConfig, expectedEntities = 3) { import EntityPassivationSpec.Entity.Envelope @@ -283,19 +292,17 @@ class LeastFrequentlyUsedWithIdleEntityPassivationSpec "passivate entities when they haven't seen messages for the configured timeout" in { val region = start() - val idleTimeout = settings.passivationStrategySettings.leastFrequentlyUsedSettings.idleSettings.get.timeout - val lastSendNanoTime1 = System.nanoTime() region ! Envelope(shard = 1, id = 1, message = "A") region ! Envelope(shard = 1, id = 2, message = "B") // keep entity 3 active to prevent idle passivation region ! Envelope(shard = 1, id = 3, message = "C") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "D") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "E") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) val lastSendNanoTime2 = System.nanoTime() region ! Envelope(shard = 1, id = 3, message = "F") @@ -307,13 +314,13 @@ class LeastFrequentlyUsedWithIdleEntityPassivationSpec expectReceived(id = 3, message = "F") val passivate1 = expectReceived(id = 1, message = Stop) val passivate2 = expectReceived(id = 2, message = Stop) - val passivate3 = expectReceived(id = 3, message = Stop, within = idleTimeout * 2) + val passivate3 = expectReceived(id = 3, message = Stop, within = configuredIdleTimeout * 2) // note: touched timestamps are when the shard receives the message, not the entity itself // so look at the time from before sending the last message until receiving the passivate message - (passivate1.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate2.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate3.nanoTime - lastSendNanoTime2).nanos should be > idleTimeout + (passivate1.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate2.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate3.nanoTime - lastSendNanoTime2).nanos should be > configuredIdleTimeout } } } diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastRecentlyUsedSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastRecentlyUsedSpec.scala index 557834e985..d70165190a 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastRecentlyUsedSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/LeastRecentlyUsedSpec.scala @@ -14,8 +14,11 @@ object LeastRecentlyUsedSpec { val config: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-recently-used - least-recently-used.limit = 10 + strategy = lru + lru { + active-entity-limit = 10 + replacement.policy = least-recently-used + } } } """).withFallback(EntityPassivationSpec.config) @@ -23,12 +26,17 @@ object LeastRecentlyUsedSpec { val segmentedConfig: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-recently-used - least-recently-used { - limit = 10 - segmented { - levels = 2 - proportions = [0.2, 0.8] + strategy = slru + slru { + active-entity-limit = 10 + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } } } } @@ -38,10 +46,11 @@ object LeastRecentlyUsedSpec { val idleConfig: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = least-recently-used - least-recently-used { - limit = 3 - idle.timeout = 1s + strategy = lru-idle + lru-idle { + active-entity-limit = 3 + replacement.policy = least-recently-used + idle-entity.timeout = 1s } } } @@ -238,19 +247,17 @@ class LeastRecentlyUsedWithIdleSpec "passivate entities when they haven't seen messages for the configured timeout" in { val region = start() - val idleTimeout = settings.passivationStrategySettings.leastRecentlyUsedSettings.idleSettings.get.timeout - val lastSendNanoTime1 = System.nanoTime() region ! Envelope(shard = 1, id = 1, message = "A") region ! Envelope(shard = 1, id = 2, message = "B") // keep entity 3 active to prevent idle passivation region ! Envelope(shard = 1, id = 3, message = "C") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "D") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "E") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) val lastSendNanoTime2 = System.nanoTime() region ! Envelope(shard = 1, id = 3, message = "F") @@ -262,13 +269,13 @@ class LeastRecentlyUsedWithIdleSpec expectReceived(id = 3, message = "F") val passivate1 = expectReceived(id = 1, message = Stop) val passivate2 = expectReceived(id = 2, message = Stop) - val passivate3 = expectReceived(id = 3, message = Stop, within = idleTimeout * 2) + val passivate3 = expectReceived(id = 3, message = Stop, within = configuredIdleTimeout * 2) // note: touched timestamps are when the shard receives the message, not the entity itself // so look at the time from before sending the last message until receiving the passivate message - (passivate1.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate2.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate3.nanoTime - lastSendNanoTime2).nanos should be > idleTimeout + (passivate1.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate2.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate3.nanoTime - lastSendNanoTime2).nanos should be > configuredIdleTimeout } } } diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/MostRecentlyUsedSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/MostRecentlyUsedSpec.scala index 9b915c0c04..09df025f89 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/MostRecentlyUsedSpec.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/MostRecentlyUsedSpec.scala @@ -14,8 +14,11 @@ object MostRecentlyUsedSpec { val config: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = most-recently-used - most-recently-used.limit = 10 + strategy = mru + mru { + active-entity-limit = 10 + replacement.policy = most-recently-used + } } } """).withFallback(EntityPassivationSpec.config) @@ -23,10 +26,11 @@ object MostRecentlyUsedSpec { val idleConfig: Config = ConfigFactory.parseString(""" akka.cluster.sharding { passivation { - strategy = most-recently-used - most-recently-used { - limit = 3 - idle.timeout = 1s + strategy = mru-idle + mru-idle { + active-entity-limit = 3 + replacement.policy = most-recently-used + idle-entity.timeout = 1s } } } @@ -141,7 +145,7 @@ class MostRecentlyUsedSpec extends AbstractEntityPassivationSpec(MostRecentlyUse } } -class MostRecentlyUsedWithIdleEntityPassivationSpec +class MostRecentlyUsedWithIdleSpec extends AbstractEntityPassivationSpec(MostRecentlyUsedSpec.idleConfig, expectedEntities = 3) { import EntityPassivationSpec.Entity.Envelope @@ -151,19 +155,17 @@ class MostRecentlyUsedWithIdleEntityPassivationSpec "passivate entities when they haven't seen messages for the configured timeout" in { val region = start() - val idleTimeout = settings.passivationStrategySettings.mostRecentlyUsedSettings.idleSettings.get.timeout - val lastSendNanoTime1 = System.nanoTime() region ! Envelope(shard = 1, id = 1, message = "A") region ! Envelope(shard = 1, id = 2, message = "B") // keep entity 3 active to prevent idle passivation region ! Envelope(shard = 1, id = 3, message = "C") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "D") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) region ! Envelope(shard = 1, id = 3, message = "E") - Thread.sleep((idleTimeout / 2).toMillis) + Thread.sleep((configuredIdleTimeout / 2).toMillis) val lastSendNanoTime2 = System.nanoTime() region ! Envelope(shard = 1, id = 3, message = "F") @@ -175,13 +177,13 @@ class MostRecentlyUsedWithIdleEntityPassivationSpec expectReceived(id = 3, message = "F") val passivate1 = expectReceived(id = 1, message = Stop) val passivate2 = expectReceived(id = 2, message = Stop) - val passivate3 = expectReceived(id = 3, message = Stop, within = idleTimeout * 2) + val passivate3 = expectReceived(id = 3, message = Stop, within = configuredIdleTimeout * 2) // note: touched timestamps are when the shard receives the message, not the entity itself // so look at the time from before sending the last message until receiving the passivate message - (passivate1.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate2.nanoTime - lastSendNanoTime1).nanos should be > idleTimeout - (passivate3.nanoTime - lastSendNanoTime2).nanos should be > idleTimeout + (passivate1.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate2.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate3.nanoTime - lastSendNanoTime2).nanos should be > configuredIdleTimeout } } } diff --git a/akka-docs/src/main/paradox/typed/cluster-sharding.md b/akka-docs/src/main/paradox/typed/cluster-sharding.md index b76dd2817a..d5cb16e661 100644 --- a/akka-docs/src/main/paradox/typed/cluster-sharding.md +++ b/akka-docs/src/main/paradox/typed/cluster-sharding.md @@ -289,7 +289,10 @@ The stop message is only sent locally, from the shard to the entity so does not ## Automatic Passivation Entities are automatically passivated based on a passivation strategy. The default passivation strategy is to -passivate idle entities when they haven't received a message within a specified interval. +[passivate idle entities](#idle-entity-passivation) when they haven't received a message within a specified interval, +and this is the current default strategy to maintain compatibility with earlier versions. It's recommended to switch to +a [passivation strategy with an active entity limit](#active-entity-limits) and a pre-configured default strategy is +provided. Active entity limits and idle entity timeouts can also be used together. Automatic passivation can be disabled by setting `akka.cluster.sharding.passivation.strategy = none`. It is disabled automatically if @ref:[Remembering Entities](#remembering-entities) is enabled. @@ -301,34 +304,78 @@ directly to the `ActorRef`, including messages that the actor sends to itself, a @@@ -Supported passivation strategies are: +### Idle entity passivation -### Idle passivation strategy - -The **idle** passivation strategy passivates entities when they have not received a message for a specified length of -time. This is the default strategy and is enabled automatically with a timeout of 2 minutes. Specify a different idle -timeout with configuration: +Idle entities can be automatically passivated when they have not received a message for a specified length of time. +This is currently the default strategy, for compatibility, and is enabled automatically with a timeout of 2 minutes. +Specify a different idle timeout with configuration: @@snip [passivation idle timeout](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-idle-timeout type=conf } -Or specify the idle timeout as a duration using the `withIdlePassivationStrategy` method on `ClusterShardingSettings`. +Or specify the idle timeout as a duration using the `withPassivationStrategy` method on `ClusterShardingSettings`. -### Least recently used passivation strategy +Idle entity timeouts can be enabled and configured for any passivation strategy. -The **least recently used** passivation strategy passivates those entities that have the least recent activity when the -number of active entities passes a specified limit. The configurable limit is for a whole shard region and is divided -evenly among the active shards in each region. Configure automatic passivation to use the least recently used -passivation strategy, and set the limit for active entities in a shard region: +### Active entity limits -@@snip [passivation least recently used](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-least-recently-used type=conf } +Automatic passivation strategies can limit the number of active entities. Limit-based passivation strategies use a +replacement policy to determine which active entities should be passivated when the active entity limit is exceeded. +The configurable limit is for a whole shard region and is divided evenly among the active shards in each region. -Or enable the least recently used passivation strategy and set the active entity limit using the -`withLeastRecentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +A recommended passivation strategy, which will become the new default passivation strategy in future versions of Akka +Cluster Sharding, can be enabled with configuration: -#### Segmented least recently used strategy +@@snip [passivation new default strategy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-new-default-strategy type=conf } -A variation of the least recently used passivation strategy can be enabled that divides the active entity space into -multiple segments to introduce frequency information into the strategy. Higher-level segments contain entities that +This default strategy uses a [segmented least recently used policy](#segmented-least-recently-used-policy). The active +entity limit can be configured: + +@@snip [passivation new default strategy configured](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-new-default-strategy-configured type=conf } + +Or using the `withActiveEntityLimit` method on `ClusterShardingSettings.PassivationStrategySettings`. + +An [idle entity timeout](#idle-entity-passivation) can also be enabled and configured for this strategy: + +@@snip [passivation new default strategy with idle](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-new-default-strategy-with-idle type=conf } + +Or using the `withIdleEntityPassivation` method on `ClusterShardingSettings.PassivationStrategySettings`. + +If the default strategy is not appropriate for particular workloads and access patterns, a [custom passivation +strategy](#custom-passivation-strategies) can be created with configurable replacement policies, active entity limits, +and idle entity timeouts. + +### Custom passivation strategies + +To configure a custom passivation strategy, create a configuration section for the strategy under +`akka.cluster.sharding.passivation` and select this strategy using the `strategy` setting. The strategy needs a +_replacement policy_ to be chosen, an _active entity limit_ to be set, and can optionally [passivate idle +entities](#idle-entity-passivation). For example, a custom strategy can be configured to use the [least recently used +policy](#least-recently-used-policy): + +@@snip [custom passivation strategy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #custom-passivation-strategy type=conf } + +The active entity limit and replacement policy can also be configured using the `withPassivationStrategy` method on +`ClusterShardingSettings`, passing custom `ClusterShardingSettings.PassivationStrategySettings`. + +### Least recently used policy + +The **least recently used** policy passivates those entities that have the least recent activity when the number of +active entities passes the specified limit. + +**When to use**: the least recently used policy should be used when access patterns are recency biased, where entities +that were recently accessed are likely to be accessed again. See the [segmented least recently used +policy](#segmented-least-recently-used-policy) for a variation that also distinguishes frequency of access. + +Configure a passivation strategy to use the least recently used policy: + +@@snip [LRU policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #lru-policy type=conf } + +Or using the `withLeastRecentlyUsedReplacement` method on `ClusterShardingSettings.PassivationStrategySettings`. + +#### Segmented least recently used policy + +A variation of the least recently used policy can be enabled that divides the active entity space into multiple +segments to introduce frequency information into the passivation strategy. Higher-level segments contain entities that have been accessed more often. The first segment is for entities that have only been accessed once, the second segment for entities that have been accessed at least twice, and so on. When an entity is accessed again, it will be promoted to the most recent position of the next-level or highest-level segment. The higher-level segments are limited, where @@ -338,80 +385,66 @@ to the level below. Only the least recently used entities in the lowest level wi higher levels are considered "protected", where entities will have additional opportunities to be accessed before being considered for passivation. -To configure a segmented least recently used (SLRU) strategy, with two levels and a protected segment limited to 80% of the total limit: +**When to use**: the segmented least recently used policy can be used for workloads where some entities are more +popular than others, to prioritize those entities that are accessed more frequently. -@@snip [passivation segmented least recently used](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-segmented-least-recently-used type=conf } +To configure a segmented least recently used (SLRU) policy, with two levels and a protected segment limited to 80% of +the total limit: -Or to configure a 4-level segmented least recently used (S4LRU) strategy, with 4 evenly divided levels: +@@snip [SLRU policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #slru-policy type=conf } -@@snip [passivation segmented least recently used](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-s4-least-recently-used type=conf } +Or to configure a 4-level segmented least recently used (S4LRU) policy, with 4 evenly divided levels: -Or using the `withLeastRecentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +@@snip [S4LRU policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #s4lru-policy type=conf } -#### Idle timeouts (with least recently used strategy) +Or using custom `ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings`. -Passivating idle entities (when they have not received a message for a specified length of time) can also be enabled by configuring the least recently used passivation strategy with an idle timeout: +### Most recently used policy -@@snip [passivation least recently used with idle](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-least-recently-used-with-idle type=conf } +The **most recently used** policy passivates those entities that have the most recent activity when the number of +active entities passes the specified limit. -Or enable the least recently used passivation strategy with both an active entity limit and an idle timeout using the -`withLeastRecentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +**When to use**: the most recently used policy is most useful when the older an entity is, the more likely that entity +will be accessed again; as seen in cyclic access patterns. -### Most recently used passivation strategy +Configure a passivation strategy to use the most recently used policy: -The **most recently used** passivation strategy passivates those entities that have the most recent activity when the -number of active entities passes a specified limit. The configurable limit is for a whole shard region and is divided -evenly among the active shards in each region. This strategy is most useful when the older an entity is, the more -likely that entity will be accessed again; as seen in cyclic access patterns. Configure automatic passivation to use -the most recently used passivation strategy, and set the limit for active entities in a shard region: +@@snip [MRU policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #mru-policy type=conf } -@@snip [passivation most recently used](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-most-recently-used type=conf } +Or using the `withMostRecentlyUsedReplacement` method on `ClusterShardingSettings.PassivationStrategySettings`. -Or enable the most recently used passivation strategy and set the active entity limit using the -`withMostRecentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +### Least frequently used policy -#### Idle timeouts (with most recently used strategy) +The **least frequently used** policy passivates those entities that have the least frequent activity when the number of +active entities passes the specified limit. -Passivating idle entities (when they have not received a message for a specified length of time) can also be enabled by configuring the most recently used passivation strategy with an idle timeout: +**When to use**: the least frequently used policy should be used when access patterns are frequency biased, where some +entities are much more popular than others and should be prioritized. See the [least frequently used with dynamic aging +policy](#least-frequently-used-with-dynamic-aging-policy) for a variation that also handles shifts in popularity. -@@snip [passivation most recently used with idle](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-most-recently-used-with-idle type=conf } +Configure automatic passivation to use the least frequently used policy: -Or enable the most recently used passivation strategy with both an active entity limit and an idle timeout using the -`withMostRecentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +@@snip [LFU policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #lfu-policy type=conf } -### Least frequently used passivation strategy +Or using the `withLeastFrequentlyUsedReplacement` method on `ClusterShardingSettings.PassivationStrategySettings`. -The **least frequently used** passivation strategy passivates those entities that have the least frequent activity when -the number of active entities passes a specified limit. The configurable limit is for a whole shard region and is -divided evenly among the active shards in each region. Configure automatic passivation to use the least frequently used -passivation strategy, and set the limit for active entities in a shard region: +#### Least frequently used with dynamic aging policy -@@snip [passivation least frequently used](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-least-frequently-used type=conf } +A variation of the least frequently used policy can be enabled that uses "dynamic aging" to adapt to shifts in the set +of popular entities, which is useful for smaller active entity limits and when shifts in popularity are common. If +entities were frequently accessed in the past but then become unpopular, they can still remain active for a long time +given their high frequency counts. Dynamic aging effectively increases the frequencies for recently accessed entities +so they can more easily become higher priority over entities that are no longer accessed. -Or enable the least frequently used passivation strategy and set the active entity limit using the -`withLeastFrequentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +**When to use**: the least frequently used with dynamic aging policy can be used when workloads are frequency biased +(there are some entities that are much more popular), but which entities are most popular changes over time. Shifts in +popularity can have more impact on a least frequently used policy if the active entity limit is small. -#### Dynamic aging for least frequently used strategy +Configure dynamic aging with the least frequently used policy: -A variation of the least frequently used passivation strategy can be enabled that uses "dynamic aging" to adapt to -shifts in the set of popular entities, which is useful for smaller active entity limits and when shifts in popularity -are common. If entities were frequently accessed in the past but then become unpopular, they can still remain active -for a long time given their high frequency counts. Dynamic aging effectively increases the frequencies for recently -accessed entities so they can more easily become higher priority over entities that are no longer accessed. Configure -dynamic aging with the least frequently used passivation strategy: +@@snip [LFUDA policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #lfuda-policy type=conf } -@@snip [passivation least frequently used with dynamic aging](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-least-frequently-used-with-dynamic-aging type=conf } - -Or when using the `withLeastFrequentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. - -#### Idle timeouts (with least frequently used strategy) - -Passivating idle entities (when they have not received a message for a specified length of time) can also be enabled by configuring the least frequently used passivation strategy with an idle timeout: - -@@snip [passivation least frequently used with idle](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-least-frequently-used-with-idle type=conf } - -Or enable the least frequently used passivation strategy with both an active entity limit and an idle timeout using the -`withLeastFrequentlyUsedPassivationStrategy` method on `ClusterShardingSettings`. +Or using custom `ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings`. ## Sharding State