public static IReadOnlyList <RedisClusterSize> ComputeShortestPath(RedisClusterSize from, RedisClusterSize to, Func <RedisClusterSize, IEnumerable <RedisClusterSize> > neighbors, Func <RedisClusterSize, RedisClusterSize, double> weight, IReadOnlyList <RedisClusterSize>?vertices = null) { if (from.Equals(to)) { return(Array.Empty <RedisClusterSize>()); } vertices ??= RedisClusterSize.Instances; var shortestPaths = ComputeOneToAllShortestPath(vertices, neighbors, weight, from); return(ComputeShortestPath(shortestPaths, from, to)); }
/// <summary> /// This function embodies the concept of "how much does it cost to switch from /// <paramref name="current"/> to <paramref name="target"/>". At this point, we can assume that: /// - The two input sizes are valid states to be in /// - We can reach the target from current via some amount of autoscaling operations /// Hence, we're just ranking amonst the many potential states. /// </summary> public static double CostFunction(RedisClusterSize current, RedisClusterSize target, ModelContext modelContext, IReadOnlyDictionary <RedisClusterSize, RedisScalingUtilities.Node> shortestPaths) { // Switching to the same size (i.e. no op) is free if (current.Equals(target)) { return(0); } var shortestPath = RedisScalingUtilities.ComputeShortestPath(shortestPaths, current, target); Contract.Assert(shortestPath.Count > 0); // Positive if we are spending more money, negative if we are saving return((double)(target.MonthlyCostUsd - current.MonthlyCostUsd)); }
public static bool CanScale(RedisClusterSize from, RedisClusterSize to) { if (from.Equals(to)) { return(true); } if (!CanScale(from.Tier, to.Tier)) { return(false); } if (from.Shards != to.Shards && !from.Tier.Equals(to.Tier)) { // Azure can't change both shards and tiers at once, we need to do them one at a time. return(false); } return(true); }
public static TimeSpan ExpectedScalingDelay(RedisClusterSize from, RedisClusterSize to) { Contract.Requires(CanScale(from, to)); if (from.Equals(to)) { return(TimeSpan.Zero); } if (from.Tier.Equals(to.Tier)) { // The tier is the same, so autoscaling will be either adding or reducing shards var shardDelta = Math.Abs(from.Shards - to.Shards); return(TimeSpan.FromTicks(Constants.RedisScaleTimePerShard.Ticks * shardDelta)); } else { // Tier changed, which means the number of shards didn't. However, we will take the same amount of time // as the amount of shards that need to change tier. Contract.Assert(from.Shards == to.Shards); return(TimeSpan.FromTicks(Constants.RedisScaleTimePerShard.Ticks * from.Shards)); } }
public static IReadOnlyList <RedisClusterSize> ComputeShortestPath(IReadOnlyDictionary <RedisClusterSize, Node> shortestPaths, RedisClusterSize from, RedisClusterSize to) { if (from.Equals(to)) { return(Array.Empty <RedisClusterSize>()); } var path = new List <Node>(); var current = shortestPaths[to]; while (current.Predecessor != null) { path.Add(current); current = current.Predecessor; } if (!current.ClusterSize.Equals(from)) { return(Array.Empty <RedisClusterSize>()); } path.Reverse(); return(path.Select(p => p.ClusterSize).ToList()); }