From 61c27ae69cd79fef9c4875b5bf7da63b5ed668f2 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Tue, 15 Feb 2022 01:20:42 +1300 Subject: [PATCH] Add advanced passivation strategies (#31124) --- .../main/scala/akka/util/FrequencyList.scala | 18 + .../main/scala/akka/util/RecencyList.scala | 10 + .../akka/util/SegmentedRecencyList.scala | 5 + .../typed/ClusterShardingSettings.scala | 252 +++++- .../src/main/resources/reference.conf | 92 ++- .../sharding/ClusterShardingSettings.scala | 427 +++++++++- .../akka/cluster/sharding/ShardRegion.scala | 40 +- .../internal/EntityPassivationStrategy.scala | 766 ++++++++++++++++-- .../src/test/resources/adaptivity-trace.conf | 243 ++++++ .../test/resources/arc-trace-database.conf | 247 ++++-- .../src/test/resources/arc-trace-search.conf | 193 ++++- .../test/resources/lirs-trace-glimpse.conf | 247 ++++-- .../src/test/resources/lirs-trace-multi.conf | 301 +++++-- .../test/resources/lirs-trace-postgres.conf | 247 ++++-- .../src/test/resources/lirs2-trace-w106.conf | 247 ++++-- .../src/test/resources/reference.conf | 26 + .../src/test/resources/synthetic-loop.conf | 67 +- .../src/test/resources/synthetic-zipfian.conf | 260 ++++-- .../src/test/resources/text-moby-dick.conf | 193 ++++- .../test/resources/wikipedia-trace-2018.conf | 145 ++-- .../ClusterShardingSettingsSpec.scala | 401 ++++++++- .../sharding/passivation/CompositeSpec.scala | 479 +++++++++++ .../HillClimbingAdmissionOptimizerSpec.scala | 77 ++ .../passivation/simulator/AccessPattern.scala | 21 + .../passivation/simulator/Simulator.scala | 113 ++- .../simulator/SimulatorSettings.scala | 93 ++- .../main/paradox/typed/cluster-sharding.md | 66 +- 27 files changed, 4611 insertions(+), 665 deletions(-) create mode 100644 akka-cluster-sharding/src/test/resources/adaptivity-trace.conf create mode 100644 akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/CompositeSpec.scala create mode 100644 akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/HillClimbingAdmissionOptimizerSpec.scala diff --git a/akka-actor/src/main/scala/akka/util/FrequencyList.scala b/akka-actor/src/main/scala/akka/util/FrequencyList.scala index 0fa98efd6a..28c434c4f1 100644 --- a/akka-actor/src/main/scala/akka/util/FrequencyList.scala +++ b/akka-actor/src/main/scala/akka/util/FrequencyList.scala @@ -100,6 +100,24 @@ private[akka] final class FrequencyList[A](dynamicAging: Boolean, clock: OptionV def contains(value: A): Boolean = lookupNode.contains(value) + def leastFrequent: OptionVal[A] = frequency.getFirst match { + case OptionVal.Some(least) => + least.nodes.getFirst match { + case OptionVal.Some(first) => OptionVal.Some(first.value) + case _ => OptionVal.none + } + case _ => OptionVal.none + } + + def mostFrequent: OptionVal[A] = frequency.getLast match { + case OptionVal.Some(most) => + most.nodes.getLast match { + case OptionVal.Some(last) => OptionVal.Some(last.value) + case _ => OptionVal.none + } + case _ => OptionVal.none + } + def leastToMostFrequent: Iterator[A] = forwardIterator.map(_.value) def mostToLeastFrequent: Iterator[A] = backwardIterator.map(_.value) diff --git a/akka-actor/src/main/scala/akka/util/RecencyList.scala b/akka-actor/src/main/scala/akka/util/RecencyList.scala index 64ace986d4..a1e552d556 100644 --- a/akka-actor/src/main/scala/akka/util/RecencyList.scala +++ b/akka-actor/src/main/scala/akka/util/RecencyList.scala @@ -76,6 +76,16 @@ private[akka] final class RecencyList[A](clock: RecencyList.Clock) { def contains(value: A): Boolean = lookupNode.contains(value) + def leastRecent: OptionVal[A] = recency.getFirst match { + case OptionVal.Some(first) => OptionVal.Some(first.value) + case _ => OptionVal.none + } + + def mostRecent: OptionVal[A] = recency.getLast match { + case OptionVal.Some(last) => OptionVal.Some(last.value) + case _ => OptionVal.none + } + def leastToMostRecent: Iterator[A] = recency.forwardIterator.map(_.value) def mostToLeastRecent: Iterator[A] = recency.backwardIterator.map(_.value) diff --git a/akka-actor/src/main/scala/akka/util/SegmentedRecencyList.scala b/akka-actor/src/main/scala/akka/util/SegmentedRecencyList.scala index 5780b43819..9eabd0531f 100644 --- a/akka-actor/src/main/scala/akka/util/SegmentedRecencyList.scala +++ b/akka-actor/src/main/scala/akka/util/SegmentedRecencyList.scala @@ -89,6 +89,11 @@ private[akka] final class SegmentedRecencyList[A]( def contains(value: A): Boolean = lookupNode.contains(value) + def leastRecent: OptionVal[A] = segments(lowest).getFirst match { + case OptionVal.Some(first) => OptionVal.Some(first.value) + case _ => OptionVal.none + } + def leastToMostRecentOf(level: Int): Iterator[A] = segments(level).forwardIterator.map(_.value) def removeLeastRecentOverLimit(): immutable.Seq[A] = { 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 d03018dd57..63ee1ecc99 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 @@ -171,13 +171,33 @@ object ClusterShardingSettings { val idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], val activeEntityLimit: Option[Int], val replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + val admissionSettings: Option[PassivationStrategySettings.AdmissionSettings], private[akka] val oldSettingUsed: Boolean) { + private[akka] def this( + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + oldSettingUsed: Boolean) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, admissionSettings = None, oldSettingUsed) + + def this( + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + admissionSettings: Option[PassivationStrategySettings.AdmissionSettings]) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, admissionSettings, oldSettingUsed = false) + def this( idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], activeEntityLimit: Option[Int], replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings]) = - this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed = false) + this( + idleEntitySettings, + activeEntityLimit, + replacementPolicySettings, + admissionSettings = None, + oldSettingUsed = false) import PassivationStrategySettings._ @@ -213,19 +233,29 @@ object ClusterShardingSettings { def withLeastFrequentlyUsedReplacement(): PassivationStrategySettings = withReplacementPolicy(LeastFrequentlyUsedSettings.defaults) + def withAdmission(settings: AdmissionSettings): PassivationStrategySettings = + copy(admissionSettings = Some(settings)) + private[akka] def withOldIdleStrategy(timeout: FiniteDuration): PassivationStrategySettings = copy( idleEntitySettings = Some(new IdleSettings(timeout, None)), activeEntityLimit = None, replacementPolicySettings = None, + admissionSettings = None, oldSettingUsed = true) private def copy( idleEntitySettings: Option[IdleSettings] = idleEntitySettings, activeEntityLimit: Option[Int] = activeEntityLimit, replacementPolicySettings: Option[PolicySettings] = replacementPolicySettings, + admissionSettings: Option[AdmissionSettings] = admissionSettings, oldSettingUsed: Boolean = oldSettingUsed): PassivationStrategySettings = - new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed) + new PassivationStrategySettings( + idleEntitySettings, + activeEntityLimit, + replacementPolicySettings, + admissionSettings, + oldSettingUsed) } /** @@ -239,6 +269,7 @@ object ClusterShardingSettings { idleEntitySettings = None, activeEntityLimit = None, replacementPolicySettings = None, + admissionSettings = None, oldSettingUsed = false) val disabled: PassivationStrategySettings = defaults @@ -248,6 +279,7 @@ object ClusterShardingSettings { classic.idleEntitySettings.map(IdleSettings.apply), classic.activeEntityLimit, classic.replacementPolicySettings.map(PolicySettings.apply), + classic.admissionSettings.map(AdmissionSettings.apply), classic.oldSettingUsed) def toClassic(settings: PassivationStrategySettings): ClassicPassivationStrategySettings = @@ -255,6 +287,7 @@ object ClusterShardingSettings { settings.idleEntitySettings.map(IdleSettings.toClassic), settings.activeEntityLimit, settings.replacementPolicySettings.map(PolicySettings.toClassic), + settings.admissionSettings.map(AdmissionSettings.toClassic), settings.oldSettingUsed) object IdleSettings { @@ -390,6 +423,221 @@ object ClusterShardingSettings { new LeastFrequentlyUsedSettings(dynamicAging) } + object AdmissionSettings { + val defaults = new AdmissionSettings(filter = None, window = None) + + object FilterSettings { + def apply(classic: ClassicPassivationStrategySettings.AdmissionSettings.FilterSettings): FilterSettings = + classic match { + case classic: ClassicPassivationStrategySettings.AdmissionSettings.FrequencySketchSettings => + FrequencySketchSettings(classic) + } + + def toClassic(settings: FilterSettings): ClassicPassivationStrategySettings.AdmissionSettings.FilterSettings = + settings match { + case settings: FrequencySketchSettings => FrequencySketchSettings.toClassic(settings) + } + } + + sealed trait FilterSettings + + object FrequencySketchSettings { + val defaults = + new FrequencySketchSettings(depth = 4, counterBits = 4, widthMultiplier = 4, resetMultiplier = 10.0) + + def apply(classic: ClassicPassivationStrategySettings.AdmissionSettings.FrequencySketchSettings) + : FrequencySketchSettings = + new FrequencySketchSettings( + classic.depth, + classic.counterBits, + classic.widthMultiplier, + classic.resetMultiplier) + + def toClassic(settings: FrequencySketchSettings) + : ClassicPassivationStrategySettings.AdmissionSettings.FrequencySketchSettings = + new ClassicPassivationStrategySettings.AdmissionSettings.FrequencySketchSettings( + settings.depth, + settings.counterBits, + settings.widthMultiplier, + settings.resetMultiplier) + } + + final class FrequencySketchSettings( + val depth: Int, + val counterBits: Int, + val widthMultiplier: Int, + val resetMultiplier: Double) + extends FilterSettings { + + def withDepth(depth: Int): FrequencySketchSettings = + copy(depth = depth) + + def withCounterBits(bits: Int): FrequencySketchSettings = + copy(counterBits = bits) + + def withWidthMultiplier(multiplier: Int): FrequencySketchSettings = + copy(widthMultiplier = multiplier) + + def withResetMultiplier(multiplier: Double): FrequencySketchSettings = + copy(resetMultiplier = multiplier) + + private def copy( + depth: Int = depth, + counterBits: Int = counterBits, + widthMultiplier: Int = widthMultiplier, + resetMultiplier: Double = resetMultiplier): FrequencySketchSettings = + new FrequencySketchSettings(depth, counterBits, widthMultiplier, resetMultiplier) + + } + + object WindowSettings { + val defaults: WindowSettings = new WindowSettings( + initialProportion = 0.01, + minimumProportion = 0.01, + maximumProportion = 1.0, + optimizer = None, + policy = None) + + def apply(classic: ClassicPassivationStrategySettings.AdmissionSettings.WindowSettings): WindowSettings = + new WindowSettings( + classic.initialProportion, + classic.minimumProportion, + classic.maximumProportion, + classic.optimizer.map(OptimizerSettings.apply), + classic.policy.map(PolicySettings.apply)) + + def toClassic(settings: WindowSettings): ClassicPassivationStrategySettings.AdmissionSettings.WindowSettings = + new ClassicPassivationStrategySettings.AdmissionSettings.WindowSettings( + settings.initialProportion, + settings.minimumProportion, + settings.maximumProportion, + settings.optimizer.map(OptimizerSettings.toClassic), + settings.policy.map(PolicySettings.toClassic)) + } + + final class WindowSettings( + val initialProportion: Double, + val minimumProportion: Double, + val maximumProportion: Double, + val optimizer: Option[OptimizerSettings], + val policy: Option[PolicySettings]) { + + def withInitialProportion(proportion: Double): WindowSettings = + copy(initialProportion = proportion) + + def withMinimumProportion(proportion: Double): WindowSettings = + copy(minimumProportion = proportion) + + def withMaximumProportion(proportion: Double): WindowSettings = + copy(maximumProportion = proportion) + + def withOptimizer(settings: OptimizerSettings): WindowSettings = + copy(optimizer = Some(settings)) + + def withPolicy(settings: PolicySettings): WindowSettings = + copy(policy = Some(settings)) + + private def copy( + initialProportion: Double = initialProportion, + minimumProportion: Double = minimumProportion, + maximumProportion: Double = maximumProportion, + optimizer: Option[OptimizerSettings] = optimizer, + policy: Option[PolicySettings] = policy): WindowSettings = + new WindowSettings(initialProportion, minimumProportion, maximumProportion, optimizer, policy) + } + + object OptimizerSettings { + def apply(classic: ClassicPassivationStrategySettings.AdmissionSettings.OptimizerSettings): OptimizerSettings = + classic match { + case classic: ClassicPassivationStrategySettings.AdmissionSettings.HillClimbingSettings => + HillClimbingSettings(classic) + } + + def toClassic( + settings: OptimizerSettings): ClassicPassivationStrategySettings.AdmissionSettings.OptimizerSettings = + settings match { + case settings: HillClimbingSettings => HillClimbingSettings.toClassic(settings) + } + } + + sealed trait OptimizerSettings + + object HillClimbingSettings { + val defaults: HillClimbingSettings = new HillClimbingSettings( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98) + + def apply( + classic: ClassicPassivationStrategySettings.AdmissionSettings.HillClimbingSettings): HillClimbingSettings = + new HillClimbingSettings( + classic.adjustMultiplier, + classic.initialStep, + classic.restartThreshold, + classic.stepDecay) + + def toClassic( + settings: HillClimbingSettings): ClassicPassivationStrategySettings.AdmissionSettings.HillClimbingSettings = + new ClassicPassivationStrategySettings.AdmissionSettings.HillClimbingSettings( + settings.adjustMultiplier, + settings.initialStep, + settings.restartThreshold, + settings.stepDecay) + } + + final class HillClimbingSettings( + val adjustMultiplier: Double, + val initialStep: Double, + val restartThreshold: Double, + val stepDecay: Double) + extends OptimizerSettings { + + def withAdjustMultiplier(multiplier: Double): HillClimbingSettings = + copy(adjustMultiplier = multiplier) + + def withInitialStep(step: Double): HillClimbingSettings = + copy(initialStep = step) + + def withRestartThreshold(threshold: Double): HillClimbingSettings = + copy(restartThreshold = threshold) + + def withStepDecay(decay: Double): HillClimbingSettings = + copy(stepDecay = decay) + + private def copy( + adjustMultiplier: Double = adjustMultiplier, + initialStep: Double = initialStep, + restartThreshold: Double = restartThreshold, + stepDecay: Double = stepDecay): HillClimbingSettings = + new HillClimbingSettings(adjustMultiplier, initialStep, restartThreshold, stepDecay) + } + + def apply(classic: ClassicPassivationStrategySettings.AdmissionSettings): AdmissionSettings = + new AdmissionSettings(classic.filter.map(FilterSettings.apply), classic.window.map(WindowSettings.apply)) + + def toClassic(settings: AdmissionSettings): ClassicPassivationStrategySettings.AdmissionSettings = + new ClassicPassivationStrategySettings.AdmissionSettings( + settings.filter.map(FilterSettings.toClassic), + settings.window.map(WindowSettings.toClassic)) + } + + final class AdmissionSettings( + val filter: Option[AdmissionSettings.FilterSettings], + val window: Option[AdmissionSettings.WindowSettings]) { + + def withFilter(settings: AdmissionSettings.FilterSettings): AdmissionSettings = + copy(filter = Some(settings)) + + def withWindow(settings: AdmissionSettings.WindowSettings): AdmissionSettings = + copy(window = Some(settings)) + + private def copy( + filter: Option[AdmissionSettings.FilterSettings] = filter, + window: Option[AdmissionSettings.WindowSettings] = window): AdmissionSettings = + new AdmissionSettings(filter, window) + } + private[akka] def oldDefault(idleTimeout: FiniteDuration): PassivationStrategySettings = disabled.withOldIdleStrategy(idleTimeout) } diff --git a/akka-cluster-sharding/src/main/resources/reference.conf b/akka-cluster-sharding/src/main/resources/reference.conf index 61fdf8a5a7..678e8c5030 100644 --- a/akka-cluster-sharding/src/main/resources/reference.conf +++ b/akka-cluster-sharding/src/main/resources/reference.conf @@ -53,12 +53,22 @@ akka.cluster.sharding { } # Recommended default strategy for automatic passivation with an active entity limit. - # Configured with a segmented least recently used (SLRU) replacement policy. + # Configured with an adaptive recency-based admission window, a frequency-based admission filter, and + # a segmented least recently used (SLRU) replacement policy for the main active entity tracking. default-strategy { # Default limit of 100k active entities in a shard region (in a cluster node). active-entity-limit = 100000 - # Segmented LRU replacement policy with an 80% "protected" level by default. + # Admisson window with LRU policy and adaptive sizing, and a frequency sketch admission filter to the main area. + admission { + window { + policy = least-recently-used + optimizer = hill-climbing + } + filter = frequency-sketch + } + + # Main area with segmented LRU replacement policy with an 80% "protected" level by default. replacement { policy = least-recently-used least-recently-used { @@ -118,6 +128,84 @@ akka.cluster.sharding { dynamic-aging = off } } + + # An optional admission area, with a window for newly and recently activated entities, and an admission filter + # to determine whether a candidate should be admitted to the main area of the passivation strategy. + admission { + # An optional window area, where newly created entities will be admitted initially, and when evicted + # from the window area have an opportunity to move to the main area based on the admission filter. + window { + # The initial sizing for the window area (if enabled), as a fraction of the total active entity limit. + proportion = 0.01 + + # The minimum adaptive sizing for the window area, as a fraction of the total active entity limit. + # Only applies when an adaptive window optimizer is enabled. + minimum-proportion = 0.01 + + # The maximum adaptive sizing for the window area, as a fraction of the total active entity limit. + # Only applies when an adaptive window optimizer is enabled. + maximum-proportion = 1.0 + + # Adaptive optimizer to use for dynamically resizing the window area. Possible values are: + # - "hill-climbing" + # Set to "none" or "off" to disable adaptive sizing of the window area. + optimizer = off + + # A window proportion optimizer using a simple hill-climbing algorithm. + hill-climbing { + # Multiplier of the active entity limit for how often (in accesses) to adjust the window proportion. + adjust-multiplier = 10.0 + + # The size of the initial step to take (also used when the climbing restarts). + initial-step = 0.0625 + + # A threshold for the change in active rate (hit rate) to restart climbing. + restart-threshold = 0.05 + + # The decay ratio applied on each climbing step. + step-decay = 0.98 + } + + # Replacement policy to use for the window area. + # Entities that are evicted from the window area may move to the main area, based on the admission filter. + # Possible values are the same as for the main replacement policy. + # Set to "none" or "off" to disable the window area. + policy = none + + least-recently-used { + segmented { + levels = none + proportions = [] + } + } + + most-recently-used {} + + least-frequently-used { + dynamic-aging = off + } + } + + # The admission filter for the main area of the passivation strategy. Possible values are: + # - "frequency-sketch" + # Set to "none" or "off" to disable the admission filter and always admit to the main area. + filter = none + + # An admission filter based on a frequency sketch (a variation of a count-min sketch). + frequency-sketch { + # The depth of the frequency sketch (the number of hash functions). + depth = 4 + + # The size of the frequency counters in bits: 2, 4, 8, 16, 32, or 64 bits. + counter-bits = 4 + + # Multiplier of the active entity limit for the width of the frequency sketch. + width-multiplier = 4 + + # Multiplier of the active entity limit for how often the reset operation of the frequency sketch is applied. + reset-multiplier = 10.0 + } + } } } 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 e89465c2eb..1ae3d9e3ed 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 @@ -137,13 +137,33 @@ object ClusterShardingSettings { val idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], val activeEntityLimit: Option[Int], val replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + val admissionSettings: Option[PassivationStrategySettings.AdmissionSettings], private[akka] val oldSettingUsed: Boolean) { + private[akka] def this( + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + oldSettingUsed: Boolean) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, admissionSettings = None, oldSettingUsed) + + def this( + idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], + activeEntityLimit: Option[Int], + replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings], + admissionSettings: Option[PassivationStrategySettings.AdmissionSettings]) = + this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, admissionSettings, oldSettingUsed = false) + def this( idleEntitySettings: Option[PassivationStrategySettings.IdleSettings], activeEntityLimit: Option[Int], replacementPolicySettings: Option[PassivationStrategySettings.PolicySettings]) = - this(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed = false) + this( + idleEntitySettings, + activeEntityLimit, + replacementPolicySettings, + admissionSettings = None, + oldSettingUsed = false) import PassivationStrategySettings._ @@ -179,19 +199,29 @@ object ClusterShardingSettings { def withLeastFrequentlyUsedReplacement(): PassivationStrategySettings = withReplacementPolicy(LeastFrequentlyUsedSettings.defaults) + def withAdmission(settings: AdmissionSettings): PassivationStrategySettings = + copy(admissionSettings = Some(settings)) + private[akka] def withOldIdleStrategy(timeout: FiniteDuration): PassivationStrategySettings = copy( idleEntitySettings = Some(new IdleSettings(timeout, None)), activeEntityLimit = None, replacementPolicySettings = None, + admissionSettings = None, oldSettingUsed = true) private def copy( idleEntitySettings: Option[IdleSettings] = idleEntitySettings, activeEntityLimit: Option[Int] = activeEntityLimit, replacementPolicySettings: Option[PolicySettings] = replacementPolicySettings, + admissionSettings: Option[AdmissionSettings] = admissionSettings, oldSettingUsed: Boolean = oldSettingUsed): PassivationStrategySettings = - new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings, oldSettingUsed) + new PassivationStrategySettings( + idleEntitySettings, + activeEntityLimit, + replacementPolicySettings, + admissionSettings, + oldSettingUsed) } /** @@ -203,6 +233,7 @@ object ClusterShardingSettings { idleEntitySettings = None, activeEntityLimit = None, replacementPolicySettings = None, + admissionSettings = None, oldSettingUsed = false) val disabled: PassivationStrategySettings = defaults @@ -340,6 +371,195 @@ object ClusterShardingSettings { new LeastFrequentlyUsedSettings(dynamicAging) } + object AdmissionSettings { + val defaults = new AdmissionSettings(filter = None, window = None) + + object FilterSettings { + def optional(config: Config): Option[FilterSettings] = + toRootLowerCase(config.getString("filter")) match { + case "off" | "none" => None + case "frequency-sketch" => Some(FrequencySketchSettings(config.getConfig("frequency-sketch"))) + case _ => None + } + } + + sealed trait FilterSettings + + object FrequencySketchSettings { + val defaults = + new FrequencySketchSettings(depth = 4, counterBits = 4, widthMultiplier = 4, resetMultiplier = 10.0) + + def apply(config: Config): FrequencySketchSettings = { + val depth = config.getInt("depth") + val counterBits = config.getInt("counter-bits") + val widthMultiplier = config.getInt("width-multiplier") + val resetMultiplier = config.getDouble("reset-multiplier") + new FrequencySketchSettings(depth, counterBits, widthMultiplier, resetMultiplier) + } + } + + final class FrequencySketchSettings( + val depth: Int, + val counterBits: Int, + val widthMultiplier: Int, + val resetMultiplier: Double) + extends FilterSettings { + + def withDepth(depth: Int): FrequencySketchSettings = + copy(depth = depth) + + def withCounterBits(bits: Int): FrequencySketchSettings = + copy(counterBits = bits) + + def withWidthMultiplier(multiplier: Int): FrequencySketchSettings = + copy(widthMultiplier = multiplier) + + def withResetMultiplier(multiplier: Double): FrequencySketchSettings = + copy(resetMultiplier = multiplier) + + private def copy( + depth: Int = depth, + counterBits: Int = counterBits, + widthMultiplier: Int = widthMultiplier, + resetMultiplier: Double = resetMultiplier): FrequencySketchSettings = + new FrequencySketchSettings(depth, counterBits, widthMultiplier, resetMultiplier) + + } + + object WindowSettings { + val defaults: WindowSettings = new WindowSettings( + initialProportion = 0.01, + minimumProportion = 0.01, + maximumProportion = 1.0, + optimizer = None, + policy = None) + + def apply(config: Config): WindowSettings = { + val initialProportion = config.getDouble("proportion") + val minimumProportion = config.getDouble("minimum-proportion") + val maximumProportion = config.getDouble("maximum-proportion") + val optimizer = OptimizerSettings.optional(config) + val policy = PolicySettings.optional(config) + new WindowSettings(initialProportion, minimumProportion, maximumProportion, optimizer, policy) + } + + def optional(config: Config): Option[WindowSettings] = + toRootLowerCase(config.getString("policy")) match { + case "off" | "none" => None + case _ => Some(WindowSettings(config)) + } + } + + final class WindowSettings( + val initialProportion: Double, + val minimumProportion: Double, + val maximumProportion: Double, + val optimizer: Option[OptimizerSettings], + val policy: Option[PolicySettings]) { + + def withInitialProportion(proportion: Double): WindowSettings = + copy(initialProportion = proportion) + + def withMinimumProportion(proportion: Double): WindowSettings = + copy(minimumProportion = proportion) + + def withMaximumProportion(proportion: Double): WindowSettings = + copy(maximumProportion = proportion) + + def withOptimizer(settings: OptimizerSettings): WindowSettings = + copy(optimizer = Some(settings)) + + def withPolicy(settings: PolicySettings): WindowSettings = + copy(policy = Some(settings)) + + private def copy( + initialProportion: Double = initialProportion, + minimumProportion: Double = minimumProportion, + maximumProportion: Double = maximumProportion, + optimizer: Option[OptimizerSettings] = optimizer, + policy: Option[PolicySettings] = policy): WindowSettings = + new WindowSettings(initialProportion, minimumProportion, maximumProportion, optimizer, policy) + } + + object OptimizerSettings { + def optional(config: Config): Option[OptimizerSettings] = + toRootLowerCase(config.getString("optimizer")) match { + case "off" | "none" => None + case "hill-climbing" => Some(HillClimbingSettings(config.getConfig("hill-climbing"))) + case _ => None + } + } + + sealed trait OptimizerSettings + + object HillClimbingSettings { + val defaults: HillClimbingSettings = new HillClimbingSettings( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98) + + def apply(config: Config): HillClimbingSettings = { + val adjustMultiplier = config.getDouble("adjust-multiplier") + val initialStep = config.getDouble("initial-step") + val restartThreshold = config.getDouble("restart-threshold") + val stepDecay = config.getDouble("step-decay") + new HillClimbingSettings(adjustMultiplier, initialStep, restartThreshold, stepDecay) + } + } + + final class HillClimbingSettings( + val adjustMultiplier: Double, + val initialStep: Double, + val restartThreshold: Double, + val stepDecay: Double) + extends OptimizerSettings { + + def withAdjustMultiplier(multiplier: Double): HillClimbingSettings = + copy(adjustMultiplier = multiplier) + + def withInitialStep(step: Double): HillClimbingSettings = + copy(initialStep = step) + + def withRestartThreshold(threshold: Double): HillClimbingSettings = + copy(restartThreshold = threshold) + + def withStepDecay(decay: Double): HillClimbingSettings = + copy(stepDecay = decay) + + private def copy( + adjustMultiplier: Double = adjustMultiplier, + initialStep: Double = initialStep, + restartThreshold: Double = restartThreshold, + stepDecay: Double = stepDecay): HillClimbingSettings = + new HillClimbingSettings(adjustMultiplier, initialStep, restartThreshold, stepDecay) + } + + def optional(config: Config): Option[AdmissionSettings] = { + val filter = FilterSettings.optional(config) + val window = WindowSettings.optional(config.getConfig("window")) + if (filter.isDefined || window.isDefined) + Some(new AdmissionSettings(filter, window)) + else None + } + } + + final class AdmissionSettings( + val filter: Option[AdmissionSettings.FilterSettings], + val window: Option[AdmissionSettings.WindowSettings]) { + + def withFilter(settings: AdmissionSettings.FilterSettings): AdmissionSettings = + copy(filter = Some(settings)) + + def withWindow(settings: AdmissionSettings.WindowSettings): AdmissionSettings = + copy(window = Some(settings)) + + private def copy( + filter: Option[AdmissionSettings.FilterSettings] = filter, + window: Option[AdmissionSettings.WindowSettings] = window): AdmissionSettings = + new AdmissionSettings(filter, window) + } + /** * API MAY CHANGE: Settings and configuration for passivation strategies may change after additional * testing and feedback. @@ -357,7 +577,12 @@ object ClusterShardingSettings { case _ => Some(strategyConfig.getInt("active-entity-limit")) } val replacementPolicySettings = PolicySettings.optional(strategyConfig.getConfig("replacement")) - new PassivationStrategySettings(idleEntitySettings, activeEntityLimit, replacementPolicySettings) + val admissionSettings = AdmissionSettings.optional(strategyConfig.getConfig("admission")) + new PassivationStrategySettings( + idleEntitySettings, + activeEntityLimit, + replacementPolicySettings, + admissionSettings) } } @@ -465,6 +690,101 @@ object ClusterShardingSettings { idle: Option[IdlePassivationStrategy]) extends PassivationStrategy + /** + * INTERNAL API + */ + @InternalApi + private[akka] object CompositePassivationStrategy { + object AdmissionFilter { + def apply(filterSettings: Option[PassivationStrategySettings.AdmissionSettings.FilterSettings]): AdmissionFilter = + filterSettings match { + case Some(settings: PassivationStrategySettings.AdmissionSettings.FrequencySketchSettings) => + FrequencySketchAdmissionFilter( + widthMultiplier = settings.widthMultiplier, + resetMultiplier = settings.resetMultiplier, + depth = settings.depth, + counterBits = settings.counterBits) + case _ => AlwaysAdmissionFilter + } + } + + sealed trait AdmissionFilter + + case object AlwaysAdmissionFilter extends AdmissionFilter + + case class FrequencySketchAdmissionFilter( + widthMultiplier: Int, + resetMultiplier: Double, + depth: Int, + counterBits: Int) + extends AdmissionFilter + + object AdmissionOptimizer { + def apply(optimizerSettings: Option[PassivationStrategySettings.AdmissionSettings.OptimizerSettings]) + : AdmissionOptimizer = + optimizerSettings match { + case Some(settings: PassivationStrategySettings.AdmissionSettings.HillClimbingSettings) => + HillClimbingAdmissionOptimizer( + adjustMultiplier = settings.adjustMultiplier, + initialStep = settings.initialStep, + restartThreshold = settings.restartThreshold, + stepDecay = settings.stepDecay) + case _ => NoAdmissionOptimizer + } + } + + sealed trait AdmissionOptimizer + + case object NoAdmissionOptimizer extends AdmissionOptimizer + + case class HillClimbingAdmissionOptimizer( + adjustMultiplier: Double, + initialStep: Double, + restartThreshold: Double, + stepDecay: Double) + extends AdmissionOptimizer + + def apply( + limit: Int, + mainSettings: Option[PassivationStrategySettings.PolicySettings], + admissionSettings: PassivationStrategySettings.AdmissionSettings, + idle: Option[IdlePassivationStrategy]): CompositePassivationStrategy = { + val mainStrategy = PassivationStrategy(mainSettings, limit = 0, idle = None) + val windowStrategy = PassivationStrategy(admissionSettings.window.flatMap(_.policy), limit = 0, idle = None) + val initialWindowProportion = admissionSettings.window.fold(0.0)(_.initialProportion) + val minimumWindowProportion = admissionSettings.window.fold(0.0)(_.minimumProportion) + val maximumWindowProportion = admissionSettings.window.fold(0.0)(_.maximumProportion) + val windowOptimizer = AdmissionOptimizer(admissionSettings.window.flatMap(_.optimizer)) + val admissionFilter = AdmissionFilter(admissionSettings.filter) + CompositePassivationStrategy( + limit, + mainStrategy, + windowStrategy, + initialWindowProportion, + minimumWindowProportion, + maximumWindowProportion, + windowOptimizer, + admissionFilter, + idle) + } + } + + /** + * INTERNAL API + */ + @InternalApi + private[akka] case class CompositePassivationStrategy( + limit: Int, + mainStrategy: PassivationStrategy, + windowStrategy: PassivationStrategy, + initialWindowProportion: Double, + minimumWindowProportion: Double, + maximumWindowProportion: Double, + windowOptimizer: CompositePassivationStrategy.AdmissionOptimizer, + admissionFilter: CompositePassivationStrategy.AdmissionFilter, + idle: Option[IdlePassivationStrategy]) + extends PassivationStrategy + /** * INTERNAL API * Determine the passivation strategy to use from settings. @@ -481,18 +801,103 @@ object ClusterShardingSettings { } 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) + settings.passivationStrategySettings.admissionSettings match { + case Some(admission) => + val main = settings.passivationStrategySettings.replacementPolicySettings + CompositePassivationStrategy(limit, main, admission, idle) + case _ => + PassivationStrategy(settings.passivationStrategySettings.replacementPolicySettings, limit, idle) } case _ => idle.getOrElse(NoPassivationStrategy) } } + + def apply( + policySettings: Option[PassivationStrategySettings.PolicySettings], + limit: Int, + idle: Option[IdlePassivationStrategy]): PassivationStrategy = policySettings 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) + } + + def describe(strategy: PassivationStrategy): String = { + import akka.util.PrettyDuration._ + strategy match { + case NoPassivationStrategy => + "disabled" + case IdlePassivationStrategy(timeout, interval) => + s"idle entities after [${timeout.pretty}], checked every [${interval.pretty}]" + case LeastRecentlyUsedPassivationStrategy(limit, segmented, idle) => + s"least recently used entities" + + (if (limit > 0) s" when over [$limit] entities" else "") + + (if (segmented.nonEmpty) { + val levels = segmented.size + val proportions = segmented.map(proportion => "%.2f".format(proportion)).mkString(", ") + s" (segmented with [$levels] levels with proportions of [$proportions])" + } else "") + + idle.fold("")(idle => " and " + describe(idle)) + case MostRecentlyUsedPassivationStrategy(limit, idle) => + s"most recently used entities" + + (if (limit > 0) s" when over [$limit] entities" else "") + + idle.fold("")(idle => " and " + describe(idle)) + case LeastFrequentlyUsedPassivationStrategy(limit, dynamicAging, idle) => + s"least frequently used entities" + + (if (limit > 0) s" when over [$limit] entities" else "") + + (if (dynamicAging) " (with dynamic aging)" else "") + + idle.fold("")(idle => " and " + describe(idle)) + case CompositePassivationStrategy( + limit, + mainStrategy, + windowStrategy, + initialWindowProportion, + minimumWindowProportion, + maximumWindowProportion, + windowOptimizer, + admissionFilter, + idle) => + val describeWindow = windowStrategy match { + case NoPassivationStrategy => "no admission window" + case _ => + s"admission window (${describe(windowStrategy)})" + + (windowOptimizer match { + case CompositePassivationStrategy.NoAdmissionOptimizer => + s" with proportion [$initialWindowProportion]" + case CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier, + initialStep, + restartThreshold, + stepDecay) => + s" with proportions [initial = $initialWindowProportion, min = $minimumWindowProportion, max = $maximumWindowProportion]" + + " adapting with hill-climbing optimizer [" + + s"adjust multiplier = $adjustMultiplier, " + + s"initial step = $initialStep, " + + s"restart threshold = $restartThreshold, " + + s"step decay = $stepDecay]" + }) + } + val describeFilter = admissionFilter match { + case CompositePassivationStrategy.AlwaysAdmissionFilter => "always admit" + case CompositePassivationStrategy.FrequencySketchAdmissionFilter( + widthMultiplier, + resetMultiplier, + depth, + counterBits) => + "admit using frequency sketch [" + + s"width multiplier = $widthMultiplier, " + + s"reset multiplier = $resetMultiplier, " + + s"depth = $depth, " + + s"counter bits = $counterBits]" + } + s"composite strategy with limit of [$limit] active entities, " + + s"$describeWindow, $describeFilter, main (${describe(mainStrategy)})" + + idle.fold("")(idle => " and " + describe(idle)) + } + } } class TuningParameters( 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 8fbbfe323a..207d3e4947 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 @@ -5,7 +5,6 @@ package akka.cluster.sharding import java.net.URLEncoder - import scala.annotation.tailrec import scala.collection.immutable import scala.concurrent.{ Future, Promise } @@ -23,13 +22,13 @@ import akka.cluster.ClusterSettings import akka.cluster.ClusterSettings.DataCenter import akka.cluster.Member import akka.cluster.MemberStatus +import akka.cluster.sharding.ClusterShardingSettings.PassivationStrategy import akka.cluster.sharding.Shard.ShardStats import akka.cluster.sharding.internal.RememberEntitiesProvider import akka.event.Logging import akka.pattern.ask import akka.pattern.pipe import akka.util.MessageBufferMap -import akka.util.PrettyDuration import akka.util.Timeout /** @@ -689,42 +688,13 @@ private[akka] class ShardRegion( if (settings.rememberEntities) { log.debug("{}: Entities will not be passivated automatically because 'rememberEntities' is enabled.", typeName) } else { - logPassivation(settings.passivationStrategy) + log.info( + "{}: Automatic entity passivation: {}", + typeName, + PassivationStrategy.describe(settings.passivationStrategy)) } } - private def logPassivation(strategy: ClusterShardingSettings.PassivationStrategy): Unit = - strategy match { - case ClusterShardingSettings.IdlePassivationStrategy(timeout, interval) => - log.info( - "{}: Idle entities will be passivated after [{}], checked every [{}]", - typeName, - PrettyDuration.format(timeout), - PrettyDuration.format(interval)) - case ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit, segmented, idle) => - log.info("{}: Least recently used entities will be passivated when over [{}] entities", typeName, limit) - if (segmented.nonEmpty) { - log.info( - "{}: Least recently used strategy is segmented with [{}] levels with proportions of [{}]", - typeName, - segmented.size, - segmented.map(proportion => "%.2f".format(proportion)).mkString(", ")) - } - idle.foreach(logPassivation) - case ClusterShardingSettings.MostRecentlyUsedPassivationStrategy(limit, idle) => - log.info("{}: Most recently used entities will be passivated when over [{}] entities", typeName, limit) - idle.foreach(logPassivation) - case ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(limit, dynamicAging, idle) => - log.info( - "{}: Least frequently used entities will be passivated when over [{}] entities {}", - typeName, - limit, - if (dynamicAging) "with dynamic aging" else "") - idle.foreach(logPassivation) - case _ => - log.debug("{}: Entities will not be passivated automatically", typeName) - } - // when using proxy the data center can be different from the own data center private val targetDcRole = dataCenter match { case Some(t) => ClusterSettings.DcRolePrefix + t diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/internal/EntityPassivationStrategy.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/internal/EntityPassivationStrategy.scala index 9b4444d76e..fa9ed1dcc0 100644 --- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/internal/EntityPassivationStrategy.scala +++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/internal/EntityPassivationStrategy.scala @@ -7,6 +7,9 @@ package akka.cluster.sharding.internal import akka.annotation.InternalApi import akka.cluster.sharding.ClusterShardingSettings import akka.cluster.sharding.ShardRegion.EntityId +import akka.util.FastFrequencySketch +import akka.util.FrequencySketch +import akka.util.OptionVal import akka.util.{ FrequencyList, RecencyList, SegmentedRecencyList } import scala.collection.immutable @@ -37,6 +40,31 @@ private[akka] object EntityPassivationStrategy { case ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(limit, dynamicAging, idle) => val idleCheck = idle.map(idle => new IdleCheck(idle.timeout, idle.interval)) new LeastFrequentlyUsedEntityPassivationStrategy(limit, dynamicAging, idleCheck) + case composite: ClusterShardingSettings.CompositePassivationStrategy => + val main = ActiveEntities(composite.mainStrategy, composite.idle.isDefined) + if (main eq NoActiveEntities) DisabledEntityPassivationStrategy + else { + val initialLimit = composite.limit + val window = ActiveEntities(composite.windowStrategy, composite.idle.isDefined) + val initialWindowProportion = if (window eq NoActiveEntities) 0.0 else composite.initialWindowProportion + val minimumWindowProportion = if (window eq NoActiveEntities) 0.0 else composite.minimumWindowProportion + val maximumWindowProportion = if (window eq NoActiveEntities) 0.0 else composite.maximumWindowProportion + val windowOptimizer = + if (window eq NoActiveEntities) NoAdmissionOptimizer + else AdmissionOptimizer(composite.limit, composite.windowOptimizer) + val admissionFilter = AdmissionFilter(composite.limit, composite.admissionFilter) + val idleCheck = composite.idle.map(idle => new IdleCheck(idle.timeout, idle.interval)) + new CompositeEntityPassivationStrategy( + initialLimit, + window, + initialWindowProportion, + minimumWindowProportion, + maximumWindowProportion, + windowOptimizer, + admissionFilter, + main, + idleCheck) + } case _ => DisabledEntityPassivationStrategy } } @@ -165,37 +193,31 @@ private[akka] abstract class LimitBasedEntityPassivationStrategy(initialLimit: I } /** - * INTERNAL API: Passivate the least recently used entities when the number of active entities in a shard region + * INTERNAL API + * + * 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. + * * @param initialLimit initial active entity capacity for a shard region * @param idleCheck optionally passivate idle entities after the given timeout, checking every interval */ -@InternalApi private[akka] final class LeastRecentlyUsedEntityPassivationStrategy(initialLimit: Int, idleCheck: Option[IdleCheck]) extends LimitBasedEntityPassivationStrategy(initialLimit) { import EntityPassivationStrategy.PassivateEntities - private val recencyList = RecencyList.empty[EntityId] + val active = new LeastRecentlyUsedReplacementPolicy(initialLimit) - override val scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = active.updateLimit(perShardLimit) - override def entityTouched(id: EntityId): PassivateEntities = { - recencyList.update(id) - passivateExcessEntities() - } + override def entityTouched(id: EntityId): PassivateEntities = active.update(id) - override def entityTerminated(id: EntityId): Unit = recencyList.remove(id) + override def entityTerminated(id: EntityId): Unit = active.remove(id) + + override def scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => - recencyList.removeLeastRecentOutside(idle.timeout) - } - - override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = passivateExcessEntities() - - private def passivateExcessEntities(): PassivateEntities = { - val excess = recencyList.size - perShardLimit - if (excess > 0) recencyList.removeLeastRecent(excess) else PassivateEntities.none + active.removeIdle(idle.timeout) } } @@ -207,6 +229,7 @@ private[akka] final class LeastRecentlyUsedEntityPassivationStrategy(initialLimi * Active entities are tracked in multiple recency lists, where entities are promoted to higher-level * segments on subsequent accesses, and demoted through levels when segments become full. * The proportions of the segmented levels can be configured as fractions of the overall limit. + * * @param initialLimit initial active entity capacity for a shard region * @param proportions proportions of the segmented levels * @param idleCheck optionally passivate idle entities after the given timeout, checking every interval @@ -220,31 +243,19 @@ private[akka] final class SegmentedLeastRecentlyUsedEntityPassivationStrategy( import EntityPassivationStrategy.PassivateEntities - private def limits: immutable.Seq[Int] = proportions.map(p => (p * perShardLimit).toInt) + val active = new SegmentedLeastRecentlyUsedReplacementPolicy(initialLimit, proportions, idleCheck.isDefined) - private val segmentedRecencyList = - if (idleCheck.isDefined) SegmentedRecencyList.withOverallRecency.empty[EntityId](limits) - else SegmentedRecencyList.empty[EntityId](limits) + override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = active.updateLimit(perShardLimit) - override val scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + override def entityTouched(id: EntityId): PassivateEntities = active.update(id) - override def entityTouched(id: EntityId): PassivateEntities = { - segmentedRecencyList.update(id) - passivateExcessEntities() - } + override def entityTerminated(id: EntityId): Unit = active.remove(id) - override def entityTerminated(id: EntityId): Unit = segmentedRecencyList.remove(id) + override def scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => - segmentedRecencyList.removeOverallLeastRecentOutside(idle.timeout) + active.removeIdle(idle.timeout) } - - override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = { - segmentedRecencyList.updateLimits(limits) - passivateExcessEntities() - } - - private def passivateExcessEntities(): PassivateEntities = segmentedRecencyList.removeLeastRecentOverLimit() } /** @@ -252,6 +263,7 @@ private[akka] final class SegmentedLeastRecentlyUsedEntityPassivationStrategy( * * 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. + * * @param initialLimit initial active entity capacity for a shard region * @param idleCheck optionally passivate idle entities after the given timeout, checking every interval */ @@ -261,26 +273,18 @@ private[akka] final class MostRecentlyUsedEntityPassivationStrategy(initialLimit import EntityPassivationStrategy.PassivateEntities - private val recencyList = RecencyList.empty[EntityId] + val active = new MostRecentlyUsedReplacementPolicy(initialLimit) - override val scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = active.updateLimit(perShardLimit) - override def entityTouched(id: EntityId): PassivateEntities = { - recencyList.update(id) - passivateExcessEntities(skip = 1) // remove most recent before adding this created entity - } + override def entityTouched(id: EntityId): PassivateEntities = active.update(id) - override def entityTerminated(id: EntityId): Unit = recencyList.remove(id) + override def entityTerminated(id: EntityId): Unit = active.remove(id) + + override def scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => - recencyList.removeLeastRecentOutside(idle.timeout) - } - - override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = passivateExcessEntities() - - private def passivateExcessEntities(skip: Int = 0): PassivateEntities = { - val excess = recencyList.size - perShardLimit - if (excess > 0) recencyList.removeMostRecent(excess, skip) else PassivateEntities.none + active.removeIdle(idle.timeout) } } @@ -289,6 +293,7 @@ private[akka] final class MostRecentlyUsedEntityPassivationStrategy(initialLimit * * 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. + * * @param initialLimit initial active entity capacity for a shard region * @param dynamicAging whether to apply "dynamic aging" as entities are passivated * @param idleCheck optionally passivate idle entities after the given timeout, checking every interval @@ -302,33 +307,670 @@ private[akka] final class LeastFrequentlyUsedEntityPassivationStrategy( import EntityPassivationStrategy.PassivateEntities + val active = new LeastFrequentlyUsedReplacementPolicy(initialLimit, dynamicAging, idleCheck.isDefined) + + override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = active.updateLimit(perShardLimit) + + override def entityTouched(id: EntityId): PassivateEntities = active.update(id) + + override def entityTerminated(id: EntityId): Unit = active.remove(id) + + override def scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + + override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => + active.removeIdle(idle.timeout) + } +} + +/** + * INTERNAL API + */ +@InternalApi +private[akka] object ActiveEntities { + def apply(strategy: ClusterShardingSettings.PassivationStrategy, idleEnabled: Boolean): ActiveEntities = + strategy match { + case ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(_, segmented, _) => + if (segmented.isEmpty) new LeastRecentlyUsedReplacementPolicy(initialLimit = 0) + else new SegmentedLeastRecentlyUsedReplacementPolicy(initialLimit = 0, segmented, idleEnabled) + case ClusterShardingSettings.MostRecentlyUsedPassivationStrategy(_, _) => + new MostRecentlyUsedReplacementPolicy(initialLimit = 0) + case ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(_, dynamicAging, _) => + new LeastFrequentlyUsedReplacementPolicy(initialLimit = 0, dynamicAging, idleEnabled) + case _ => NoActiveEntities + } +} + +/** + * INTERNAL API + * + * Active entity tracking for entity passivation strategies, implemented with a replacement policy. + */ +@InternalApi +private[akka] sealed abstract class ActiveEntities { + import EntityPassivationStrategy.PassivateEntities + + /** + * The current number of active entities being tracked. + * @return size of active entities + */ + def size: Int + + /** + * Check whether the entity id is currently tracked as active. + * @param id the entity id to check + * @return whether the entity is active + */ + def isActive(id: EntityId): Boolean + + /** + * The per-shard active entity limit has been updated, which can trigger passivation. + * @param newLimit the new per-shard active entity limit + * @return entities to passivate in the associated shard + */ + def updateLimit(newLimit: Int): PassivateEntities + + /** + * An entity instance has been touched. Recorded before message delivery. + * @param id entity id for the touched entity instance + * @return entities to passivate, when active capacity has been reached + */ + def update(id: EntityId): PassivateEntities + + /** + * Select the entity that would be passivated by the replacement policy, when active capacity has been reached. + * @return entity that would be passivated + */ + def select: OptionVal[EntityId] + + /** + * An entity instance should be removed from active tracking. + * @param id entity id for the removed entity instance + */ + def remove(id: EntityId): Unit + + /** + * Remove entity instances that have not been active for the given timeout. + * @param timeout the idle timeout for entities + * @return entities to passivate, if deemed inactive + */ + def removeIdle(timeout: FiniteDuration): PassivateEntities +} + +/** + * INTERNAL API + * + * Disabled ActiveEntities (for no window in composite passivation strategies). + */ +@InternalApi +private[akka] object NoActiveEntities extends ActiveEntities { + import EntityPassivationStrategy.PassivateEntities + + override def size: Int = 0 + override def isActive(id: EntityId): Boolean = false + override def updateLimit(newLimit: Int): PassivateEntities = PassivateEntities.none + override def update(id: EntityId): PassivateEntities = List(id) + override def select: OptionVal[EntityId] = OptionVal.None + override def remove(id: EntityId): Unit = () + override def removeIdle(timeout: FiniteDuration): PassivateEntities = PassivateEntities.none +} + +/** + * INTERNAL API + * + * 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. + * + * @param initialLimit initial active entity capacity for a shard + */ +@InternalApi +private[akka] final class LeastRecentlyUsedReplacementPolicy(initialLimit: Int) extends ActiveEntities { + import EntityPassivationStrategy.PassivateEntities + + private var limit = initialLimit + private val recencyList = RecencyList.empty[EntityId] + + override def size: Int = recencyList.size + + override def isActive(id: EntityId): Boolean = recencyList.contains(id) + + override def updateLimit(newLimit: Int): PassivateEntities = { + limit = newLimit + removeExcess() + } + + override def update(id: EntityId): PassivateEntities = { + recencyList.update(id) + removeExcess() + } + + override def select: OptionVal[EntityId] = recencyList.leastRecent + + override def remove(id: EntityId): Unit = recencyList.remove(id) + + override def removeIdle(timeout: FiniteDuration): PassivateEntities = recencyList.removeLeastRecentOutside(timeout) + + private def removeExcess(): PassivateEntities = { + val excess = recencyList.size - limit + if (excess > 0) recencyList.removeLeastRecent(excess) else PassivateEntities.none + } +} + +/** + * INTERNAL API + * + * 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. + * Active entities are tracked in multiple recency lists, where entities are promoted to higher-level + * segments on subsequent accesses, and demoted through levels when segments become full. + * The proportions of the segmented levels can be configured as fractions of the overall limit. + * + * @param initialLimit initial active entity capacity for a shard + * @param proportions proportions of the segmented levels + * @param idleEnabled whether idle entity passivation is enabled + */ +@InternalApi +private[akka] final class SegmentedLeastRecentlyUsedReplacementPolicy( + initialLimit: Int, + proportions: immutable.Seq[Double], + idleEnabled: Boolean) + extends ActiveEntities { + + import EntityPassivationStrategy.PassivateEntities + + private var limit = initialLimit + private def segmentLimits: immutable.Seq[Int] = { + // assign the first segment with the leftover, to have an accurate total limit + val higherLimits = proportions.drop(1).map(p => (p * limit).toInt) + (limit - higherLimits.sum) +: higherLimits + } + + private val segmentedRecencyList = + if (idleEnabled) SegmentedRecencyList.withOverallRecency.empty[EntityId](segmentLimits) + else SegmentedRecencyList.empty[EntityId](segmentLimits) + + override def size: Int = segmentedRecencyList.size + + override def isActive(id: EntityId): Boolean = segmentedRecencyList.contains(id) + + override def updateLimit(newLimit: Int): PassivateEntities = { + limit = newLimit + segmentedRecencyList.updateLimits(segmentLimits) + removeExcess() + } + + override def update(id: EntityId): PassivateEntities = { + segmentedRecencyList.update(id) + removeExcess() + } + + override def select: OptionVal[EntityId] = segmentedRecencyList.leastRecent + + override def remove(id: EntityId): Unit = segmentedRecencyList.remove(id) + + override def removeIdle(timeout: FiniteDuration): PassivateEntities = + segmentedRecencyList.removeOverallLeastRecentOutside(timeout) + + private def removeExcess(): PassivateEntities = segmentedRecencyList.removeLeastRecentOverLimit() +} + +/** + * INTERNAL API + * + * 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. + * + * @param initialLimit initial active entity capacity for a shard + */ +@InternalApi +private[akka] final class MostRecentlyUsedReplacementPolicy(initialLimit: Int) extends ActiveEntities { + import EntityPassivationStrategy.PassivateEntities + + private var limit = initialLimit + private val recencyList = RecencyList.empty[EntityId] + + override def size: Int = recencyList.size + + override def isActive(id: EntityId): Boolean = recencyList.contains(id) + + override def updateLimit(newLimit: Int): PassivateEntities = { + limit = newLimit + removeExcess() + } + + override def update(id: EntityId): PassivateEntities = { + recencyList.update(id) + removeExcess(skip = 1) // remove the most recent entity before the just touched entity + } + + override def select: OptionVal[EntityId] = recencyList.mostRecent + + override def remove(id: EntityId): Unit = recencyList.remove(id) + + override def removeIdle(timeout: FiniteDuration): PassivateEntities = recencyList.removeLeastRecentOutside(timeout) + + private def removeExcess(skip: Int = 0): PassivateEntities = { + val excess = recencyList.size - limit + if (excess > 0) recencyList.removeMostRecent(excess, skip) else PassivateEntities.none + } +} + +/** + * INTERNAL API + * + * 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. + * + * @param initialLimit initial active entity capacity for a shard + * @param dynamicAging whether to apply "dynamic aging" as entities are passivated + * @param idleEnabled whether idle entity passivation is enabled + */ +@InternalApi +private[akka] final class LeastFrequentlyUsedReplacementPolicy( + initialLimit: Int, + dynamicAging: Boolean, + idleEnabled: Boolean) + extends ActiveEntities { + + import EntityPassivationStrategy.PassivateEntities + + private var limit = initialLimit + private val frequencyList = - if (idleCheck.isDefined) FrequencyList.withOverallRecency.empty[EntityId](dynamicAging) + if (idleEnabled) FrequencyList.withOverallRecency.empty[EntityId](dynamicAging) else FrequencyList.empty[EntityId](dynamicAging) - override val scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + override def size: Int = frequencyList.size - override def entityTouched(id: EntityId): PassivateEntities = { + override def isActive(id: EntityId): Boolean = frequencyList.contains(id) + + override def updateLimit(newLimit: Int): PassivateEntities = { + limit = newLimit + removeExcess() + } + + override def update(id: EntityId): PassivateEntities = { // first remove excess entities so that dynamic aging is updated // and the adjusted age is applied to any new entities on update // adjust the expected size by 1 if this is a newly activated entity val adjustment = if (frequencyList.contains(id)) 0 else 1 - val passivated = passivateExcessEntities(adjustment) + val passivated = removeExcess(adjustment) frequencyList.update(id) passivated } - override def entityTerminated(id: EntityId): Unit = frequencyList.remove(id) + override def select: OptionVal[EntityId] = frequencyList.leastFrequent - override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => - frequencyList.removeOverallLeastRecentOutside(idle.timeout) - } + override def remove(id: EntityId): Unit = frequencyList.remove(id) - override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = passivateExcessEntities() + override def removeIdle(timeout: FiniteDuration): PassivateEntities = + frequencyList.removeOverallLeastRecentOutside(timeout) - private def passivateExcessEntities(adjustment: Int = 0): PassivateEntities = { - val excess = frequencyList.size - perShardLimit + adjustment + private def removeExcess(adjustment: Int = 0): PassivateEntities = { + val excess = frequencyList.size - limit + adjustment if (excess > 0) frequencyList.removeLeastFrequent(excess) else PassivateEntities.none } - +} + +/** + * INTERNAL API + * + * Passivate entities using a "composite" strategy, with an admission area before the main area, an admission + * filter for admitting entities to the main area, and an optimizer for the proportion of the admission window. + * + * References: + * + * "TinyLFU: A Highly Efficient Cache Admission Policy" + * Gil Einziger, Roy Friedman, Ben Manes + * + * "Adaptive Software Cache Management" + * Gil Einziger, Ohad Eytan, Roy Friedman, Ben Manes + * + * @param initialLimit initial active entity capacity for a shard region + * @param window the active entities tracking and replacement policy for the admission window + * @param initialWindowProportion the initial proportion for the admission window + * @param minimumWindowProportion the minimum proportion for the admission window (if being optimized) + * @param maximumWindowProportion the maximum proportion for the admission window (if being optimized) + * @param windowOptimizer the optimizer for the admission window proportion + * @param admissionFilter the admission filter to apply for the main area + * @param main the active entities tracking and replacement policy for the main area + * @param idleCheck optionally passivate idle entities after the given timeout, checking every interval + */ +@InternalApi +private[akka] final class CompositeEntityPassivationStrategy( + initialLimit: Int, + window: ActiveEntities, + initialWindowProportion: Double, + minimumWindowProportion: Double, + maximumWindowProportion: Double, + windowOptimizer: AdmissionOptimizer, + admissionFilter: AdmissionFilter, + main: ActiveEntities, + idleCheck: Option[IdleCheck]) + extends LimitBasedEntityPassivationStrategy(initialLimit) { + + import EntityPassivationStrategy.PassivateEntities + + private var windowProportion = initialWindowProportion + private var windowLimit = 0 + private var mainLimit = 0 + + private def calculateLimits(): Unit = { + windowLimit = (windowProportion * perShardLimit).toInt + mainLimit = perShardLimit - windowLimit + } + + // set initial limits based on initial window proportion + calculateLimits() + window.updateLimit(windowLimit) + main.updateLimit(mainLimit) + + override def entityTouched(id: EntityId): PassivateEntities = { + admissionFilter.update(id) + val passivated = if (window.isActive(id)) { + windowOptimizer.recordActive() + window.update(id) + } else if (main.isActive(id)) { + windowOptimizer.recordActive() + main.update(id) + } else { + windowOptimizer.recordPassive() + maybeAdmitToMain(window.update(id)) + } + adaptWindow() + passivated + } + + override def entityTerminated(id: EntityId): Unit = { + window.remove(id) + main.remove(id) + } + + override protected def passivateEntitiesOnLimitUpdate(): PassivateEntities = { + calculateLimits() + windowOptimizer.updateLimit(perShardLimit) + admissionFilter.updateCapacity(perShardLimit) + maybeAdmitToMain(window.updateLimit(windowLimit)) ++ main.updateLimit(mainLimit) + } + + override def scheduledInterval: Option[FiniteDuration] = idleCheck.map(_.interval) + + override def intervalPassed(): PassivateEntities = idleCheck.fold(PassivateEntities.none) { idle => + window.removeIdle(idle.timeout) ++ main.removeIdle(idle.timeout) + } + + private def maybeAdmitToMain(candidates: PassivateEntities): PassivateEntities = { + if (candidates.nonEmpty) { + var passivated: PassivateEntities = PassivateEntities.none + candidates.foreach { candidate => + if (main.size >= mainLimit) { + main.select match { + case OptionVal.Some(selected) => + if (admissionFilter.admit(candidate, selected)) + passivated ++= main.update(candidate) + else passivated :+= candidate + case _ => passivated ++= main.update(candidate) + } + } else passivated ++= main.update(candidate) + } + passivated + } else PassivateEntities.none + } + + private def adaptWindow(): Unit = { + val adjustment = windowOptimizer.calculateAdjustment() + if (adjustment != 0.0) { + windowProportion = + math.max(minimumWindowProportion, math.min(maximumWindowProportion, windowProportion + adjustment)) + calculateLimits() + // note: no passivations from adjustments, entities are transferred between window and main + if (adjustment > 0.0) { // increase window limit + window.updateLimit(windowLimit) + main.updateLimit(mainLimit).foreach(window.update) + } else { // decrease window limit + main.updateLimit(mainLimit) + window.updateLimit(windowLimit).foreach(main.update) + } + } + } +} + +/** + * INTERNAL API + */ +@InternalApi +private[akka] object AdmissionOptimizer { + def apply( + initialLimit: Int, + optimizer: ClusterShardingSettings.CompositePassivationStrategy.AdmissionOptimizer): AdmissionOptimizer = + optimizer match { + case ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier, + initialStep, + restartThreshold, + stepDecay) => + new HillClimbingAdmissionOptimizer(initialLimit, adjustMultiplier, initialStep, restartThreshold, stepDecay) + case _ => NoAdmissionOptimizer + } +} + +/** + * INTERNAL API + * + * An optimizer for the size of the admission window for a composite passivation strategy. + */ +@InternalApi +private[akka] abstract class AdmissionOptimizer { + + /** + * An entity was accessed that is already active. + */ + def recordActive(): Unit + + /** + * An entity was accessed that was passive (needed to be activated). + */ + def recordPassive(): Unit + + /** + * The per-shard limit has been updated. + * @param newLimit the new per-shard limit + */ + def updateLimit(newLimit: Int): Unit + + /** + * Calculate an adjustment to the proportion of the admission window. + * Can be positive (to grow the window) or negative (to shrink the window). + * Returns 0.0 if no adjustment should be made. + * @return the adjustment to make to the admission window proportion + */ + def calculateAdjustment(): Double +} + +/** + * INTERNAL API + * + * Disabled admission window proportion optimizer. + */ +@InternalApi +private[akka] object NoAdmissionOptimizer extends AdmissionOptimizer { + override def recordActive(): Unit = () + override def recordPassive(): Unit = () + override def updateLimit(newLimit: Int): Unit = () + override def calculateAdjustment(): Double = 0.0 +} + +/** + * INTERNAL API + * + * Optimizer for the admission window using a simple hill-climbing algorithm. + */ +@InternalApi +private[akka] final class HillClimbingAdmissionOptimizer( + initialLimit: Int, + adjustMultiplier: Double, + initialStep: Double, + restartThreshold: Double, + stepDecay: Double) + extends AdmissionOptimizer { + private var adjustSize = adjustMultiplier * initialLimit + private var accesses = 0 + private var activeAccesses = 0 + private var previousActiveRate = 0.0 + private var nextStep = -initialStep // start in decreasing direction + + override def recordActive(): Unit = { + accesses += 1 + activeAccesses += 1 + } + + override def recordPassive(): Unit = accesses += 1 + + override def updateLimit(newLimit: Int): Unit = + adjustSize = adjustMultiplier * newLimit + + override def calculateAdjustment(): Double = { + if (accesses >= adjustSize) { + val activeRate = activeAccesses.toDouble / accesses + val delta = activeRate - previousActiveRate + val adjustment = if (delta >= 0) nextStep else -nextStep + val direction = if (adjustment >= 0) 1 else -1 + val restart = math.abs(delta) >= restartThreshold + nextStep = if (restart) initialStep * direction else adjustment * stepDecay + previousActiveRate = activeRate + accesses = 0 + activeAccesses = 0 + adjustment + } else 0.0 + } +} + +/** + * INTERNAL API + */ +@InternalApi +private[akka] object AdmissionFilter { + def apply( + initialCapacity: Int, + filter: ClusterShardingSettings.CompositePassivationStrategy.AdmissionFilter): AdmissionFilter = filter match { + case ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier, resetMultiplier, depth, counterBits) => + FrequencySketchAdmissionFilter(initialCapacity, widthMultiplier, resetMultiplier, depth, counterBits) + case _ => AlwaysAdmissionFilter + } +} + +/** + * INTERNAL API + * + * An admission filter for the main area for a composite passivation strategy. + */ +@InternalApi +private[akka] abstract class AdmissionFilter { + + /** + * Update the capacity, the per-shard entity limit. + * @param newCapacity the new capacity for the filter + */ + def updateCapacity(newCapacity: Int): Unit + + /** + * Update the filter when an entity is accessed + * @param id the entity id that has been accessed + */ + def update(id: EntityId): Unit + + /** + * Determine whether an entity should be admitted to the main area. + * The candidate has been removed from the admission window (according to its replacement policy) + * and can replace an entity in the main area (selected by its replacement policy). + * Whichever entity is not admitted or retained will be passivated. + * @param candidate the candidate from the window that may be admitted to the main area + * @param selected the entity selected from the main area to possibly be replaced by the candidate + * @return whether to admit the candidate to the main area + */ + def admit(candidate: EntityId, selected: EntityId): Boolean +} + +/** + * INTERNAL API + * + * Disabled admission filter, always admit candidates to the main area. + */ +@InternalApi +private[akka] object AlwaysAdmissionFilter extends AdmissionFilter { + override def updateCapacity(newCapacity: Int): Unit = () + override def update(id: EntityId): Unit = () + override def admit(candidate: EntityId, selected: EntityId): Boolean = true +} + +/** + * INTERNAL API + * + * Admission filter based on a frequency sketch. + */ +@InternalApi +private[akka] object FrequencySketchAdmissionFilter { + def apply( + initialCapacity: Int, + widthMultiplier: Int, + resetMultiplier: Double, + depth: Int, + counterBits: Int): AdmissionFilter = { + if (depth == 4 && counterBits == 4) + new FastFrequencySketchAdmissionFilter(initialCapacity, widthMultiplier, resetMultiplier) + else + new FrequencySketchAdmissionFilter(initialCapacity, widthMultiplier, resetMultiplier, depth, counterBits) + } +} + +/** + * INTERNAL API + * + * Admission filter based on a frequency sketch. + */ +@InternalApi +private[akka] final class FrequencySketchAdmissionFilter( + initialCapacity: Int, + widthMultiplier: Int, + resetMultiplier: Double, + depth: Int, + counterBits: Int) + extends AdmissionFilter { + + private def createSketch(capacity: Int): FrequencySketch[EntityId] = + FrequencySketch[EntityId](capacity, widthMultiplier, resetMultiplier, depth, counterBits) + + private var frequencySketch = createSketch(initialCapacity) + + override def updateCapacity(newCapacity: Int): Unit = frequencySketch = createSketch(newCapacity) + + override def update(id: EntityId): Unit = frequencySketch.increment(id) + + override def admit(candidate: EntityId, selected: EntityId): Boolean = + frequencySketch.frequency(candidate) > frequencySketch.frequency(selected) +} + +/** + * INTERNAL API + * + * Admission filter based on a frequency sketch (fast version with depth of 4 and 4-bit counters). + */ +@InternalApi +private[akka] final class FastFrequencySketchAdmissionFilter( + initialCapacity: Int, + widthMultiplier: Int, + resetMultiplier: Double) + extends AdmissionFilter { + + private def createSketch(capacity: Int): FastFrequencySketch[EntityId] = + FastFrequencySketch[EntityId](capacity, widthMultiplier, resetMultiplier) + + private var frequencySketch = createSketch(initialCapacity) + + override def updateCapacity(newCapacity: Int): Unit = frequencySketch = createSketch(newCapacity) + + override def update(id: EntityId): Unit = frequencySketch.increment(id) + + override def admit(candidate: EntityId, selected: EntityId): Boolean = + frequencySketch.frequency(candidate) > frequencySketch.frequency(selected) } diff --git a/akka-cluster-sharding/src/test/resources/adaptivity-trace.conf b/akka-cluster-sharding/src/test/resources/adaptivity-trace.conf new file mode 100644 index 0000000000..c933d82a5b --- /dev/null +++ b/akka-cluster-sharding/src/test/resources/adaptivity-trace.conf @@ -0,0 +1,243 @@ +# +# Run a joined trace for testing adaptivity, switching between a recency-biased R3 Corda trace and +# a frequency-biased LIRS loop trace. +# +# Similar to the adaptivity test for Caffeine: https://github.com/ben-manes/caffeine/wiki/Efficiency +# +# Download LIRS traces from: https://github.com/zhongch4g/LIRS2 +# +# Download Corda traces from: https://github.com/ben-manes/caffeine +# +# > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator adaptivity-trace +# +# ╔════════════════════╤═════════╤═══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ OPT 500 │ 41.89 % │ 8,565,548 │ 4,977,489 │ 4,976,989 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU 500 │ 15.63 % │ 8,565,548 │ 7,226,870 │ 7,226,370 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ SLRU 500 │ 7.31 % │ 8,565,548 │ 7,939,452 │ 7,938,952 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ S4LRU 500 │ 15.63 % │ 8,565,548 │ 7,226,902 │ 7,226,402 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ MRU 500 │ 0.03 % │ 8,565,548 │ 8,563,041 │ 8,562,541 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFU 500 │ 0.02 % │ 8,565,548 │ 8,564,051 │ 8,563,551 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFUDA 500 │ 15.63 % │ 8,565,548 │ 7,226,892 │ 7,226,392 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500 │ 26.14 % │ 8,565,548 │ 6,326,555 │ 6,326,055 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500 │ 40.74 % │ 8,565,548 │ 5,076,280 │ 5,075,780 ║ +# ╚════════════════════╧═════════╧═══════════╧═════════════╧══════════════╝ +# + +corda-traces="corda-traces" +corda-traces=${?CORDA_TRACES} + +lirs-traces="lirs-traces" +lirs-traces=${?LIRS_TRACES} + +akka.cluster.sharding { + passivation.simulator { + runs = [ + { + name = "OPT 500" + shards = 1 + regions = 1 + pattern = changing + strategy = optimal-500 + }, + { + name = "LRU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = lru-500 + }, + { + name = "SLRU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = slru-500 + }, + { + name = "S4LRU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = s4lru-500 + }, + { + name = "MRU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = mru-500 + }, + { + name = "LFU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = lfu-500 + }, + { + name = "LFUDA 500" + shards = 1 + regions = 1 + pattern = changing + strategy = lfuda-500 + }, + { + name = "LRU/FS/SLRU 500" + shards = 1 + regions = 1 + pattern = changing + strategy = lru-fs-slru-500 + }, + { + name = "LRU/FS/SLRU/HC 500" + shards = 1 + regions = 1 + pattern = changing + strategy = lru-fs-slru-hc-500 + }, + ] + + changing { + pattern = joined + joined = [ + corda-vaultservice, + lirs-loop, + lirs-loop, + lirs-loop, + corda-vaultservice-large, + lirs-loop, + lirs-loop, + lirs-loop, + corda-vaultservice, + lirs-loop, + lirs-loop, + lirs-loop, + corda-vaultservice-large, + ] + } + + # recency-biased + corda-vaultservice { + pattern = trace + trace { + format = corda + path = ${corda-traces}"/vaultservice.trace" + } + } + + # recency-biased + corda-vaultservice-large { + pattern = trace + trace { + format = corda + path = ${corda-traces}"/vaultservice-large.trace" + } + } + + # frequency-biased + lirs-loop { + pattern = trace + trace { + format = lirs + path = ${lirs-traces}"/loop.trace" + } + } + + optimal-500 { + strategy = optimal + optimal { + per-region-limit = 500 + } + } + + lru-500 { + strategy = least-recently-used + least-recently-used { + per-region-limit = 500 + } + } + + slru-500 { + strategy = least-recently-used + least-recently-used { + per-region-limit = 500 + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + + s4lru-500 { + strategy = least-recently-used + least-recently-used { + per-region-limit = 500 + segmented.levels = 4 + } + } + + mru-500 { + strategy = most-recently-used + most-recently-used { + per-region-limit = 500 + } + } + + lfu-500 { + strategy = least-frequently-used + least-frequently-used { + per-region-limit = 500 + } + } + + lfuda-500 { + strategy = least-frequently-used + least-frequently-used { + per-region-limit = 500 + dynamic-aging = on + } + } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 500 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 500 + } + } +} diff --git a/akka-cluster-sharding/src/test/resources/arc-trace-database.conf b/akka-cluster-sharding/src/test/resources/arc-trace-database.conf index bb7c852c33..83a2c3be35 100644 --- a/akka-cluster-sharding/src/test/resources/arc-trace-database.conf +++ b/akka-cluster-sharding/src/test/resources/arc-trace-database.conf @@ -8,65 +8,81 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator arc-trace-database # -# ╔══════════╤═════════╤════════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ OPT 1M │ 20.19 % │ 43,704,979 │ 34,879,633 │ 33,879,633 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ OPT 2M │ 31.79 % │ 43,704,979 │ 29,810,905 │ 27,810,905 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ OPT 4M │ 48.09 % │ 43,704,979 │ 22,685,636 │ 18,685,636 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ OPT 8M │ 74.93 % │ 43,704,979 │ 10,957,661 │ 2,957,661 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LRU 1M │ 3.09 % │ 43,704,979 │ 42,356,500 │ 41,356,500 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 2M │ 10.75 % │ 43,704,979 │ 39,007,141 │ 37,007,141 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 4M │ 20.24 % │ 43,704,979 │ 34,857,131 │ 30,857,131 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 8M │ 43.03 % │ 43,704,979 │ 24,896,638 │ 16,896,638 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ SLRU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 2M │ 23.37 % │ 43,704,979 │ 33,489,560 │ 31,489,560 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 4M │ 39.06 % │ 43,704,979 │ 26,632,624 │ 22,632,624 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 8M │ 57.55 % │ 43,704,979 │ 18,552,089 │ 10,552,089 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ S4LRU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 2M │ 16.74 % │ 43,704,979 │ 36,387,353 │ 34,387,353 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 4M │ 30.27 % │ 43,704,979 │ 30,477,096 │ 26,477,096 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 8M │ 57.86 % │ 43,704,979 │ 18,419,181 │ 10,419,181 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ MRU 1M │ 11.92 % │ 43,704,979 │ 38,493,369 │ 37,493,369 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 2M │ 25.67 % │ 43,704,979 │ 32,486,285 │ 30,486,285 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 4M │ 41.82 % │ 43,704,979 │ 25,429,608 │ 21,429,608 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 8M │ 68.01 % │ 43,704,979 │ 13,980,246 │ 5,980,246 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LFU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 2M │ 23.39 % │ 43,704,979 │ 33,482,361 │ 31,482,361 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 4M │ 39.06 % │ 43,704,979 │ 26,632,624 │ 22,632,624 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 8M │ 57.55 % │ 43,704,979 │ 18,552,089 │ 10,552,089 ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LFUDA 1M │ 3.25 % │ 43,704,979 │ 42,285,825 │ 41,285,825 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 2M │ 11.02 % │ 43,704,979 │ 38,890,280 │ 36,890,280 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 4M │ 21.96 % │ 43,704,979 │ 34,107,344 │ 30,107,344 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 8M │ 54.72 % │ 43,704,979 │ 19,790,371 │ 11,790,371 ║ -# ╚══════════╧═════════╧════════════╧═════════════╧══════════════╝ +# ╔═══════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ OPT 1M │ 20.19 % │ 43,704,979 │ 34,879,633 │ 33,879,633 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ OPT 2M │ 31.79 % │ 43,704,979 │ 29,810,905 │ 27,810,905 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ OPT 4M │ 48.09 % │ 43,704,979 │ 22,685,636 │ 18,685,636 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ OPT 8M │ 74.93 % │ 43,704,979 │ 10,957,661 │ 2,957,661 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU 1M │ 3.09 % │ 43,704,979 │ 42,356,500 │ 41,356,500 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 2M │ 10.75 % │ 43,704,979 │ 39,007,141 │ 37,007,141 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 4M │ 20.24 % │ 43,704,979 │ 34,857,131 │ 30,857,131 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 8M │ 43.03 % │ 43,704,979 │ 24,896,638 │ 16,896,638 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ SLRU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 2M │ 23.37 % │ 43,704,979 │ 33,489,560 │ 31,489,560 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 4M │ 39.06 % │ 43,704,979 │ 26,632,624 │ 22,632,624 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 8M │ 57.55 % │ 43,704,979 │ 18,552,089 │ 10,552,089 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ S4LRU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 2M │ 16.74 % │ 43,704,979 │ 36,387,353 │ 34,387,353 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 4M │ 30.27 % │ 43,704,979 │ 30,477,096 │ 26,477,096 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 8M │ 57.86 % │ 43,704,979 │ 18,419,181 │ 10,419,181 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ MRU 1M │ 11.92 % │ 43,704,979 │ 38,493,369 │ 37,493,369 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 2M │ 25.67 % │ 43,704,979 │ 32,486,285 │ 30,486,285 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 4M │ 41.82 % │ 43,704,979 │ 25,429,608 │ 21,429,608 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 8M │ 68.01 % │ 43,704,979 │ 13,980,246 │ 5,980,246 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LFU 1M │ 6.13 % │ 43,704,979 │ 41,026,869 │ 40,026,869 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 2M │ 23.39 % │ 43,704,979 │ 33,482,361 │ 31,482,361 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 4M │ 39.06 % │ 43,704,979 │ 26,632,624 │ 22,632,624 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 8M │ 57.55 % │ 43,704,979 │ 18,552,089 │ 10,552,089 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LFUDA 1M │ 3.25 % │ 43,704,979 │ 42,285,825 │ 41,285,825 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 2M │ 11.02 % │ 43,704,979 │ 38,890,280 │ 36,890,280 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 4M │ 21.96 % │ 43,704,979 │ 34,107,344 │ 30,107,344 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 8M │ 54.72 % │ 43,704,979 │ 19,790,371 │ 11,790,371 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 1M │ 14.36 % │ 43,704,979 │ 37,428,825 │ 36,428,825 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 2M │ 28.61 % │ 43,704,979 │ 31,200,295 │ 29,200,295 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 4M │ 45.36 % │ 43,704,979 │ 23,882,207 │ 19,882,207 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 8M │ 70.73 % │ 43,704,979 │ 12,791,039 │ 4,791,039 ║ +# ╠═══════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 1M │ 14.28 % │ 43,704,979 │ 37,463,497 │ 36,463,497 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 2M │ 28.61 % │ 43,704,979 │ 31,200,295 │ 29,200,295 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 4M │ 45.36 % │ 43,704,979 │ 23,882,207 │ 19,882,207 ║ +# ╟───────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 8M │ 70.73 % │ 43,704,979 │ 12,791,039 │ 4,791,039 ║ +# ╚═══════════════════╧═════════╧════════════╧═════════════╧══════════════╝ # arc-traces="arc-traces" @@ -271,6 +287,62 @@ akka.cluster.sharding { pattern = arc-database strategy = lfuda-800k }, + { + name = "LRU/FS/SLRU 1M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-100k + }, + { + name = "LRU/FS/SLRU 2M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-200k + }, + { + name = "LRU/FS/SLRU 4M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-400k + }, + { + name = "LRU/FS/SLRU 8M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-800k + }, + { + name = "LRU/FS/SLRU/HC 1M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-hc-100k + }, + { + name = "LRU/FS/SLRU/HC 2M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-hc-200k + }, + { + name = "LRU/FS/SLRU/HC 4M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-hc-400k + }, + { + name = "LRU/FS/SLRU/HC 8M" + shards = 100 + regions = 10 + pattern = arc-database + strategy = lru-fs-slru-hc-800k + }, ] print-detailed-stats = true @@ -502,5 +574,62 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-100k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 100000 + } + + lru-fs-slru-200k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 200000 + } + + lru-fs-slru-400k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 400000 + } + + lru-fs-slru-800k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 800000 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-100k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 100000 + } + + lru-fs-slru-hc-200k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 200000 + } + + lru-fs-slru-hc-400k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 400000 + } + + lru-fs-slru-hc-800k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 800000 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/arc-trace-search.conf b/akka-cluster-sharding/src/test/resources/arc-trace-search.conf index ce89e1b5ee..52ee1e0362 100644 --- a/akka-cluster-sharding/src/test/resources/arc-trace-search.conf +++ b/akka-cluster-sharding/src/test/resources/arc-trace-search.conf @@ -8,51 +8,63 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator arc-trace-search # -# ╔════════════╤═════════╤════════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ OPT 250k │ 30.13 % │ 37,656,092 │ 26,309,294 │ 26,059,294 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ OPT 500k │ 45.95 % │ 37,656,092 │ 20,354,161 │ 19,854,161 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ OPT 1M │ 65.55 % │ 37,656,092 │ 12,970,868 │ 11,970,868 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LRU 250k │ 3.07 % │ 37,656,092 │ 36,498,516 │ 36,248,516 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 500k │ 7.53 % │ 37,656,092 │ 34,822,122 │ 34,322,122 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 1M │ 25.37 % │ 37,656,092 │ 28,102,287 │ 27,102,287 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ SLRU 250k │ 9.59 % │ 37,656,092 │ 34,044,825 │ 33,794,825 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 500k │ 16.76 % │ 37,656,092 │ 31,345,965 │ 30,845,965 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 1M │ 27.49 % │ 37,656,092 │ 27,305,632 │ 26,305,632 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ S4LRU 250k │ 9.78 % │ 37,656,092 │ 33,973,161 │ 33,723,161 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 500k │ 17.04 % │ 37,656,092 │ 31,241,138 │ 30,741,138 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 1M │ 27.69 % │ 37,656,092 │ 27,229,802 │ 26,229,802 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ MRU 250k │ 5.25 % │ 37,656,092 │ 35,680,111 │ 35,430,111 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 500k │ 10.50 % │ 37,656,092 │ 33,702,975 │ 33,202,975 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 1M │ 20.79 % │ 37,656,092 │ 29,826,997 │ 28,826,997 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LFU 250k │ 9.04 % │ 37,656,092 │ 34,253,102 │ 34,003,102 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 500k │ 16.42 % │ 37,656,092 │ 31,471,765 │ 30,971,765 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 1M │ 27.43 % │ 37,656,092 │ 27,328,351 │ 26,328,351 ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LFUDA 250k │ 3.18 % │ 37,656,092 │ 36,457,345 │ 36,207,345 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 500k │ 8.95 % │ 37,656,092 │ 34,285,981 │ 33,785,981 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 1M │ 28.10 % │ 37,656,092 │ 27,073,194 │ 26,073,194 ║ -# ╚════════════╧═════════╧════════════╧═════════════╧══════════════╝ +# ╔═════════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ OPT 250k │ 30.13 % │ 37,656,092 │ 26,309,294 │ 26,059,294 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ OPT 500k │ 45.95 % │ 37,656,092 │ 20,354,161 │ 19,854,161 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ OPT 1M │ 65.55 % │ 37,656,092 │ 12,970,868 │ 11,970,868 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU 250k │ 3.07 % │ 37,656,092 │ 36,498,516 │ 36,248,516 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 500k │ 7.53 % │ 37,656,092 │ 34,822,122 │ 34,322,122 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 1M │ 25.37 % │ 37,656,092 │ 28,102,287 │ 27,102,287 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ SLRU 250k │ 9.59 % │ 37,656,092 │ 34,044,825 │ 33,794,825 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 500k │ 16.76 % │ 37,656,092 │ 31,345,965 │ 30,845,965 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 1M │ 27.49 % │ 37,656,092 │ 27,305,632 │ 26,305,632 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ S4LRU 250k │ 9.78 % │ 37,656,092 │ 33,973,161 │ 33,723,161 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 500k │ 17.04 % │ 37,656,092 │ 31,241,138 │ 30,741,138 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 1M │ 27.69 % │ 37,656,092 │ 27,229,802 │ 26,229,802 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ MRU 250k │ 5.25 % │ 37,656,092 │ 35,680,111 │ 35,430,111 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 500k │ 10.50 % │ 37,656,092 │ 33,702,975 │ 33,202,975 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 1M │ 20.79 % │ 37,656,092 │ 29,826,997 │ 28,826,997 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LFU 250k │ 9.04 % │ 37,656,092 │ 34,253,102 │ 34,003,102 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 500k │ 16.42 % │ 37,656,092 │ 31,471,765 │ 30,971,765 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 1M │ 27.43 % │ 37,656,092 │ 27,328,351 │ 26,328,351 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LFUDA 250k │ 3.18 % │ 37,656,092 │ 36,457,345 │ 36,207,345 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 500k │ 8.95 % │ 37,656,092 │ 34,285,981 │ 33,785,981 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 1M │ 28.10 % │ 37,656,092 │ 27,073,194 │ 26,073,194 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 250k │ 13.05 % │ 37,656,092 │ 32,741,656 │ 32,491,656 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500k │ 24.89 % │ 37,656,092 │ 28,281,835 │ 27,781,835 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 1M │ 44.63 % │ 37,656,092 │ 20,850,711 │ 19,850,711 ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 250k │ 12.48 % │ 37,656,092 │ 32,955,813 │ 32,705,813 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500k │ 24.76 % │ 37,656,092 │ 28,332,311 │ 27,832,311 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 1M │ 44.63 % │ 37,656,092 │ 20,850,711 │ 19,850,711 ║ +# ╚═════════════════════╧═════════╧════════════╧═════════════╧══════════════╝ # arc-traces="arc-traces" @@ -208,6 +220,48 @@ akka.cluster.sharding { pattern = arc-search-merged strategy = lfuda-100k }, + { + name = "LRU/FS/SLRU 250k" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-25k + }, + { + name = "LRU/FS/SLRU 500k" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-50k + }, + { + name = "LRU/FS/SLRU 1M" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-100k + }, + { + name = "LRU/FS/SLRU/HC 250k" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-hc-25k + }, + { + name = "LRU/FS/SLRU/HC 500k" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-hc-50k + }, + { + name = "LRU/FS/SLRU/HC 1M" + shards = 100 + regions = 10 + pattern = arc-search-merged + strategy = lru-fs-slru-hc-100k + }, ] print-detailed-stats = true @@ -384,5 +438,54 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-25k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 25000 + } + + lru-fs-slru-50k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 50000 + } + + lru-fs-slru-100k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 100000 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-25k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 25000 + } + + lru-fs-slru-hc-50k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 50000 + } + + lru-fs-slru-hc-100k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 100000 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/lirs-trace-glimpse.conf b/akka-cluster-sharding/src/test/resources/lirs-trace-glimpse.conf index 9cad6e5592..b85acea3f0 100644 --- a/akka-cluster-sharding/src/test/resources/lirs-trace-glimpse.conf +++ b/akka-cluster-sharding/src/test/resources/lirs-trace-glimpse.conf @@ -8,65 +8,81 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator lirs-trace-glimpse # -# ╔════════════╤═════════╤══════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ OPT 250 │ 17.70 % │ 6,016 │ 4,951 │ 4,701 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 500 │ 34.33 % │ 6,016 │ 3,951 │ 3,451 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 1000 │ 53.14 % │ 6,016 │ 2,819 │ 1,819 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 1500 │ 57.95 % │ 6,016 │ 2,530 │ 1,030 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LRU 250 │ 0.91 % │ 6,016 │ 5,961 │ 5,711 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 500 │ 0.95 % │ 6,016 │ 5,959 │ 5,459 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 1000 │ 11.20 % │ 6,016 │ 5,342 │ 4,342 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 1500 │ 36.55 % │ 6,016 │ 3,817 │ 2,317 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ SLRU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,683 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,433 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 1000 │ 31.33 % │ 6,016 │ 4,131 │ 3,131 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ S4LRU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,693 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,453 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 1000 │ 19.70 % │ 6,016 │ 4,831 │ 3,831 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 1500 │ 48.02 % │ 6,016 │ 3,127 │ 1,647 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ MRU 250 │ 14.78 % │ 6,016 │ 5,127 │ 4,877 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 500 │ 31.40 % │ 6,016 │ 4,127 │ 3,627 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 1000 │ 48.62 % │ 6,016 │ 3,091 │ 2,091 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 1500 │ 53.37 % │ 6,016 │ 2,805 │ 1,305 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,683 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,433 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 1000 │ 31.33 % │ 6,016 │ 4,131 │ 3,131 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFUDA 250 │ 1.11 % │ 6,016 │ 5,949 │ 5,699 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 500 │ 1.25 % │ 6,016 │ 5,941 │ 5,441 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 1000 │ 16.37 % │ 6,016 │ 5,031 │ 4,031 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ -# ╚════════════╧═════════╧══════════╧═════════════╧══════════════╝ +# ╔═════════════════════╤═════════╤══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ OPT 250 │ 17.70 % │ 6,016 │ 4,951 │ 4,701 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 500 │ 34.33 % │ 6,016 │ 3,951 │ 3,451 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 1000 │ 53.14 % │ 6,016 │ 2,819 │ 1,819 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 1500 │ 57.95 % │ 6,016 │ 2,530 │ 1,030 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU 250 │ 0.91 % │ 6,016 │ 5,961 │ 5,711 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 500 │ 0.95 % │ 6,016 │ 5,959 │ 5,459 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 1000 │ 11.20 % │ 6,016 │ 5,342 │ 4,342 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 1500 │ 36.55 % │ 6,016 │ 3,817 │ 2,317 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ SLRU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,683 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,433 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 1000 │ 31.33 % │ 6,016 │ 4,131 │ 3,131 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ S4LRU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,693 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,453 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 1000 │ 19.70 % │ 6,016 │ 4,831 │ 3,831 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 1500 │ 48.02 % │ 6,016 │ 3,127 │ 1,647 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ MRU 250 │ 14.78 % │ 6,016 │ 5,127 │ 4,877 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 500 │ 31.40 % │ 6,016 │ 4,127 │ 3,627 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 1000 │ 48.62 % │ 6,016 │ 3,091 │ 2,091 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 1500 │ 53.37 % │ 6,016 │ 2,805 │ 1,305 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFU 250 │ 1.38 % │ 6,016 │ 5,933 │ 5,683 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 500 │ 1.38 % │ 6,016 │ 5,933 │ 5,433 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 1000 │ 31.33 % │ 6,016 │ 4,131 │ 3,131 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFUDA 250 │ 1.11 % │ 6,016 │ 5,949 │ 5,699 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 500 │ 1.25 % │ 6,016 │ 5,941 │ 5,441 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 1000 │ 16.37 % │ 6,016 │ 5,031 │ 4,031 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 1500 │ 51.85 % │ 6,016 │ 2,897 │ 1,397 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 250 │ 13.43 % │ 6,016 │ 5,208 │ 4,958 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500 │ 32.40 % │ 6,016 │ 4,067 │ 3,567 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 1000 │ 50.83 % │ 6,016 │ 2,958 │ 1,958 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 1500 │ 53.54 % │ 6,016 │ 2,795 │ 1,295 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 250 │ 13.43 % │ 6,016 │ 5,208 │ 4,958 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500 │ 32.40 % │ 6,016 │ 4,067 │ 3,567 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 1000 │ 50.83 % │ 6,016 │ 2,958 │ 1,958 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 1500 │ 53.54 % │ 6,016 │ 2,795 │ 1,295 ║ +# ╚═════════════════════╧═════════╧══════════╧═════════════╧══════════════╝ # lirs-traces="lirs-traces" @@ -271,6 +287,62 @@ akka.cluster.sharding { pattern = lirs-glimpse strategy = lfuda-1500 }, + { + name = "LRU/FS/SLRU 250" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-250 + }, + { + name = "LRU/FS/SLRU 500" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-500 + }, + { + name = "LRU/FS/SLRU 1000" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-1000 + }, + { + name = "LRU/FS/SLRU 1500" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-1500 + }, + { + name = "LRU/FS/SLRU/HC 250" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-hc-250 + }, + { + name = "LRU/FS/SLRU/HC 500" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-hc-500 + }, + { + name = "LRU/FS/SLRU/HC 1000" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-hc-1000 + }, + { + name = "LRU/FS/SLRU/HC 1500" + shards = 10 + regions = 1 + pattern = lirs-glimpse + strategy = lru-fs-slru-hc-1500 + }, ] print-detailed-stats = true @@ -502,5 +574,62 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-250 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 250 + } + + lru-fs-slru-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 500 + } + + lru-fs-slru-1000 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 1000 + } + + lru-fs-slru-1500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 1500 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-250 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 250 + } + + lru-fs-slru-hc-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 500 + } + + lru-fs-slru-hc-1000 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 1000 + } + + lru-fs-slru-hc-1500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 1500 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/lirs-trace-multi.conf b/akka-cluster-sharding/src/test/resources/lirs-trace-multi.conf index 8b4569ef95..f9f96db7d5 100644 --- a/akka-cluster-sharding/src/test/resources/lirs-trace-multi.conf +++ b/akka-cluster-sharding/src/test/resources/lirs-trace-multi.conf @@ -8,79 +8,99 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator lirs-trace-multi # -# ╔════════════╤═════════╤══════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ OPT 100 │ 31.20 % │ 30,241 │ 20,807 │ 20,707 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 200 │ 38.47 % │ 30,241 │ 18,607 │ 18,407 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 400 │ 47.14 % │ 30,241 │ 15,986 │ 15,586 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 800 │ 53.53 % │ 30,241 │ 14,054 │ 13,254 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 1600 │ 64.14 % │ 30,241 │ 10,844 │ 9,244 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LRU 100 │ 6.53 % │ 30,241 │ 28,266 │ 28,166 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 200 │ 14.38 % │ 30,241 │ 25,891 │ 25,691 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 400 │ 27.55 % │ 30,241 │ 21,909 │ 21,509 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 800 │ 35.97 % │ 30,241 │ 19,364 │ 18,564 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 1600 │ 44.23 % │ 30,241 │ 16,865 │ 15,265 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ SLRU 100 │ 9.22 % │ 30,241 │ 27,454 │ 27,354 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 200 │ 22.41 % │ 30,241 │ 23,463 │ 23,263 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 400 │ 29.94 % │ 30,241 │ 21,186 │ 20,786 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 800 │ 37.30 % │ 30,241 │ 18,961 │ 18,161 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ S4LRU 100 │ 8.43 % │ 30,241 │ 27,691 │ 27,611 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 200 │ 22.62 % │ 30,241 │ 23,401 │ 23,201 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 400 │ 30.10 % │ 30,241 │ 21,139 │ 20,739 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 800 │ 37.30 % │ 30,241 │ 18,962 │ 18,162 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ MRU 100 │ 2.41 % │ 30,241 │ 29,512 │ 29,412 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 200 │ 4.20 % │ 30,241 │ 28,970 │ 28,770 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 400 │ 7.21 % │ 30,241 │ 28,062 │ 27,662 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 800 │ 14.11 % │ 30,241 │ 25,973 │ 25,173 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 1600 │ 26.31 % │ 30,241 │ 22,284 │ 20,684 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFU 100 │ 8.88 % │ 30,241 │ 27,557 │ 27,457 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 200 │ 22.14 % │ 30,241 │ 23,547 │ 23,347 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 400 │ 29.85 % │ 30,241 │ 21,213 │ 20,813 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 800 │ 37.30 % │ 30,241 │ 18,961 │ 18,161 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFUDA 100 │ 10.16 % │ 30,241 │ 27,168 │ 27,068 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 200 │ 22.99 % │ 30,241 │ 23,290 │ 23,090 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 400 │ 30.50 % │ 30,241 │ 21,019 │ 20,619 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 800 │ 37.25 % │ 30,241 │ 18,977 │ 18,177 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 1600 │ 46.21 % │ 30,241 │ 16,267 │ 14,667 ║ -# ╚════════════╧═════════╧══════════╧═════════════╧══════════════╝ +# ╔═════════════════════╤═════════╤══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ OPT 100 │ 31.20 % │ 30,241 │ 20,807 │ 20,707 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 200 │ 38.47 % │ 30,241 │ 18,607 │ 18,407 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 400 │ 47.14 % │ 30,241 │ 15,986 │ 15,586 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 800 │ 53.53 % │ 30,241 │ 14,054 │ 13,254 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 1600 │ 64.14 % │ 30,241 │ 10,844 │ 9,244 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU 100 │ 6.53 % │ 30,241 │ 28,266 │ 28,166 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 200 │ 14.38 % │ 30,241 │ 25,891 │ 25,691 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 400 │ 27.55 % │ 30,241 │ 21,909 │ 21,509 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 800 │ 35.97 % │ 30,241 │ 19,364 │ 18,564 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 1600 │ 44.23 % │ 30,241 │ 16,865 │ 15,265 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ SLRU 100 │ 9.22 % │ 30,241 │ 27,454 │ 27,354 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 200 │ 22.41 % │ 30,241 │ 23,463 │ 23,263 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 400 │ 29.94 % │ 30,241 │ 21,186 │ 20,786 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 800 │ 37.30 % │ 30,241 │ 18,961 │ 18,161 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ S4LRU 100 │ 8.43 % │ 30,241 │ 27,691 │ 27,611 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 200 │ 22.62 % │ 30,241 │ 23,401 │ 23,201 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 400 │ 30.10 % │ 30,241 │ 21,139 │ 20,739 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 800 │ 37.30 % │ 30,241 │ 18,962 │ 18,162 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ MRU 100 │ 2.41 % │ 30,241 │ 29,512 │ 29,412 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 200 │ 4.20 % │ 30,241 │ 28,970 │ 28,770 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 400 │ 7.21 % │ 30,241 │ 28,062 │ 27,662 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 800 │ 14.11 % │ 30,241 │ 25,973 │ 25,173 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 1600 │ 26.31 % │ 30,241 │ 22,284 │ 20,684 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFU 100 │ 8.88 % │ 30,241 │ 27,557 │ 27,457 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 200 │ 22.14 % │ 30,241 │ 23,547 │ 23,347 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 400 │ 29.85 % │ 30,241 │ 21,213 │ 20,813 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 800 │ 37.30 % │ 30,241 │ 18,961 │ 18,161 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 1600 │ 46.51 % │ 30,241 │ 16,177 │ 14,577 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFUDA 100 │ 10.16 % │ 30,241 │ 27,168 │ 27,068 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 200 │ 22.99 % │ 30,241 │ 23,290 │ 23,090 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 400 │ 30.50 % │ 30,241 │ 21,019 │ 20,619 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 800 │ 37.25 % │ 30,241 │ 18,977 │ 18,177 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 1600 │ 46.21 % │ 30,241 │ 16,267 │ 14,667 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 100 │ 25.96 % │ 30,241 │ 22,389 │ 22,289 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 200 │ 32.56 % │ 30,241 │ 20,396 │ 20,196 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 400 │ 42.64 % │ 30,241 │ 17,347 │ 16,947 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 800 │ 46.94 % │ 30,241 │ 16,045 │ 15,245 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 1600 │ 57.65 % │ 30,241 │ 12,807 │ 11,207 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 100 │ 24.74 % │ 30,241 │ 22,760 │ 22,660 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 200 │ 32.38 % │ 30,241 │ 20,450 │ 20,250 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 400 │ 42.63 % │ 30,241 │ 17,348 │ 16,948 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 800 │ 47.11 % │ 30,241 │ 15,994 │ 15,194 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 1600 │ 57.65 % │ 30,241 │ 12,807 │ 11,207 ║ +# ╚═════════════════════╧═════════╧══════════╧═════════════╧══════════════╝ # lirs-traces="lirs-traces" @@ -334,6 +354,76 @@ akka.cluster.sharding { pattern = lirs-multi3 strategy = lfuda-1600 }, + { + name = "LRU/FS/SLRU 100" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-100 + }, + { + name = "LRU/FS/SLRU 200" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-200 + }, + { + name = "LRU/FS/SLRU 400" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-400 + }, + { + name = "LRU/FS/SLRU 800" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-800 + }, + { + name = "LRU/FS/SLRU 1600" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-1600 + }, + { + name = "LRU/FS/SLRU/HC 100" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-hc-100 + }, + { + name = "LRU/FS/SLRU/HC 200" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-hc-200 + }, + { + name = "LRU/FS/SLRU/HC 400" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-hc-400 + }, + { + name = "LRU/FS/SLRU/HC 800" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-hc-800 + }, + { + name = "LRU/FS/SLRU/HC 1600" + shards = 10 + regions = 1 + pattern = lirs-multi3 + strategy = lru-fs-slru-hc-1600 + }, ] print-detailed-stats = true @@ -620,5 +710,70 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 100 + } + + lru-fs-slru-200 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 200 + } + + lru-fs-slru-400 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 400 + } + + lru-fs-slru-800 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 800 + } + + lru-fs-slru-1600 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 1600 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 100 + } + + lru-fs-slru-hc-200 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 200 + } + + lru-fs-slru-hc-400 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 400 + } + + lru-fs-slru-hc-800 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 800 + } + + lru-fs-slru-hc-1600 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 1600 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/lirs-trace-postgres.conf b/akka-cluster-sharding/src/test/resources/lirs-trace-postgres.conf index 61abd54d82..3f8982b5d1 100644 --- a/akka-cluster-sharding/src/test/resources/lirs-trace-postgres.conf +++ b/akka-cluster-sharding/src/test/resources/lirs-trace-postgres.conf @@ -8,65 +8,81 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator lirs-trace-postgres # -# ╔════════════╤═════════╤══════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ OPT 125 │ 35.16 % │ 10,448 │ 6,774 │ 6,654 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 250 │ 53.33 % │ 10,448 │ 4,876 │ 4,626 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 500 │ 58.12 % │ 10,448 │ 4,376 │ 3,876 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 1000 │ 67.69 % │ 10,448 │ 3,376 │ 2,376 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LRU 125 │ 12.19 % │ 10,448 │ 9,174 │ 9,054 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 250 │ 13.95 % │ 10,448 │ 8,991 │ 8,741 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 500 │ 48.55 % │ 10,448 │ 5,376 │ 4,876 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 1000 │ 48.55 % │ 10,448 │ 5,376 │ 4,376 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ SLRU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,362 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,443 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 500 │ 52.59 % │ 10,448 │ 4,953 │ 4,453 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ S4LRU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,352 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,453 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 500 │ 51.95 % │ 10,448 │ 5,020 │ 4,540 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ MRU 125 │ 14.23 % │ 10,448 │ 8,961 │ 8,841 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 250 │ 30.84 % │ 10,448 │ 7,226 │ 6,976 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 500 │ 45.67 % │ 10,448 │ 5,676 │ 5,176 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 1000 │ 64.41 % │ 10,448 │ 3,718 │ 2,718 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,352 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,443 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 500 │ 52.59 % │ 10,448 │ 4,953 │ 4,453 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ -# ╠════════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFUDA 125 │ 12.89 % │ 10,448 │ 9,101 │ 8,981 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 250 │ 16.79 % │ 10,448 │ 8,694 │ 8,444 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 500 │ 52.58 % │ 10,448 │ 4,954 │ 4,454 ║ -# ╟────────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 1000 │ 52.58 % │ 10,448 │ 4,954 │ 3,954 ║ -# ╚════════════╧═════════╧══════════╧═════════════╧══════════════╝ +# ╔═════════════════════╤═════════╤══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ OPT 125 │ 35.16 % │ 10,448 │ 6,774 │ 6,654 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 250 │ 53.33 % │ 10,448 │ 4,876 │ 4,626 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 500 │ 58.12 % │ 10,448 │ 4,376 │ 3,876 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 1000 │ 67.69 % │ 10,448 │ 3,376 │ 2,376 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU 125 │ 12.19 % │ 10,448 │ 9,174 │ 9,054 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 250 │ 13.95 % │ 10,448 │ 8,991 │ 8,741 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 500 │ 48.55 % │ 10,448 │ 5,376 │ 4,876 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 1000 │ 48.55 % │ 10,448 │ 5,376 │ 4,376 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ SLRU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,362 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,443 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 500 │ 52.59 % │ 10,448 │ 4,953 │ 4,453 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ S4LRU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,352 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,453 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 500 │ 51.95 % │ 10,448 │ 5,020 │ 4,540 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ MRU 125 │ 14.23 % │ 10,448 │ 8,961 │ 8,841 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 250 │ 30.84 % │ 10,448 │ 7,226 │ 6,976 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 500 │ 45.67 % │ 10,448 │ 5,676 │ 5,176 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 1000 │ 64.41 % │ 10,448 │ 3,718 │ 2,718 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFU 125 │ 9.34 % │ 10,448 │ 9,472 │ 9,352 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 250 │ 16.80 % │ 10,448 │ 8,693 │ 8,443 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 500 │ 52.59 % │ 10,448 │ 4,953 │ 4,453 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 1000 │ 52.59 % │ 10,448 │ 4,953 │ 3,953 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFUDA 125 │ 12.89 % │ 10,448 │ 9,101 │ 8,981 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 250 │ 16.79 % │ 10,448 │ 8,694 │ 8,444 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 500 │ 52.58 % │ 10,448 │ 4,954 │ 4,454 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 1000 │ 52.58 % │ 10,448 │ 4,954 │ 3,954 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 125 │ 30.64 % │ 10,448 │ 7,247 │ 7,127 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 250 │ 52.41 % │ 10,448 │ 4,972 │ 4,722 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500 │ 55.98 % │ 10,448 │ 4,599 │ 4,099 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 1000 │ 66.56 % │ 10,448 │ 3,494 │ 2,494 ║ +# ╠═════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 125 │ 29.96 % │ 10,448 │ 7,318 │ 7,198 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 250 │ 52.38 % │ 10,448 │ 4,975 │ 4,725 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500 │ 55.98 % │ 10,448 │ 4,599 │ 4,099 ║ +# ╟─────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 1000 │ 66.56 % │ 10,448 │ 3,494 │ 2,494 ║ +# ╚═════════════════════╧═════════╧══════════╧═════════════╧══════════════╝ # lirs-traces="lirs-traces" @@ -271,6 +287,62 @@ akka.cluster.sharding { pattern = lirs-postgres strategy = lfuda-1000 }, + { + name = "LRU/FS/SLRU 125" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-125 + }, + { + name = "LRU/FS/SLRU 250" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-250 + }, + { + name = "LRU/FS/SLRU 500" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-500 + }, + { + name = "LRU/FS/SLRU 1000" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-1000 + }, + { + name = "LRU/FS/SLRU/HC 125" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-hc-125 + }, + { + name = "LRU/FS/SLRU/HC 250" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-hc-250 + }, + { + name = "LRU/FS/SLRU/HC 500" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-hc-500 + }, + { + name = "LRU/FS/SLRU/HC 1000" + shards = 10 + regions = 1 + pattern = lirs-postgres + strategy = lru-fs-slru-hc-1000 + }, ] print-detailed-stats = true @@ -502,5 +574,62 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-125 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 125 + } + + lru-fs-slru-250 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 250 + } + + lru-fs-slru-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 500 + } + + lru-fs-slru-1000 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 1000 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-125 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 125 + } + + lru-fs-slru-hc-250 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 250 + } + + lru-fs-slru-hc-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 500 + } + + lru-fs-slru-hc-1000 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 1000 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/lirs2-trace-w106.conf b/akka-cluster-sharding/src/test/resources/lirs2-trace-w106.conf index 3a36717a14..b1f531083a 100644 --- a/akka-cluster-sharding/src/test/resources/lirs2-trace-w106.conf +++ b/akka-cluster-sharding/src/test/resources/lirs2-trace-w106.conf @@ -11,65 +11,81 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator lirs2-trace-w106 # -# ╔═══════════╤═════════╤═══════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ OPT 50 │ 57.92 % │ 5,292,456 │ 2,226,826 │ 2,226,776 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ OPT 100 │ 66.17 % │ 5,292,456 │ 1,790,310 │ 1,790,210 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ OPT 200 │ 71.69 % │ 5,292,456 │ 1,498,368 │ 1,498,168 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ OPT 500 │ 77.20 % │ 5,292,456 │ 1,206,445 │ 1,205,945 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ LRU 50 │ 45.35 % │ 5,292,456 │ 2,892,538 │ 2,892,488 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LRU 100 │ 53.47 % │ 5,292,456 │ 2,462,659 │ 2,462,559 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LRU 200 │ 64.96 % │ 5,292,456 │ 1,854,353 │ 1,854,153 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LRU 500 │ 70.97 % │ 5,292,456 │ 1,536,203 │ 1,535,703 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ SLRU 50 │ 41.44 % │ 5,292,456 │ 3,099,112 │ 3,099,062 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ SLRU 100 │ 52.52 % │ 5,292,456 │ 2,513,049 │ 2,512,949 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ SLRU 200 │ 60.42 % │ 5,292,456 │ 2,094,936 │ 2,094,736 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ SLRU 500 │ 70.02 % │ 5,292,456 │ 1,586,665 │ 1,586,165 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ S4LRU 50 │ 39.87 % │ 5,292,456 │ 3,182,347 │ 3,182,307 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ S4LRU 100 │ 54.30 % │ 5,292,456 │ 2,418,459 │ 2,418,359 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ S4LRU 200 │ 63.50 % │ 5,292,456 │ 1,931,759 │ 1,931,559 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ S4LRU 500 │ 70.68 % │ 5,292,456 │ 1,551,818 │ 1,551,318 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ MRU 50 │ 22.23 % │ 5,292,456 │ 4,116,089 │ 4,116,039 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ MRU 100 │ 22.36 % │ 5,292,456 │ 4,109,089 │ 4,108,989 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ MRU 200 │ 22.68 % │ 5,292,456 │ 4,092,384 │ 4,092,184 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ MRU 500 │ 23.53 % │ 5,292,456 │ 4,046,928 │ 4,046,428 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ LFU 50 │ 35.92 % │ 5,292,456 │ 3,391,594 │ 3,391,544 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFU 100 │ 42.52 % │ 5,292,456 │ 3,041,915 │ 3,041,815 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFU 200 │ 48.42 % │ 5,292,456 │ 2,729,711 │ 2,729,511 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFU 500 │ 53.57 % │ 5,292,456 │ 2,457,445 │ 2,456,945 ║ -# ╠═══════════╪═════════╪═══════════╪═════════════╪══════════════╣ -# ║ LFUDA 50 │ 46.11 % │ 5,292,456 │ 2,851,965 │ 2,851,915 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFUDA 100 │ 55.62 % │ 5,292,456 │ 2,348,665 │ 2,348,565 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFUDA 200 │ 64.83 % │ 5,292,456 │ 1,861,307 │ 1,861,107 ║ -# ╟───────────┼─────────┼───────────┼─────────────┼──────────────╢ -# ║ LFUDA 500 │ 71.38 % │ 5,292,456 │ 1,514,631 │ 1,514,131 ║ -# ╚═══════════╧═════════╧═══════════╧═════════════╧══════════════╝ +# ╔════════════════════╤═════════╤═══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ OPT 50 │ 57.92 % │ 5,292,456 │ 2,226,826 │ 2,226,776 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ OPT 100 │ 66.17 % │ 5,292,456 │ 1,790,310 │ 1,790,210 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ OPT 200 │ 71.69 % │ 5,292,456 │ 1,498,368 │ 1,498,168 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ OPT 500 │ 77.20 % │ 5,292,456 │ 1,206,445 │ 1,205,945 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ LRU 50 │ 45.35 % │ 5,292,456 │ 2,892,538 │ 2,892,488 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU 100 │ 53.47 % │ 5,292,456 │ 2,462,659 │ 2,462,559 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU 200 │ 64.96 % │ 5,292,456 │ 1,854,353 │ 1,854,153 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU 500 │ 70.97 % │ 5,292,456 │ 1,536,203 │ 1,535,703 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ SLRU 50 │ 41.44 % │ 5,292,456 │ 3,099,112 │ 3,099,062 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ SLRU 100 │ 52.52 % │ 5,292,456 │ 2,513,049 │ 2,512,949 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ SLRU 200 │ 60.42 % │ 5,292,456 │ 2,094,936 │ 2,094,736 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ SLRU 500 │ 70.02 % │ 5,292,456 │ 1,586,665 │ 1,586,165 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ S4LRU 50 │ 39.87 % │ 5,292,456 │ 3,182,347 │ 3,182,307 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ S4LRU 100 │ 54.30 % │ 5,292,456 │ 2,418,459 │ 2,418,359 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ S4LRU 200 │ 63.50 % │ 5,292,456 │ 1,931,759 │ 1,931,559 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ S4LRU 500 │ 70.68 % │ 5,292,456 │ 1,551,818 │ 1,551,318 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ MRU 50 │ 22.23 % │ 5,292,456 │ 4,116,089 │ 4,116,039 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ MRU 100 │ 22.36 % │ 5,292,456 │ 4,109,089 │ 4,108,989 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ MRU 200 │ 22.68 % │ 5,292,456 │ 4,092,384 │ 4,092,184 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ MRU 500 │ 23.53 % │ 5,292,456 │ 4,046,928 │ 4,046,428 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ LFU 50 │ 35.92 % │ 5,292,456 │ 3,391,594 │ 3,391,544 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFU 100 │ 42.52 % │ 5,292,456 │ 3,041,915 │ 3,041,815 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFU 200 │ 48.42 % │ 5,292,456 │ 2,729,711 │ 2,729,511 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFU 500 │ 53.57 % │ 5,292,456 │ 2,457,445 │ 2,456,945 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ LFUDA 50 │ 46.11 % │ 5,292,456 │ 2,851,965 │ 2,851,915 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFUDA 100 │ 55.62 % │ 5,292,456 │ 2,348,665 │ 2,348,565 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFUDA 200 │ 64.83 % │ 5,292,456 │ 1,861,307 │ 1,861,107 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LFUDA 500 │ 71.38 % │ 5,292,456 │ 1,514,631 │ 1,514,131 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 50 │ 34.91 % │ 5,292,456 │ 3,444,873 │ 3,444,823 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 100 │ 45.39 % │ 5,292,456 │ 2,890,305 │ 2,890,205 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 200 │ 52.07 % │ 5,292,456 │ 2,536,639 │ 2,536,439 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500 │ 65.37 % │ 5,292,456 │ 1,832,718 │ 1,832,218 ║ +# ╠════════════════════╪═════════╪═══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 50 │ 46.11 % │ 5,292,456 │ 2,852,272 │ 2,852,222 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 100 │ 55.86 % │ 5,292,456 │ 2,336,245 │ 2,336,145 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 200 │ 65.50 % │ 5,292,456 │ 1,826,070 │ 1,825,870 ║ +# ╟────────────────────┼─────────┼───────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500 │ 71.77 % │ 5,292,456 │ 1,493,991 │ 1,493,491 ║ +# ╚════════════════════╧═════════╧═══════════╧═════════════╧══════════════╝ # lirs2-traces="lirs2-traces" @@ -274,6 +290,62 @@ akka.cluster.sharding { pattern = lirs2-w106 strategy = lfuda-500 }, + { + name = "LRU/FS/SLRU 50" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-50 + }, + { + name = "LRU/FS/SLRU 100" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-100 + }, + { + name = "LRU/FS/SLRU 200" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-200 + }, + { + name = "LRU/FS/SLRU 500" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-500 + }, + { + name = "LRU/FS/SLRU/HC 50" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-hc-50 + }, + { + name = "LRU/FS/SLRU/HC 100" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-hc-100 + }, + { + name = "LRU/FS/SLRU/HC 200" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-hc-200 + }, + { + name = "LRU/FS/SLRU/HC 500" + shards = 5 + regions = 1 + pattern = lirs2-w106 + strategy = lru-fs-slru-hc-500 + }, ] print-detailed-stats = true @@ -505,5 +577,62 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-50 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 50 + } + + lru-fs-slru-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 100 + } + + lru-fs-slru-200 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 200 + } + + lru-fs-slru-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 500 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-50 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 50 + } + + lru-fs-slru-hc-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 100 + } + + lru-fs-slru-hc-200 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 200 + } + + lru-fs-slru-hc-500 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 500 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/reference.conf b/akka-cluster-sharding/src/test/resources/reference.conf index 5eb2e93f1a..9c35785593 100644 --- a/akka-cluster-sharding/src/test/resources/reference.conf +++ b/akka-cluster-sharding/src/test/resources/reference.conf @@ -19,8 +19,34 @@ akka.cluster.sharding { } } least-frequently-used { + per-region-limit = 100000 dynamic-aging = off } + composite { + main.strategy = least-recently-used + admission { + window { + proportion = 0.01 + minimum-proportion = 0.01 + maximum-proportion = 1.0 + strategy = least-recently-used + } + filter = frequency-sketch + frequency-sketch { + depth = 4 + counter-bits = 4 + width-multiplier = 4 + reset-multiplier = 10 + } + optimizer = hill-climbing + hill-climbing { + adjust-multiplier = 10 + initial-step = 0.0625 + restart-threshold = 0.05 + step-decay = 0.98 + } + } + } } pattern-defaults { diff --git a/akka-cluster-sharding/src/test/resources/synthetic-loop.conf b/akka-cluster-sharding/src/test/resources/synthetic-loop.conf index ab8f961103..39f959c5ff 100644 --- a/akka-cluster-sharding/src/test/resources/synthetic-loop.conf +++ b/akka-cluster-sharding/src/test/resources/synthetic-loop.conf @@ -3,13 +3,17 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator synthetic-loop # -# ╔══════════╤═════════╤════════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠══════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ LRU 500k │ 0.00 % │ 10,000,000 │ 10,000,000 │ 9,500,000 ║ -# ╟──────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 500k │ 45.00 % │ 10,000,000 │ 5,500,000 │ 5,000,000 ║ -# ╚══════════╧═════════╧════════════╧═════════════╧══════════════╝ +# ╔═════════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ LRU 500k │ 0.00 % │ 10,000,000 │ 10,000,000 │ 9,500,000 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 500k │ 45.00 % │ 10,000,000 │ 5,500,000 │ 5,000,000 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 500k │ 44.24 % │ 10,000,000 │ 5,576,284 │ 5,076,284 ║ +# ╟─────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 500k │ 44.24 % │ 10,000,000 │ 5,576,284 │ 5,076,284 ║ +# ╚═════════════════════╧═════════╧════════════╧═════════════╧══════════════╝ # akka.cluster.sharding { @@ -28,7 +32,21 @@ akka.cluster.sharding { regions = 10 pattern = loop-1M strategy = mru-50k - } + }, + { + name = "LRU/FS/SLRU 500k" + shards = 100 + regions = 10 + pattern = loop-1M + strategy = lru-fs-slru-50k + }, + { + name = "LRU/FS/SLRU/HC 500k" + shards = 100 + regions = 10 + pattern = loop-1M + strategy = lru-fs-slru-hc-50k + }, ] print-detailed-stats = true @@ -64,5 +82,38 @@ akka.cluster.sharding { per-region-limit = 50000 } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-50k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 50000 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-50k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 50000 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/synthetic-zipfian.conf b/akka-cluster-sharding/src/test/resources/synthetic-zipfian.conf index 39c01c6d9e..c8ba95be69 100644 --- a/akka-cluster-sharding/src/test/resources/synthetic-zipfian.conf +++ b/akka-cluster-sharding/src/test/resources/synthetic-zipfian.conf @@ -3,110 +3,182 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator synthetic-zipfian # +# Zipfian: +# +# ╔════════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ OPT 10k │ 60.36 % │ 50,000,000 │ 19,818,336 │ 19,808,336 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 10k │ 46.99 % │ 50,000,000 │ 26,504,477 │ 26,494,477 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 10k │ 55.23 % │ 50,000,000 │ 22,384,620 │ 22,374,620 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 10k │ 54.94 % │ 50,000,000 │ 22,532,034 │ 22,522,034 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 10k │ 15.89 % │ 50,000,000 │ 42,054,589 │ 42,044,589 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 10k │ 55.41 % │ 50,000,000 │ 22,292,520 │ 22,282,520 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 10k │ 52.15 % │ 50,000,000 │ 23,926,057 │ 23,916,057 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 10k │ 55.67 % │ 50,000,000 │ 22,167,106 │ 22,157,106 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 10k │ 54.43 % │ 50,000,000 │ 22,785,290 │ 22,775,290 ║ +# ╚════════════════════╧═════════╧════════════╧═════════════╧══════════════╝ +# # Scrambled Zipfian: # -# ╔════════════╤═════════╤════════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ OPT 100k │ 53.75 % │ 50,000,000 │ 23,125,433 │ 23,025,433 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 100k │ 40.47 % │ 50,000,000 │ 29,764,380 │ 29,664,380 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 100k │ 47.14 % │ 50,000,000 │ 26,428,026 │ 26,328,026 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 100k │ 47.08 % │ 50,000,000 │ 26,458,377 │ 26,358,377 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 100k │ 10.08 % │ 50,000,000 │ 44,960,042 │ 44,860,042 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 100k │ 46.82 % │ 50,000,000 │ 26,589,475 │ 26,489,475 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 100k │ 43.66 % │ 50,000,000 │ 28,169,941 │ 28,069,941 ║ -# ╚════════════╧═════════╧════════════╧═════════════╧══════════════╝ +# ╔════════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ OPT 10k │ 41.84 % │ 50,000,000 │ 29,079,442 │ 29,069,442 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 10k │ 29.79 % │ 50,000,000 │ 35,105,304 │ 35,095,304 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 10k │ 37.81 % │ 50,000,000 │ 31,095,778 │ 31,085,778 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 10k │ 37.58 % │ 50,000,000 │ 31,208,317 │ 31,198,317 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 10k │ 9.04 % │ 50,000,000 │ 45,479,628 │ 45,469,628 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 10k │ 37.82 % │ 50,000,000 │ 31,088,819 │ 31,078,819 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 10k │ 33.80 % │ 50,000,000 │ 33,098,270 │ 33,088,270 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 10k │ 38.00 % │ 50,000,000 │ 30,997,711 │ 30,987,711 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 10k │ 36.98 % │ 50,000,000 │ 31,510,604 │ 31,500,604 ║ +# ╚════════════════════╧═════════╧════════════╧═════════════╧══════════════╝ # # Shifting Scrambled Zipfian: # -# ╔════════════╤═════════╤════════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪════════════╪═════════════╪══════════════╣ -# ║ OPT 100k │ 32.31 % │ 50,000,000 │ 33,846,785 │ 33,746,785 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LRU 100k │ 22.50 % │ 50,000,000 │ 38,749,623 │ 38,649,623 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ SLRU 100k │ 22.63 % │ 50,000,000 │ 38,682,890 │ 38,582,890 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ S4LRU 100k │ 22.58 % │ 50,000,000 │ 38,711,319 │ 38,611,319 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ MRU 100k │ 9.45 % │ 50,000,000 │ 45,276,420 │ 45,176,420 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFU 100k │ 13.23 % │ 50,000,000 │ 43,385,876 │ 43,285,876 ║ -# ╟────────────┼─────────┼────────────┼─────────────┼──────────────╢ -# ║ LFUDA 100k │ 22.52 % │ 50,000,000 │ 38,739,017 │ 38,639,017 ║ -# ╚════════════╧═════════╧════════════╧═════════════╧══════════════╝ +# ╔════════════════════╤═════════╤════════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪════════════╪═════════════╪══════════════╣ +# ║ OPT 10k │ 24.98 % │ 50,000,000 │ 37,510,100 │ 37,500,100 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU 10k │ 21.86 % │ 50,000,000 │ 39,072,007 │ 39,062,007 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ SLRU 10k │ 21.18 % │ 50,000,000 │ 39,410,297 │ 39,400,297 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ S4LRU 10k │ 21.47 % │ 50,000,000 │ 39,265,652 │ 39,255,652 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ MRU 10k │ 8.60 % │ 50,000,000 │ 45,699,342 │ 45,689,342 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFU 10k │ 9.19 % │ 50,000,000 │ 45,404,024 │ 45,394,024 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LFUDA 10k │ 21.84 % │ 50,000,000 │ 39,077,569 │ 39,067,569 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 10k │ 16.99 % │ 50,000,000 │ 41,503,020 │ 41,493,020 ║ +# ╟────────────────────┼─────────┼────────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 10k │ 21.39 % │ 50,000,000 │ 39,306,616 │ 39,296,616 ║ +# ╚════════════════════╧═════════╧════════════╧═════════════╧══════════════╝ # akka.cluster.sharding { passivation.simulator { runs = [ { - name = "OPT 100k" + name = "OPT 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = optimal-10k + strategy = optimal-1k }, { - name = "LRU 100k" + name = "LRU 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = lru-10k + strategy = lru-1k }, { - name = "SLRU 100k" + name = "SLRU 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = slru-10k + strategy = slru-1k }, { - name = "S4LRU 100k" + name = "S4LRU 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = s4lru-10k + strategy = s4lru-1k }, { - name = "MRU 100k" + name = "MRU 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = mru-10k + strategy = mru-1k }, { - name = "LFU 100k" + name = "LFU 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = lfu-10k + strategy = lfu-1k }, { - name = "LFUDA 100k" + name = "LFUDA 10k" shards = 100 regions = 10 + # pattern = zipfian pattern = scrambled-zipfian # pattern = shifting-scrambled-zipfian - strategy = lfuda-10k + strategy = lfuda-1k + }, + { + name = "LRU/FS/SLRU 10k" + shards = 100 + regions = 10 + # pattern = zipfian + pattern = scrambled-zipfian + # pattern = shifting-scrambled-zipfian + strategy = lru-fs-slru-1k + }, + { + name = "LRU/FS/SLRU/HC 10k" + shards = 100 + regions = 10 + # pattern = zipfian + pattern = scrambled-zipfian + # pattern = shifting-scrambled-zipfian + strategy = lru-fs-slru-hc-1k }, ] print-detailed-stats = true + # zipfian distribution + # generate 50M events over 10M ids + zipfian { + pattern = synthetic + synthetic { + events = 50000000 + generator = zipfian + zipfian { + min = 1 + max = 10000000 + scrambled = off + } + } + } + # scrambled zipfian distribution # generate 50M events over 10M ids scrambled-zipfian { @@ -138,30 +210,30 @@ akka.cluster.sharding { } } - # Optimal (clairvoyant) strategy with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - optimal-10k { + # Optimal (clairvoyant) strategy with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + optimal-1k { strategy = optimal optimal { - per-region-limit = 10000 + per-region-limit = 1000 } } - # LRU strategy with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - lru-10k { + # LRU strategy with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + lru-1k { strategy = least-recently-used least-recently-used { - per-region-limit = 10000 + per-region-limit = 1000 } } - # SLRU strategy (segmented 80% protected) with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - slru-10k { + # SLRU strategy (segmented 80% protected) with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + slru-1k { strategy = least-recently-used least-recently-used { - per-region-limit = 10000 + per-region-limit = 1000 segmented { levels = 2 proportions = [0.2, 0.8] @@ -169,42 +241,78 @@ akka.cluster.sharding { } } - # S4LRU strategy (segmented 4 levels) with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - s4lru-10k { + # S4LRU strategy (segmented 4 levels) with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + s4lru-1k { strategy = least-recently-used least-recently-used { - per-region-limit = 10000 + per-region-limit = 1000 segmented.levels = 4 } } - # MRU strategy with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - mru-10k { + # MRU strategy with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + mru-1k { strategy = most-recently-used most-recently-used { - per-region-limit = 10000 + per-region-limit = 1000 } } - # LFU strategy with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - lfu-10k { + # LFU strategy with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + lfu-1k { strategy = least-frequently-used least-frequently-used { - per-region-limit = 10000 + per-region-limit = 1000 } } - # LFUDA strategy (dynamic aging) with 10k limit in each of 10 regions - # total limit across cluster of 100k (1% of id space) - lfuda-10k { + # LFUDA strategy (dynamic aging) with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + lfuda-1k { strategy = least-frequently-used least-frequently-used { - per-region-limit = 10000 + per-region-limit = 1000 dynamic-aging = on } } + + # Window-TinyLFU strategy with 1k limit in each of 10 regions + # total limit across cluster of 10k (0.1% of id space) + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-1k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 1000 + } + + # Adaptive (hill climbing) Window-TinyLFU strategy + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-1k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 1000 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/text-moby-dick.conf b/akka-cluster-sharding/src/test/resources/text-moby-dick.conf index fa2ec4bb02..30f6744490 100644 --- a/akka-cluster-sharding/src/test/resources/text-moby-dick.conf +++ b/akka-cluster-sharding/src/test/resources/text-moby-dick.conf @@ -5,51 +5,63 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator text-moby-dick # -# ╔═══════════╤═════════╤══════════╤═════════════╤══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ OPT 25 │ 45.52 % │ 216,904 │ 118,161 │ 118,136 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 50 │ 54.52 % │ 216,904 │ 98,658 │ 98,608 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ OPT 100 │ 62.62 % │ 216,904 │ 81,073 │ 80,973 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LRU 25 │ 21.84 % │ 216,904 │ 169,542 │ 169,517 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 50 │ 32.48 % │ 216,904 │ 146,444 │ 146,394 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LRU 100 │ 43.41 % │ 216,904 │ 122,750 │ 122,650 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ SLRU 25 │ 30.22 % │ 216,904 │ 151,349 │ 151,324 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 50 │ 40.14 % │ 216,904 │ 129,845 │ 129,795 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ SLRU 100 │ 50.10 % │ 216,904 │ 108,241 │ 108,141 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ S4LRU 25 │ 30.19 % │ 216,904 │ 151,422 │ 151,398 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 50 │ 40.15 % │ 216,904 │ 129,816 │ 129,768 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ S4LRU 100 │ 50.53 % │ 216,904 │ 107,304 │ 107,204 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ MRU 25 │ 0.30 % │ 216,904 │ 216,250 │ 216,225 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 50 │ 0.46 % │ 216,904 │ 215,899 │ 215,849 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ MRU 100 │ 0.71 % │ 216,904 │ 215,359 │ 215,259 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFU 25 │ 24.55 % │ 216,904 │ 163,651 │ 163,626 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 50 │ 33.91 % │ 216,904 │ 143,345 │ 143,295 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFU 100 │ 43.58 % │ 216,904 │ 122,378 │ 122,278 ║ -# ╠═══════════╪═════════╪══════════╪═════════════╪══════════════╣ -# ║ LFUDA 25 │ 30.80 % │ 216,904 │ 150,106 │ 150,081 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 50 │ 40.90 % │ 216,904 │ 128,180 │ 128,130 ║ -# ╟───────────┼─────────┼──────────┼─────────────┼──────────────╢ -# ║ LFUDA 100 │ 50.79 % │ 216,904 │ 106,731 │ 106,631 ║ -# ╚═══════════╧═════════╧══════════╧═════════════╧══════════════╝ +# ╔════════════════════╤═════════╤══════════╤═════════════╤══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ OPT 25 │ 45.52 % │ 216,904 │ 118,161 │ 118,136 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 50 │ 54.52 % │ 216,904 │ 98,658 │ 98,608 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ OPT 100 │ 62.62 % │ 216,904 │ 81,073 │ 80,973 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU 25 │ 21.84 % │ 216,904 │ 169,542 │ 169,517 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 50 │ 32.48 % │ 216,904 │ 146,444 │ 146,394 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU 100 │ 43.41 % │ 216,904 │ 122,750 │ 122,650 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ SLRU 25 │ 30.22 % │ 216,904 │ 151,349 │ 151,324 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 50 │ 40.14 % │ 216,904 │ 129,845 │ 129,795 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ SLRU 100 │ 50.10 % │ 216,904 │ 108,241 │ 108,141 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ S4LRU 25 │ 30.64 % │ 216,904 │ 150,448 │ 150,423 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 50 │ 40.66 % │ 216,904 │ 128,707 │ 128,657 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ S4LRU 100 │ 50.53 % │ 216,904 │ 107,304 │ 107,204 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ MRU 25 │ 0.30 % │ 216,904 │ 216,250 │ 216,225 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 50 │ 0.46 % │ 216,904 │ 215,899 │ 215,849 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ MRU 100 │ 0.71 % │ 216,904 │ 215,359 │ 215,259 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFU 25 │ 24.55 % │ 216,904 │ 163,651 │ 163,626 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 50 │ 33.91 % │ 216,904 │ 143,345 │ 143,295 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFU 100 │ 43.58 % │ 216,904 │ 122,378 │ 122,278 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LFUDA 25 │ 30.80 % │ 216,904 │ 150,106 │ 150,081 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 50 │ 40.90 % │ 216,904 │ 128,180 │ 128,130 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LFUDA 100 │ 50.79 % │ 216,904 │ 106,731 │ 106,631 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU 25 │ 32.92 % │ 216,904 │ 145,499 │ 145,474 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 50 │ 42.10 % │ 216,904 │ 125,597 │ 125,547 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU 100 │ 51.24 % │ 216,904 │ 105,756 │ 105,656 ║ +# ╠════════════════════╪═════════╪══════════╪═════════════╪══════════════╣ +# ║ LRU/FS/SLRU/HC 25 │ 30.45 % │ 216,904 │ 150,867 │ 150,842 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 50 │ 41.40 % │ 216,904 │ 127,098 │ 127,048 ║ +# ╟────────────────────┼─────────┼──────────┼─────────────┼──────────────╢ +# ║ LRU/FS/SLRU/HC 100 │ 50.97 % │ 216,904 │ 106,345 │ 106,245 ║ +# ╚════════════════════╧═════════╧══════════╧═════════════╧══════════════╝ # text-traces="text-traces" @@ -205,6 +217,48 @@ akka.cluster.sharding { pattern = moby-dick strategy = lfuda-100 }, + { + name = "LRU/FS/SLRU 25" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-25 + }, + { + name = "LRU/FS/SLRU 50" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-50 + }, + { + name = "LRU/FS/SLRU 100" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-100 + }, + { + name = "LRU/FS/SLRU/HC 25" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-hc-25 + }, + { + name = "LRU/FS/SLRU/HC 50" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-hc-50 + }, + { + name = "LRU/FS/SLRU/HC 100" + shards = 1 + regions = 1 + pattern = moby-dick + strategy = lru-fs-slru-hc-100 + }, ] # Moby Dick text as a trace @@ -380,5 +434,54 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-25 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 25 + } + + lru-fs-slru-50 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 50 + } + + lru-fs-slru-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 100 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-25 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 25 + } + + lru-fs-slru-hc-50 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 50 + } + + lru-fs-slru-hc-100 = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 100 + } + } } diff --git a/akka-cluster-sharding/src/test/resources/wikipedia-trace-2018.conf b/akka-cluster-sharding/src/test/resources/wikipedia-trace-2018.conf index d9c5fad6ce..a5b08482c2 100644 --- a/akka-cluster-sharding/src/test/resources/wikipedia-trace-2018.conf +++ b/akka-cluster-sharding/src/test/resources/wikipedia-trace-2018.conf @@ -13,21 +13,25 @@ # # > akka-cluster-sharding/Test/runMain akka.cluster.sharding.passivation.simulator.Simulator wikipedia-trace-2018 # -# ╔════════════╤═════════╤═══════════════╤═══════════════╤═══════════════╗ -# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ -# ╠════════════╪═════════╪═══════════════╪═══════════════╪═══════════════╣ -# ║ LRU 100k │ 53.48 % │ 2,800,000,000 │ 1,302,519,161 │ 1,302,419,161 ║ -# ╟────────────┼─────────┼───────────────┼───────────────┼───────────────╢ -# ║ SLRU 100k │ 60.89 % │ 2,800,000,000 │ 1,095,063,465 │ 1,094,963,465 ║ -# ╟────────────┼─────────┼───────────────┼───────────────┼───────────────╢ -# ║ S4LRU 100k │ 60.66 % │ 2,800,000,000 │ 1,101,617,318 │ 1,101,517,318 ║ -# ╟────────────┼─────────┼───────────────┼───────────────┼───────────────╢ -# ║ MRU 100k │ 5.70 % │ 2,800,000,000 │ 2,640,279,048 │ 2,640,179,048 ║ -# ╟────────────┼─────────┼───────────────┼───────────────┼───────────────╢ -# ║ LFU 100k │ 58.17 % │ 2,800,000,000 │ 1,171,104,161 │ 1,171,004,161 ║ -# ╟────────────┼─────────┼───────────────┼───────────────┼───────────────╢ -# ║ LFUDA 100k │ 60.01 % │ 2,800,000,000 │ 1,119,687,614 │ 1,119,587,614 ║ -# ╚════════════╧═════════╧═══════════════╧═══════════════╧═══════════════╝ +# ╔═════════════════════╤═════════╤═══════════════╤═══════════════╤═══════════════╗ +# ║ Run │ Active │ Accesses │ Activations │ Passivations ║ +# ╠═════════════════════╪═════════╪═══════════════╪═══════════════╪═══════════════╣ +# ║ LRU 100k │ 53.48 % │ 2,800,000,000 │ 1,302,519,161 │ 1,302,419,161 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ SLRU 100k │ 60.89 % │ 2,800,000,000 │ 1,095,063,465 │ 1,094,963,465 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ S4LRU 100k │ 60.66 % │ 2,800,000,000 │ 1,101,617,318 │ 1,101,517,318 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ MRU 100k │ 5.70 % │ 2,800,000,000 │ 2,640,279,048 │ 2,640,179,048 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ LFU 100k │ 58.17 % │ 2,800,000,000 │ 1,171,104,161 │ 1,171,004,161 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ LFUDA 100k │ 60.01 % │ 2,800,000,000 │ 1,119,687,614 │ 1,119,587,614 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ LRU/FS/SLRU 100k │ 61.81 % │ 2,800,000,000 │ 1,069,272,588 │ 1,069,172,588 ║ +# ╟─────────────────────┼─────────┼───────────────┼───────────────┼───────────────╢ +# ║ LRU/FS/SLRU/HC 100k │ 60.54 % │ 2,800,000,000 │ 1,104,800,099 │ 1,104,700,099 ║ +# ╚═════════════════════╧═════════╧═══════════════╧═══════════════╧═══════════════╝ # wiki-traces="wiki-traces" @@ -36,47 +40,61 @@ wiki-traces=${?WIKI_TRACES} akka.cluster.sharding { passivation.simulator { runs = [ +# { +# name = "LRU 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = lru-10k +# }, +# { +# name = "SLRU 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = slru-10k +# }, +# { +# name = "S4LRU 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = s4lru-10k +# }, +# { +# name = "MRU 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = mru-10k +# }, +# { +# name = "LFU 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = lfu-10k +# }, +# { +# name = "LFUDA 100k" +# shards = 100 +# regions = 10 +# pattern = wiki-2018 +# strategy = lfuda-10k +# }, { - name = "LRU 100k" + name = "LRU/FS/SLRU 100k" shards = 100 regions = 10 pattern = wiki-2018 - strategy = lru-10k + strategy = lru-fs-slru-10k }, { - name = "SLRU 100k" + name = "LRU/FS/SLRU/HC 100k" shards = 100 regions = 10 pattern = wiki-2018 - strategy = slru-10k - }, - { - name = "S4LRU 100k" - shards = 100 - regions = 10 - pattern = wiki-2018 - strategy = s4lru-10k - }, - { - name = "MRU 100k" - shards = 100 - regions = 10 - pattern = wiki-2018 - strategy = mru-10k - }, - { - name = "LFU 100k" - shards = 100 - regions = 10 - pattern = wiki-2018 - strategy = lfu-10k - }, - { - name = "LFUDA 100k" - shards = 100 - regions = 10 - pattern = wiki-2018 - strategy = lfuda-10k + strategy = lru-fs-slru-hc-10k }, ] @@ -137,5 +155,38 @@ akka.cluster.sharding { dynamic-aging = on } } + + lru-fs-slru { + strategy = composite + composite { + admission { + window.strategy = least-recently-used + filter = frequency-sketch + optimizer = none + } + main { + strategy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + + lru-fs-slru-10k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.per-region-limit = 10000 + } + + lru-fs-slru-hc = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru} { + composite.admission.optimizer = hill-climbing + } + + lru-fs-slru-hc-10k = ${akka.cluster.sharding.passivation.simulator.lru-fs-slru-hc} { + composite.per-region-limit = 10000 + } + } } 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 260442206e..25ce3d24eb 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 @@ -84,9 +84,22 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { strategy = default-strategy } #passivation-new-default-strategy - """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( limit = 100000, - segmented = List(0.2, 0.8), + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), idle = None) } @@ -100,9 +113,22 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { } } #passivation-new-default-strategy-configured - """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( limit = 1000000, - segmented = List(0.2, 0.8), + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), idle = None) } @@ -116,9 +142,22 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { } } #passivation-new-default-strategy-with-idle - """).passivationStrategy shouldBe ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy( + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( limit = 100000, - segmented = List(0.2, 0.8), + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 30.minutes, interval = 15.minutes))) } @@ -389,6 +428,356 @@ class ClusterShardingSettingsSpec extends AnyWordSpec with Matchers { idle = None) } + "allow passivation strategy admission window policy to be configured (via config)" in { + settings(""" + #admission-window-policy + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission-window + custom-strategy-with-admission-window { + active-entity-limit = 1000000 + admission.window.policy = least-recently-used + replacement.policy = least-frequently-used + } + } + #admission-window-policy + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = + ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(limit = 0, dynamicAging = false, idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.NoAdmissionOptimizer, + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy.AlwaysAdmissionFilter, + idle = None) + } + + "allow passivation strategy admission window proportion to be configured (via config)" in { + settings(""" + #admission-window-proportion + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission-window + custom-strategy-with-admission-window { + active-entity-limit = 1000000 + admission.window { + policy = least-recently-used + proportion = 0.1 # 10% + } + replacement.policy = least-frequently-used + } + } + #admission-window-proportion + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = + ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(limit = 0, dynamicAging = false, idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.1, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.NoAdmissionOptimizer, + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy.AlwaysAdmissionFilter, + idle = None) + } + + "allow passivation strategy admission window optimizer to be configured (via config)" in { + settings(""" + #admission-window-optimizer + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission-window + custom-strategy-with-admission-window { + active-entity-limit = 1000000 + admission.window { + policy = least-recently-used + optimizer = hill-climbing + } + replacement.policy = least-frequently-used + } + } + #admission-window-optimizer + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = + ClusterShardingSettings.LeastFrequentlyUsedPassivationStrategy(limit = 0, dynamicAging = false, idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy.AlwaysAdmissionFilter, + idle = None) + } + + "allow passivation strategy admission to be configured (via config)" in { + settings(""" + #admission-policy + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission + custom-strategy-with-admission { + active-entity-limit = 1000000 + admission { + window { + policy = least-recently-used + optimizer = hill-climbing + } + filter = frequency-sketch + } + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + #admission-policy + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), + idle = None) + } + + "allow passivation strategy admission parameters to be tuned (via config)" in { + settings(""" + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission + custom-strategy-with-admission { + active-entity-limit = 1000000 + admission { + window { + policy = least-recently-used + proportion = 0.1 + minimum-proportion = 0.05 + maximum-proportion = 0.50 + optimizer = hill-climbing + hill-climbing { + adjust-multiplier = 5 + initial-step = 0.05 + restart-threshold = 0.025 + step-decay = 0.95 + } + } + filter = frequency-sketch + frequency-sketch { + depth = 3 + counter-bits = 8 + width-multiplier = 2 + reset-multiplier = 50.0 + } + } + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + } + } + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.1, + minimumWindowProportion = 0.05, + maximumWindowProportion = 0.5, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 5.0, + initialStep = 0.05, + restartThreshold = 0.025, + stepDecay = 0.95), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 2, resetMultiplier = 50.0, depth = 3, counterBits = 8), + idle = None) + } + + "allow passivation strategy admission to be configured (via factory method)" in { + defaultSettings + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withAdmission(ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.defaults + .withWindow(ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.WindowSettings.defaults + .withPolicy(ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults) + .withOptimizer( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.HillClimbingSettings.defaults)) + .withFilter( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.FrequencySketchSettings.defaults)) + .withReplacementPolicy( + ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults + .withSegmented(proportions = List(0.2, 0.8)))) + .passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 42000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), + idle = None) + } + + "allow passivation strategy admission settings to be tuned (via factory method)" in { + defaultSettings + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withAdmission(ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.defaults + .withWindow( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.WindowSettings.defaults + .withPolicy(ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults) + .withInitialProportion(0.1) + .withMinimumProportion(0.05) + .withMaximumProportion(0.5) + .withOptimizer( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.HillClimbingSettings.defaults + .withAdjustMultiplier(5) + .withInitialStep(0.05) + .withRestartThreshold(0.025) + .withStepDecay(0.95))) + .withFilter( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.FrequencySketchSettings.defaults + .withDepth(3) + .withCounterBits(8) + .withWidthMultiplier(2) + .withResetMultiplier(50))) + .withReplacementPolicy( + ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults + .withSegmented(proportions = List(0.2, 0.8)))) + .passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 42000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.1, + minimumWindowProportion = 0.05, + maximumWindowProportion = 0.5, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 5.0, + initialStep = 0.05, + restartThreshold = 0.025, + stepDecay = 0.95), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 2, resetMultiplier = 50.0, depth = 3, counterBits = 8), + idle = None) + } + + "allow passivation strategy with admission and idle timeout to be configured (via config)" in { + settings(""" + akka.cluster.sharding.passivation { + strategy = custom-strategy-with-admission + custom-strategy-with-admission { + active-entity-limit = 1000000 + admission { + window { + policy = least-recently-used + optimizer = hill-climbing + } + filter = frequency-sketch + } + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.2, 0.8] + } + } + } + idle-entity.timeout = 30.minutes + } + } + """).passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 1000000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), + idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 30.minutes, interval = 15.minutes))) + } + + "allow passivation strategy with admission and idle timeout to be configured (via factory method)" in { + defaultSettings + .withPassivationStrategy( + ClusterShardingSettings.PassivationStrategySettings.defaults + .withActiveEntityLimit(42000) + .withAdmission(ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.defaults + .withWindow(ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.WindowSettings.defaults + .withPolicy(ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults) + .withOptimizer( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.HillClimbingSettings.defaults)) + .withFilter( + ClusterShardingSettings.PassivationStrategySettings.AdmissionSettings.FrequencySketchSettings.defaults)) + .withReplacementPolicy( + ClusterShardingSettings.PassivationStrategySettings.LeastRecentlyUsedSettings.defaults.withSegmented( + proportions = List(0.2, 0.8))) + .withIdleEntityPassivation(timeout = 42.minutes)) + .passivationStrategy shouldBe ClusterShardingSettings.CompositePassivationStrategy( + limit = 42000, + mainStrategy = ClusterShardingSettings + .LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = List(0.2, 0.8), idle = None), + windowStrategy = + ClusterShardingSettings.LeastRecentlyUsedPassivationStrategy(limit = 0, segmented = Nil, idle = None), + initialWindowProportion = 0.01, + minimumWindowProportion = 0.01, + maximumWindowProportion = 1.0, + windowOptimizer = ClusterShardingSettings.CompositePassivationStrategy.HillClimbingAdmissionOptimizer( + adjustMultiplier = 10.0, + initialStep = 0.0625, + restartThreshold = 0.05, + stepDecay = 0.98), + admissionFilter = ClusterShardingSettings.CompositePassivationStrategy + .FrequencySketchAdmissionFilter(widthMultiplier = 4, resetMultiplier = 10.0, depth = 4, counterBits = 4), + idle = Some(ClusterShardingSettings.IdlePassivationStrategy(timeout = 42.minutes, interval = 21.minutes))) + } + "disable automatic passivation if `remember-entities` is enabled (via config)" in { settings(""" akka.cluster.sharding.remember-entities = on diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/CompositeSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/CompositeSpec.scala new file mode 100644 index 0000000000..361cdc0050 --- /dev/null +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/CompositeSpec.scala @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2009-2022 Lightbend Inc. + */ + +package akka.cluster.sharding.passivation + +import akka.cluster.sharding.ShardRegion +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory + +import scala.concurrent.duration._ + +object CompositeSpec { + + val admissionWindowAndFilterConfig: Config = ConfigFactory.parseString(""" + akka.cluster.sharding { + passivation { + strategy = lru-fs-slru + lru-fs-slru { + active-entity-limit = 20 + admission { + window { + policy = least-recently-used + proportion = 0.2 + optimizer = none + } + filter = frequency-sketch + } + replacement { + policy = least-recently-used + least-recently-used { + segmented { + levels = 2 + proportions = [0.25, 0.75] + } + } + } + } + } + } + """).withFallback(EntityPassivationSpec.config) + + val admissionFilterNoWindowConfig: Config = ConfigFactory.parseString(""" + akka.cluster.sharding { + passivation { + strategy = fs-lru + fs-lru { + active-entity-limit = 10 + admission { + window.policy = none + filter = frequency-sketch + } + replacement.policy = least-recently-used + } + } + } + """).withFallback(EntityPassivationSpec.config) + + val adaptiveWindowConfig: Config = ConfigFactory.parseString(""" + akka.cluster.sharding { + passivation { + strategy = lru-fs-lru-hc + lru-fs-lru-hc { + active-entity-limit = 10 + admission { + window { + policy = least-recently-used + proportion = 0.1 + minimum-proportion = 0.1 + maximum-proportion = 1.0 + optimizer = hill-climbing + hill-climbing { + adjust-multiplier = 2 + initial-step = 0.4 # big step for testing + restart-threshold = 0.8 # high threshold for testing + step-decay = 0.5 # fast decay for testing + } + } + filter = frequency-sketch + frequency-sketch { + reset-multiplier = 100 + } + } + replacement.policy = least-recently-used + } + } + } + """).withFallback(EntityPassivationSpec.config) + + val idleConfig: Config = ConfigFactory.parseString(""" + akka.cluster.sharding { + passivation { + strategy = default-strategy + default-strategy { + active-entity-limit = 3 + idle-entity.timeout = 1s + } + } + } + """).withFallback(EntityPassivationSpec.config) +} + +class AdmissionWindowAndFilterSpec + extends AbstractEntityPassivationSpec(CompositeSpec.admissionWindowAndFilterConfig, expectedEntities = 41) { + + import EntityPassivationSpec.Entity.Envelope + import EntityPassivationSpec.Entity.Stop + + "Passivation of entities with composite LRU/FS/SLRU strategy" must { + "passivate the least recently or frequently used entities when the per-shard entity limit is reached" in { + val region = start() + + // only one active shard at first + // entities move through the LRU window before moving to main + // candidate entities from window passivated due to admission filter (and same frequency) + // entities are only accessed once (so all in lowest segment) + for (id <- 1 to 40) { + region ! Envelope(shard = 1, id = id, message = "A") + expectReceived(id = id, message = "A") + if (id > 20) expectReceived(id = id - 4, message = Stop) + } + + // shard 1: window: 37-40, main level 0: 1-16, main level 1: empty + expectState(region)(1 -> ((1 to 16) ++ (37 to 40))) + + // increase the frequency of the entities in the main area + // accessing entities a second time moves them to the higher "protected" segment in main area + // when the limit for higher segment is reached, entities are demoted to lower "probationary" segment + // any activated entities will passivate candidates from the window (which have lower frequency) + for (id <- 1 to 20) { + region ! Envelope(shard = 1, id = id, message = "B") + expectReceived(id = id, message = "B") + if (id > 16) expectReceived(id = id + 20, message = Stop) + } + + // shard 1: window: 17-20, main level 0: 1-4, level 1: 5-16 + expectState(region)(1 -> (1 to 20)) + + // cycle through the entities in the window to increase their counts in the frequency sketch + for (id <- 17 to 20; _ <- 1 to 5) { + region ! Envelope(shard = 1, id = id, message = "C") + expectReceived(id = id, message = "C") + } + + // shard 1: window: 17-20, main level 0: 1-4, level 1: 5-16 + expectState(region)(1 -> (1 to 20)) + + // activating new entities will promote candidates from the window to main (higher frequencies now) + for (id <- 21 to 24) { + region ! Envelope(shard = 1, id = id, message = "D") + expectReceived(id = id, message = "D") + expectReceived(id = id - 20, message = Stop) + } + + // shard 1: window: 21-24, main level 0: 17-20, level 1: 5-16 + expectState(region)(1 -> (5 to 24)) + + // activating more new entities will passivate candidates from the window (lower frequencies than main) + for (id <- 25 to 28) { + region ! Envelope(shard = 1, id = id, message = "E") + expectReceived(id = id, message = "E") + expectReceived(id = id - 4, message = Stop) + } + + // shard 1: window: 25-28, main level 0: 17-20, level 1: 5-16 + expectState(region)(1 -> ((5 to 20) ++ (25 to 28))) + + // activating a second shard will divide the per-shard limit in two, passivating half of the first shard + region ! Envelope(shard = 2, id = 41, message = "F") + expectReceived(id = 41, message = "F") + for (id <- List(25, 26, 17, 18, 19, 20, 5, 6, 7, 8)) { + expectReceived(id = id, message = Stop) + } + + // shard 1: window: 27-28, main level 0: 9-10, level 1: 11-16 + // shard 2: window: 41, main level 0: empty, level 1: empty + expectState(region)(1 -> ((9 to 16) ++ (27 to 28)), 2 -> Set(41)) + + // ids 17 and 18 still have higher frequencies, will be promoted through to main from window + for (id <- 17 to 20) { + region ! Envelope(shard = 1, id = id, message = "G") + expectReceived(id = id, message = "G") + if (id == 17) expectReceived(id = 27, message = Stop) + if (id == 18) expectReceived(id = 28, message = Stop) + if (id == 19) expectReceived(id = 9, message = Stop) + if (id == 20) expectReceived(id = 10, message = Stop) + } + + // shard 1: window: 19-20, main level 0: 17-18, level 1: 11-16 + // shard 2: window: 41, main level 0: empty, level 1: empty + expectState(region)(1 -> (11 to 20), 2 -> Set(41)) + } + } +} + +class AdmissionFilterNoWindowSpec + extends AbstractEntityPassivationSpec(CompositeSpec.admissionFilterNoWindowConfig, expectedEntities = 21) { + + import EntityPassivationSpec.Entity.Envelope + import EntityPassivationSpec.Entity.Stop + + "Passivation of entities with composite (no window) FS/LRU strategy" must { + "passivate the least recently or frequently used entities when the per-shard entity limit is reached" in { + val region = start() + + // only one active shard at first + // as entities have same frequency, when limit is reached the candidate entities (11-20) are not admitted to main + for (id <- 1 to 20) { + region ! Envelope(shard = 1, id = id, message = "A") + expectReceived(id = id, message = "A") + if (id > 10) expectReceived(id = id, message = Stop) + } + + // shard 1: 1-10 + expectState(region)(1 -> (1 to 10)) + + // entities 11-20 on second access, so admitted to main, replacing current entities + // cycle through multiple times to increase frequency counts + for (id <- 11 to 20; i <- 1 to 5) { + region ! Envelope(shard = 1, id = id, message = "B") + expectReceived(id = id, message = "B") + if (i == 1) expectReceived(id = id - 10, message = Stop) + } + + // shard 1: 11-20 + expectState(region)(1 -> (11 to 20)) + + // entities 1-10 on second and third access, but not admitted to main as lower frequency + for (id <- 1 to 10; _ <- 1 to 2) { + region ! Envelope(shard = 1, id = id, message = "C") + expectReceived(id = id, message = "C") + expectReceived(id = id, message = Stop) + } + + // activating a second shard will divide the per-shard limit in two, passivating half of the first shard + region ! Envelope(shard = 2, id = 21, message = "D") + expectReceived(id = 21, message = "D") + for (id <- 11 to 15) { + expectReceived(id = id, message = Stop) + } + + // shard 1: 16-20, shard 2: 21 + expectState(region)(1 -> (16 to 20), 2 -> Set(21)) + + // frequency sketch is recreated on limit changes, so all frequencies are 0 again + // candidate entities will replace old entities, but then retain their position + for (id <- 1 to 10) { + region ! Envelope(shard = 1, id = id, message = "E") + expectReceived(id = id, message = "E") + if (id <= 5) expectReceived(id = id + 15, message = Stop) + } + + // shard 1: 16-20, shard 2: 21 + expectState(region)(1 -> (1 to 5), 2 -> Set(21)) + } + } +} + +class AdaptiveAdmissionWindowSpec + extends AbstractEntityPassivationSpec(CompositeSpec.adaptiveWindowConfig, expectedEntities = 70) { + + import EntityPassivationSpec.Entity.Envelope + import EntityPassivationSpec.Entity.Stop + + "Passivation of entities with composite LRU/FS/LRU/HC strategy" must { + "adjust the admission window size when optimizing with hill-climbing algorithm" in { + val region = start() + + // fill admission window and main areas + // increase frequency counts for entities 1-10 + for (id <- 1 to 10; i <- 1 to 10) { + region ! Envelope(shard = 1, id = id, message = s"A$i") + expectReceived(id = id, message = s"A$i") + } + + expectState(region)(1 -> (1 to 10)) + + // window = [10], main = [1, 2, 3, 4, 5, 6, 7, 8, 9] + // window limit is currently 1 (window proportion of 0.1) + // these entities will be passivated as they have lower frequencies + // given drop in active rate the adjustment direction flips to increased (for next step) + // so that window proportion is increased by step of 0.4 (to window limit of 5) on next cycle + for (i <- 1 to 4; id <- 11 to 20) { + region ! Envelope(shard = 1, id = id, message = s"B$i") + expectReceived(id = id, message = s"B$i") + val passivated = if (id == 11 && i > 1) 20 else id - 1 + expectReceived(id = passivated, message = Stop) + } + + expectState(region)(1 -> ((1 to 9) ++ Set(20))) + + // window = [20, 1, 2, 3, 4], main = [5, 6, 7, 8, 9] + // window limit is currently 5 (window proportion of 0.5), direction is increasing + // continue to maintain positive active rate delta to increase window by decayed step of 0.2 + for (id <- 21 to 30; i <- 1 to 2) { + region ! Envelope(shard = 1, id = id, message = s"C$i") + expectReceived(id = id, message = s"C$i") + if (i == 1) { + val passivated = id match { + case 21 => 20 // in window from previous loop + case x if x <= 25 => x - 21 // demoted from main + case x => x - 5 // window size + } + expectReceived(id = passivated, message = Stop) + } + } + + expectState(region)(1 -> ((5 to 9) ++ (26 to 30))) + + // window = [26, 27, 28, 29, 30, 5, 6], main = [7, 8, 9] + // window proportion is currently 0.7, direction is increasing + // continue to maintain positive active rate delta to increase window by decayed step of 0.1 + // increase to high active rate to trigger subsequent restart on delta over threshold + for (id <- 21 to 24; i <- 1 to 5) { + region ! Envelope(shard = 1, id = id, message = s"D$i") + expectReceived(id = id, message = s"D$i") + if (i == 1) expectReceived(id = id + 5, message = Stop) + } + + expectState(region)(1 -> ((5 to 9) ++ (21 to 24) ++ Set(30))) + + // window = [30, 5, 6, 21, 22, 23, 24, 7], main = [8, 9] + // window proportion is currently 0.8, direction is increasing + // drop the active rate to zero to trigger a direction change (decrease of 0.05 to window of 7) + // and hill-climbing restart triggered for next step (change is over restart threshold) + for (i <- 1 to 2; id <- 31 to 40) { + region ! Envelope(shard = 1, id = id, message = s"E$i") + expectReceived(id = id, message = s"E$i") + val previousWindow = IndexedSeq(30, 5, 6, 21, 22, 23, 24, 7) + val passivated = { + if (i == 1 && id <= 38) previousWindow(id - 31) + else if (id <= 38) id + 2 + else id - 8 // window size + } + expectReceived(id = passivated, message = Stop) + } + + expectState(region)(1 -> ((8 to 9) ++ (33 to 40))) + + // window = [34, 35, 36, 37, 38, 39, 40], main = [8, 9, 33] + // window proportion is currently 0.7, direction is decreasing and restarted at initial step + // stable or increasing active rate will keep decreasing window now (step of -0.4) + for (id <- 41 to 50; i <- 1 to 2) { + region ! Envelope(shard = 1, id = id, message = s"F$i") + expectReceived(id = id, message = s"F$i") + if (i == 1) expectReceived(id = id - 7 /* window size */, message = Stop) + } + + expectState(region)(1 -> ((8 to 9) ++ Set(33) ++ (44 to 50))) + + // window = [48, 49, 50], main = [8, 9, 33, 44, 45, 46, 47] + // window proportion is currently 0.3, direction is decreasing, with next step decayed to -0.2 + for (id <- 51 to 60; i <- 1 to 2) { + region ! Envelope(shard = 1, id = id, message = s"G$i") + expectReceived(id = id, message = s"G$i") + if (i == 1) expectReceived(id = id - 3 /* window size */, message = Stop) + } + + expectState(region)(1 -> (Set(60) ++ Set(8, 9, 33, 44, 45, 46, 47, 58, 59))) + + // window = [60], main = [8, 9, 33, 44, 45, 46, 47, 58, 59] + // window proportion is currently 0.1, direction is decreasing, with next step decayed to -0.1 + for (id <- 61 to 70) { + region ! Envelope(shard = 1, id = id, message = s"H") + expectReceived(id = id, message = s"H") + expectReceived(id = id - 1 /* window size */, message = Stop) + } + + expectState(region)(1 -> (Set(70) ++ Set(8, 9, 33, 44, 45, 46, 47, 58, 59))) + } + } +} + +class CompositeWithIdleSpec extends AbstractEntityPassivationSpec(CompositeSpec.idleConfig, expectedEntities = 3) { + + import EntityPassivationSpec.Entity.Envelope + import EntityPassivationSpec.Entity.Stop + + "Passivation of idle entities with least recently used strategy" must { + "passivate entities when they haven't seen messages for the configured timeout" in { + val region = start() + + 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((configuredIdleTimeout / 2).toMillis) + region ! Envelope(shard = 1, id = 3, message = "D") + Thread.sleep((configuredIdleTimeout / 2).toMillis) + region ! Envelope(shard = 1, id = 3, message = "E") + Thread.sleep((configuredIdleTimeout / 2).toMillis) + val lastSendNanoTime2 = System.nanoTime() + region ! Envelope(shard = 1, id = 3, message = "F") + + expectReceived(id = 1, message = "A") + expectReceived(id = 2, message = "B") + expectReceived(id = 3, message = "C") + expectReceived(id = 3, message = "D") + expectReceived(id = 3, message = "E") + 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 = 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 > configuredIdleTimeout + (passivate2.nanoTime - lastSendNanoTime1).nanos should be > configuredIdleTimeout + (passivate3.nanoTime - lastSendNanoTime2).nanos should be > configuredIdleTimeout + } + } +} + +class CompositeLimitAdjustmentSpec + extends AbstractEntityPassivationSpec(CompositeSpec.admissionWindowAndFilterConfig, expectedEntities = 41) { + + import EntityPassivationSpec.Entity.Envelope + import EntityPassivationSpec.Entity.Stop + + "Passivation of least recently used entities" must { + "adjust per-shard entity limits when the per-region limit is dynamically adjusted" in { + val region = start() + + // only one active shard at first, initial per-shard limit of 20 + // entities move through the LRU window before moving to main + // candidate entities from window passivated due to admission filter (and same frequency) + for (id <- 1 to 40) { + region ! Envelope(shard = 1, id = id, message = "A") + expectReceived(id = id, message = "A") + if (id > 20) expectReceived(id = id - 4, message = Stop) + } + + expectState(region)(1 -> ((1 to 16) ++ (37 to 40))) + + // activating a second shard will divide the per-shard limit in two, passivating half of the first shard + region ! Envelope(shard = 2, id = 41, message = "B") + expectReceived(id = 41, message = "B") + for (id <- List(37, 38) ++ (1 to 8)) { + expectReceived(id = id, message = Stop) + } + + expectState(region)(1 -> ((9 to 16) ++ (39 to 40)), 2 -> Set(41)) + + // reduce the per-region limit from 20 to 10, per-shard limit becomes 5 + region ! ShardRegion.SetActiveEntityLimit(10) + for (id <- 39 +: (9 to 12)) { // passivate entities over new limit + expectReceived(id = id, message = Stop) + } + + expectState(region)(1 -> ((13 to 16) ++ Set(40)), 2 -> Set(41)) + + // note: frequency sketch is recreated when limit changes, all frequency counts are 0 again + for (id <- 1 to 10) { + region ! Envelope(shard = 1, id = id, message = "C") + expectReceived(id = id, message = "C") + val passivated = if (id == 1) 40 else if (id <= 5) id + 11 else id - 1 + expectReceived(id = passivated, message = Stop) + } + + expectState(region)(1 -> ((1 to 4) ++ Set(10)), 2 -> Set(41)) + + // increase the per-region limit from 10 to 30, per-shard limit becomes 15 + region ! ShardRegion.SetActiveEntityLimit(30) + + // note: frequency sketch is recreated when limit changes, all frequency counts are 0 again + for (id <- 1 to 20) { + region ! Envelope(shard = 1, id = id, message = "D") + expectReceived(id = id, message = "D") + if (id > 15) { // start passivating window candidates at new higher limit of 15 + expectReceived(id = id - 3 /* new window size */, message = Stop) + } + } + + expectState(region)(1 -> ((1 to 12) ++ (18 to 20)), 2 -> Set(41)) + } + } +} diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/HillClimbingAdmissionOptimizerSpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/HillClimbingAdmissionOptimizerSpec.scala new file mode 100644 index 0000000000..2ed196db7c --- /dev/null +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/HillClimbingAdmissionOptimizerSpec.scala @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 Lightbend Inc. + */ + +package akka.cluster.sharding.passivation + +import akka.cluster.sharding.internal.HillClimbingAdmissionOptimizer +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +class HillClimbingAdmissionOptimizerSpec extends AnyWordSpec with Matchers { + + def create( + initialLimit: Int = 5, + adjustMultiplier: Double = 2.0, + initialStep: Double = 0.01, + restartThreshold: Double = 0.05, + stepDecay: Double = 0.98): HillClimbingAdmissionOptimizer = + new HillClimbingAdmissionOptimizer(initialLimit, adjustMultiplier, initialStep, restartThreshold, stepDecay) + + "HillClimbingAdmissionOptimizer" must { + + "start in decreasing direction" in { + val optimizer = create(initialStep = 0.123) + for (_ <- 1 to 10) optimizer.recordPassive() + optimizer.calculateAdjustment() shouldBe -0.123 + } + + "only adjust every 'adjust size' accesses" in { + val optimizer = create() + for (i <- 1 to 100) { + optimizer.recordPassive() + if (i % 10 == 0) optimizer.calculateAdjustment() should not be 0.0 + else optimizer.calculateAdjustment() shouldBe 0.0 + } + } + + "decay each step when active rate is under restart threshold" in { + val step = 0.1 + val decay = 0.5 + val optimizer = create(initialStep = step, stepDecay = decay) + for (i <- 1 to 100) { + optimizer.recordPassive() + if (i % 10 == 0) optimizer.calculateAdjustment() shouldBe math.pow(decay, i / 10 - 1) * -step + } + } + + "restart when active rate is over restart threshold" in { + val step = 0.1 + val decay = 0.5 + val optimizer = create(initialStep = step, stepDecay = decay) + for (i <- 1 to 100) { + // increase (and maintain) active rate after 40 accesses to trigger restart at 60 then decay again + if (i > 40) optimizer.recordActive() else optimizer.recordPassive() + if (i % 10 == 0) { + val shift = if (i < 60) 1 else 6 + optimizer.calculateAdjustment() shouldBe math.pow(decay, i / 10 - shift) * -step + } + } + } + + "change direction when active rate drops" in { + val optimizer = create() + for (i <- 1 to 500) { + // decrease active rate every 50 accesses to switch direction + val activeRate = math.max(1, 10 - (i / 50)) + if (i % 10 < activeRate) optimizer.recordActive() else optimizer.recordPassive() + if (i % 10 == 0) { + // at 10-50 should be negative direction, at 60-100 should be positive direction, and so on + val adjustment = optimizer.calculateAdjustment() + if (((i - 1) / 50) % 2 == 0) adjustment should be < 0.0 else adjustment should be > 0.0 + } + } + } + + } +} diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/AccessPattern.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/AccessPattern.scala index bd17d5e47b..15013678dc 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/AccessPattern.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/AccessPattern.scala @@ -144,6 +144,19 @@ object TraceFileReader { } } + /** + * Read binary traces from R3 Corda traces. + */ + final class Corda(path: String) extends AccessPattern { + override val isSynthetic = false + + override def entityIds: Source[EntityId, NotUsed] = + FileIO // binary file of longs + .fromPath(Paths.get(path), chunkSize = 8) + .map(bytes => bytes.toByteBuffer.getLong.toString) + .mapMaterializedValue(_ => NotUsed) + } + /** * Read traces provided with the "LIRS" (or "LIRS2") paper: * LIRS: An Efficient Low Inter-reference Recency Set Replacement Policy to Improve Buffer Cache Performance @@ -179,3 +192,11 @@ object TraceFileReader { } } } + +class JoinedAccessPatterns(patterns: Seq[AccessPattern]) extends AccessPattern { + override def isSynthetic: Boolean = + patterns.exists(_.isSynthetic) + + override def entityIds: Source[EntityId, NotUsed] = + patterns.map(_.entityIds).foldLeft(Source.empty[EntityId])(_.concat(_)) +} diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/Simulator.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/Simulator.scala index ed85b6d42e..f71449e1ca 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/Simulator.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/Simulator.scala @@ -6,20 +6,36 @@ package akka.cluster.sharding.passivation.simulator import akka.NotUsed import akka.actor.ActorSystem -import akka.cluster.sharding.internal.{ - EntityPassivationStrategy, - LeastFrequentlyUsedEntityPassivationStrategy, - LeastRecentlyUsedEntityPassivationStrategy, - MostRecentlyUsedEntityPassivationStrategy, - SegmentedLeastRecentlyUsedEntityPassivationStrategy -} -import akka.stream.scaladsl.{ Flow, Source } +import akka.cluster.sharding.internal.ActiveEntities +import akka.cluster.sharding.internal.AdmissionFilter +import akka.cluster.sharding.internal.AdmissionOptimizer +import akka.cluster.sharding.internal.AlwaysAdmissionFilter +import akka.cluster.sharding.internal.CompositeEntityPassivationStrategy +import akka.cluster.sharding.internal.DisabledEntityPassivationStrategy +import akka.cluster.sharding.internal.EntityPassivationStrategy +import akka.cluster.sharding.internal.FrequencySketchAdmissionFilter +import akka.cluster.sharding.internal.HillClimbingAdmissionOptimizer +import akka.cluster.sharding.internal.LeastFrequentlyUsedEntityPassivationStrategy +import akka.cluster.sharding.internal.LeastFrequentlyUsedReplacementPolicy +import akka.cluster.sharding.internal.LeastRecentlyUsedEntityPassivationStrategy +import akka.cluster.sharding.internal.LeastRecentlyUsedReplacementPolicy +import akka.cluster.sharding.internal.MostRecentlyUsedEntityPassivationStrategy +import akka.cluster.sharding.internal.MostRecentlyUsedReplacementPolicy +import akka.cluster.sharding.internal.NoActiveEntities +import akka.cluster.sharding.internal.NoAdmissionOptimizer +import akka.cluster.sharding.internal.SegmentedLeastRecentlyUsedEntityPassivationStrategy +import akka.cluster.sharding.internal.SegmentedLeastRecentlyUsedReplacementPolicy +import akka.stream.scaladsl.Flow +import akka.stream.scaladsl.Source import akka.util.OptionVal import com.typesafe.config.ConfigFactory -import scala.collection.{ immutable, mutable } -import scala.concurrent.{ ExecutionContext, Future } -import scala.util.{ Failure, Success } +import scala.collection.immutable +import scala.collection.mutable +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.util.Failure +import scala.util.Success /** * Simulator for testing the efficiency of passivation strategies. @@ -75,10 +91,10 @@ object Simulator { name = runSettings.name, numberOfShards = runSettings.shards, numberOfRegions = runSettings.regions, - accessPattern = accessPattern(runSettings), + accessPattern = accessPattern(runSettings.pattern), strategyCreator = strategyCreator(runSettings)) - def accessPattern(runSettings: SimulatorSettings.RunSettings): AccessPattern = runSettings.pattern match { + def accessPattern(patternSettings: SimulatorSettings.PatternSettings): AccessPattern = patternSettings match { case SimulatorSettings.PatternSettings.Synthetic(generator, events) => generator match { case SimulatorSettings.PatternSettings.Synthetic.Sequence(start) => @@ -99,6 +115,7 @@ object Simulator { case SimulatorSettings.PatternSettings.Trace(path, format) => format match { case "arc" => new TraceFileReader.Arc(path) + case "corda" => new TraceFileReader.Corda(path) case "lirs" => new TraceFileReader.Lirs(path) case "lirs2" => new TraceFileReader.Lirs2(path) case "simple" => new TraceFileReader.Simple(path) @@ -106,6 +123,8 @@ object Simulator { case "wikipedia" => new TraceFileReader.Wikipedia(path) case _ => sys.error(s"Unknown trace file format [$format]") } + case SimulatorSettings.PatternSettings.Joined(patterns) => + new JoinedAccessPatterns(patterns.map(accessPattern)) } def strategyCreator(runSettings: SimulatorSettings.RunSettings): StrategyCreator = @@ -118,6 +137,10 @@ object Simulator { new MostRecentlyUsedStrategyCreator(perRegionLimit) case SimulatorSettings.StrategySettings.LeastFrequentlyUsed(perRegionLimit, dynamicAging) => new LeastFrequentlyUsedStrategyCreator(perRegionLimit, dynamicAging) + case settings: SimulatorSettings.StrategySettings.Composite => + new CompositeStrategyCreator(settings) + case SimulatorSettings.StrategySettings.NoStrategy => + DisabledStrategyCreator } } @@ -296,6 +319,70 @@ object Simulator { new LeastFrequentlyUsedEntityPassivationStrategy(perRegionLimit, dynamicAging, idleCheck = None)) } + final class CompositeStrategyCreator(settings: SimulatorSettings.StrategySettings.Composite) + extends PassivationStrategyCreator { + override def create(shardId: ShardId): SimulatedStrategy = { + val main = activeEntities(settings.main) + val window = activeEntities(settings.window) + val initialWindowProportion = if (window eq NoActiveEntities) 0.0 else settings.initialWindowProportion + val minimumWindowProportion = if (window eq NoActiveEntities) 0.0 else settings.minimumWindowProportion + val maximumWindowProportion = if (window eq NoActiveEntities) 0.0 else settings.maximumWindowProportion + val windowOptimizer = + if (window eq NoActiveEntities) NoAdmissionOptimizer + else admissionOptimizer(settings.perRegionLimit, settings.optimizer) + val admission = admissionFilter(settings.perRegionLimit, settings.filter) + new PassivationStrategy( + new CompositeEntityPassivationStrategy( + settings.perRegionLimit, + window, + initialWindowProportion, + minimumWindowProportion, + maximumWindowProportion, + windowOptimizer, + admission, + main, + idleCheck = None)) + } + + private def activeEntities(strategySettings: SimulatorSettings.StrategySettings): ActiveEntities = + strategySettings match { + case SimulatorSettings.StrategySettings.LeastRecentlyUsed(perRegionLimit, segmented) if segmented.isEmpty => + new LeastRecentlyUsedReplacementPolicy(perRegionLimit) + case SimulatorSettings.StrategySettings.LeastRecentlyUsed(perRegionLimit, segmented) => + new SegmentedLeastRecentlyUsedReplacementPolicy(perRegionLimit, segmented, idleEnabled = false) + case SimulatorSettings.StrategySettings.MostRecentlyUsed(perRegionLimit) => + new MostRecentlyUsedReplacementPolicy(perRegionLimit) + case SimulatorSettings.StrategySettings.LeastFrequentlyUsed(perRegionLimit, dynamicAging) => + new LeastFrequentlyUsedReplacementPolicy(perRegionLimit, dynamicAging, idleEnabled = false) + case _ => NoActiveEntities + } + + private def admissionOptimizer( + capacity: Int, + optimizerSettings: SimulatorSettings.StrategySettings.AdmissionOptimizerSettings): AdmissionOptimizer = + optimizerSettings match { + case SimulatorSettings.StrategySettings.AdmissionOptimizerSettings.NoOptimizer => NoAdmissionOptimizer + case SimulatorSettings.StrategySettings.AdmissionOptimizerSettings + .HillClimbingOptimizer(adjustMultiplier, initialStep, restartThreshold, stepDecay) => + new HillClimbingAdmissionOptimizer(capacity, adjustMultiplier, initialStep, restartThreshold, stepDecay) + } + + private def admissionFilter( + capacity: Int, + filterSettings: SimulatorSettings.StrategySettings.AdmissionFilterSettings): AdmissionFilter = + filterSettings match { + case SimulatorSettings.StrategySettings.AdmissionFilterSettings.NoFilter => AlwaysAdmissionFilter + case SimulatorSettings.StrategySettings.AdmissionFilterSettings + .FrequencySketchFilter(widthMultiplier, resetMultiplier, depth, counterBits) => + FrequencySketchAdmissionFilter(capacity, widthMultiplier, resetMultiplier, depth, counterBits) + } + } + + object DisabledStrategyCreator extends PassivationStrategyCreator { + override def create(shardId: ShardId): SimulatedStrategy = + new PassivationStrategy(DisabledEntityPassivationStrategy) + } + // Clairvoyant passivation strategy using Bélády's algorithm. // Record virtual access times per entity id on a first pass through the access pattern, // to passivate entities that will not be accessed again for the furthest time in the future. diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/SimulatorSettings.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/SimulatorSettings.scala index 13eb2573b6..917aa46c7e 100644 --- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/SimulatorSettings.scala +++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/passivation/simulator/SimulatorSettings.scala @@ -49,9 +49,40 @@ object SimulatorSettings { final case class LeastRecentlyUsed(perRegionLimit: Int, segmented: immutable.Seq[Double]) extends StrategySettings final case class MostRecentlyUsed(perRegionLimit: Int) extends StrategySettings final case class LeastFrequentlyUsed(perRegionLimit: Int, dynamicAging: Boolean) extends StrategySettings + case object NoStrategy extends StrategySettings + + final case class Composite( + perRegionLimit: Int, + main: StrategySettings, + window: StrategySettings, + initialWindowProportion: Double, + minimumWindowProportion: Double, + maximumWindowProportion: Double, + filter: AdmissionFilterSettings, + optimizer: AdmissionOptimizerSettings) + extends StrategySettings def apply(simulatorConfig: Config, strategy: String): StrategySettings = { - val config = simulatorConfig.getConfig(strategy).withFallback(simulatorConfig.getConfig("strategy-defaults")) + val config = simulatorConfig.getConfig(strategy) + val fallbackConfig = simulatorConfig.getConfig("strategy-defaults") + lowerCase(config.getString("strategy")) match { + case "composite" => + val compositeConfig = config.getConfig("composite").withFallback(fallbackConfig.getConfig("composite")) + Composite( + compositeConfig.getInt("per-region-limit"), + settings(compositeConfig.getConfig("main"), fallbackConfig), + settings(compositeConfig.getConfig("admission.window"), fallbackConfig), + compositeConfig.getDouble("admission.window.proportion"), + compositeConfig.getDouble("admission.window.minimum-proportion"), + compositeConfig.getDouble("admission.window.maximum-proportion"), + AdmissionFilterSettings(compositeConfig), + AdmissionOptimizerSettings(compositeConfig)) + case _ => settings(config, fallbackConfig) + } + } + + private def settings(strategyConfig: Config, fallbackConfig: Config): StrategySettings = { + val config = strategyConfig.withFallback(fallbackConfig) lowerCase(config.getString("strategy")) match { case "optimal" => Optimal(config.getInt("optimal.per-region-limit")) case "least-recently-used" => @@ -70,7 +101,55 @@ object SimulatorSettings { LeastFrequentlyUsed( config.getInt("least-frequently-used.per-region-limit"), config.getBoolean("least-frequently-used.dynamic-aging")) - case _ => sys.error(s"Unknown strategy for [$strategy]") + case _ => NoStrategy + } + } + + sealed trait AdmissionFilterSettings + + object AdmissionFilterSettings { + object NoFilter extends AdmissionFilterSettings + final case class FrequencySketchFilter( + widthMultiplier: Int, + resetMultiplier: Double, + depth: Int, + counterBits: Int) + extends AdmissionFilterSettings + + def apply(config: Config): AdmissionFilterSettings = { + lowerCase(config.getString("admission.filter")) match { + case "frequency-sketch" => + FrequencySketchFilter( + config.getInt("admission.frequency-sketch.width-multiplier"), + config.getDouble("admission.frequency-sketch.reset-multiplier"), + config.getInt("admission.frequency-sketch.depth"), + config.getInt("admission.frequency-sketch.counter-bits")) + case _ => NoFilter + } + } + } + + sealed trait AdmissionOptimizerSettings + + object AdmissionOptimizerSettings { + object NoOptimizer extends AdmissionOptimizerSettings + final case class HillClimbingOptimizer( + adjustMultiplier: Double, + initialStep: Double, + restartThreshold: Double, + stepDecay: Double) + extends AdmissionOptimizerSettings + + def apply(config: Config): AdmissionOptimizerSettings = { + lowerCase(config.getString("admission.optimizer")) match { + case "hill-climbing" => + HillClimbingOptimizer( + config.getDouble("admission.hill-climbing.adjust-multiplier"), + config.getDouble("admission.hill-climbing.initial-step"), + config.getDouble("admission.hill-climbing.restart-threshold"), + config.getDouble("admission.hill-climbing.step-decay")) + case _ => NoOptimizer + } } } } @@ -141,11 +220,21 @@ object SimulatorSettings { } } + final case class Joined(patterns: Seq[PatternSettings]) extends PatternSettings + + object Joined { + def apply(simulatorConfig: Config, patternConfig: Config): Joined = { + val patterns = patternConfig.getStringList("joined").asScala.toSeq + Joined(patterns.map(pattern => PatternSettings(simulatorConfig, pattern))) + } + } + def apply(simulatorConfig: Config, pattern: String): PatternSettings = { val config = simulatorConfig.getConfig(pattern).withFallback(simulatorConfig.getConfig("pattern-defaults")) lowerCase(config.getString("pattern")) match { case "synthetic" => Synthetic(config) case "trace" => Trace(config) + case "joined" => Joined(simulatorConfig, config) case _ => sys.error(s"Unknown pattern for [$pattern]") } } diff --git a/akka-docs/src/main/paradox/typed/cluster-sharding.md b/akka-docs/src/main/paradox/typed/cluster-sharding.md index 8838702a96..e0f5e16668 100644 --- a/akka-docs/src/main/paradox/typed/cluster-sharding.md +++ b/akka-docs/src/main/paradox/typed/cluster-sharding.md @@ -337,8 +337,13 @@ Cluster Sharding, can be enabled with configuration: @@snip [passivation new default strategy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #passivation-new-default-strategy type=conf } -This default strategy uses a [segmented least recently used policy](#segmented-least-recently-used-policy). The active -entity limit can be configured: +This default strategy uses a [composite passivation strategy](#composite-passivation-strategies) which combines +recency-based and frequency-based tracking: the main area is configured with a [segmented least recently used +policy](#segmented-least-recently-used-policy) with a frequency-biased [admission filter](#admission-filter), fronted +by a recency-biased [admission window](#admission-window-policy) with [adaptive sizing](#admission-window-optimizer) +enabled. + +The active entity limit for the default strategy 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 } @@ -456,6 +461,63 @@ Configure dynamic aging with the least frequently used policy: Or using custom `ClusterShardingSettings.PassivationStrategySettings.LeastFrequentlyUsedSettings`. +### Composite passivation strategies + +Passivation strategies can be combined using an admission window and admission filter. The admission window tracks +newly activated entities. Entities are replaced in the admission window using one of the replacement policies, such as +the least recently used replacement policy. When an entity is replaced in the window area it has an opportunity to +enter the main entity tracking area, based on the admission filter. The admission filter determines whether an entity +that has left the window area should be admitted into the main area, or otherwise be passivated. A frequency sketch is +the default admission filter and estimates the access frequency of entities over the lifespan of the cluster sharding +node, selecting the entity that is estimated to be accessed more frequently. Composite passivation strategies with an +admission window and admission filter are implementing the _Window-TinyLFU_ caching algorithm. + +#### Admission window policy + +The admission window tracks newly activated entities. When an entity is replaced in the window area, it has an +opportunity to enter the main entity tracking area, based on the [admission filter](#admission-filter). The admission +window can be enabled by selecting a policy (while the regular replacement policy is for the main area): + +@@snip [admission window policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #admission-window-policy type=conf } + +The proportion of the active entity limit used for the admission window can be configured (the default is 1%): + +@@snip [admission window proportion](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #admission-window-proportion type=conf } + +The proportion for the admission window can also be adapted and optimized dynamically, by enabling an [admission window +optimizer](#admission-window-optimizer). + +#### Admission window optimizer + +The proportion of the active entity limit used for the admission window can be adapted dynamically using an optimizer. +The window area will usually retain entities that are accessed again in a short time (recency-biased), while the main +area can track entities that are accessed more frequently over longer times (frequency-biased). If access patterns for +entities are changeable, then the adaptive sizing of the window allows the passivation strategy to adapt between +recency-biased and frequency-biased workloads. + +The optimizer currently available uses a simple hill-climbing algorithm, which searches for a window proportion that +provides an optimal active rate (where entities are already active when accessed, the _cache hit rate_). Enable +adaptive window sizing by configuring the `hill-climbing` window optimizer: + +@@snip [admission window optimizer](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #admission-window-optimizer type=conf } + +See the `reference.conf` for parameters that can be tuned for the hill climbing admission window optimizer. + +#### Admission filter + +An admission filter can be enabled, which determines whether an entity that has left the window area (or a newly +activated entity if there is no admission window) should be admitted into the main entity tracking area, or otherwise +be passivated. If no admission filter is configured, then entities will always be admitted into the main area. + +A frequency sketch is the default admission filter and estimates the access frequency of entities over the lifespan of +the cluster sharding node, selecting the entity that is estimated to be accessed more frequently. The frequency sketch +automatically ages entries, using the approach from the _TinyLFU_ cache admission algorithm. Enable an admission filter +by configuring the `frequency-sketch` admission filter: + +@@snip [admission policy](/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ClusterShardingSettingsSpec.scala) { #admission-policy type=conf } + +See the `reference.conf` for parameters that can be tuned for the frequency sketch admission filter. + ## Sharding State