Esempio n. 1
0
        private static Result <ModelOutput> Predict(RedisClusterSize currentClusterSize, ModelContext modelContext)
        {
            // TODO: autoscaler should consider the server load percentage as well. If a shard had a very high load
            // percentage, it means that it is for some reason receiving an uneven load. Hence, adding shards helps in
            // this situation. There is no easy way to add that to the current model. Ideas:
            //  - If any server reached a load >70% at any time in the period analyzed, we need to guarantee that
            //    there's at least as many shards as there were before (i.e. no downscales are allowed).
            var shortestPaths = ComputeAllowedPaths(currentClusterSize, modelContext);

            var eligibleClusterSizes = shortestPaths
                                       .Select(kvp => (Size: kvp.Key, Node: kvp.Value))
                                       // Find all plans that we can reach from the current one via scaling operations, and that we allow scaling to
                                       .Where(entry => entry.Node.ShortestDistanceFromSource != double.PositiveInfinity && IsScalingAllowed(currentClusterSize, entry.Size, modelContext))
                                       // Compute the cost of taking the given route
                                       .Select(entry => (entry.Size, entry.Node, Cost: CostFunction(currentClusterSize, entry.Size, modelContext, shortestPaths)))
                                       .ToList();

            // Rank them by cost ascending
            var costSorted = eligibleClusterSizes
                             .OrderBy(pair => pair.Cost)
                             .ToList();

            if (costSorted.Count == 0)
            {
                return(new Result <ModelOutput>(errorMessage: "No cluster size available for scaling"));
            }

            return(new ModelOutput(
                       targetClusterSize: costSorted[0].Size,
                       modelContext: modelContext,
                       cost: costSorted[0].Cost,
                       scalePath: RedisScalingUtilities.ComputeShortestPath(shortestPaths, currentClusterSize, costSorted[0].Size)));
        }
Esempio n. 2
0
        private Result <ModelOutput> Predict(RedisClusterSize currentClusterSize, ModelContext modelContext)
        {
            var shortestPaths = ComputeAllowedPaths(currentClusterSize, modelContext);

            var eligibleClusterSizes = shortestPaths
                                       .Select(kvp => (Size: kvp.Key, Node: kvp.Value))
                                       // Find all plans that we can reach from the current one via scaling operations, and that we allow scaling to
                                       .Where(entry => entry.Node.ShortestDistanceFromSource != double.PositiveInfinity && IsScalingAllowed(currentClusterSize, entry.Size, modelContext))
                                       // Compute the cost of taking the given route
                                       .Select(entry => (entry.Size, entry.Node, Cost: CostFunction(currentClusterSize, entry.Size, modelContext, shortestPaths)))
                                       .ToList();

            // Rank them by cost ascending
            var costSorted = eligibleClusterSizes
                             .OrderBy(pair => pair.Cost)
                             .ToList();

            if (costSorted.Count == 0)
            {
                return(new Result <ModelOutput>(errorMessage: "No cluster size available for scaling"));
            }

            return(new ModelOutput(
                       targetClusterSize: costSorted[0].Size,
                       modelContext: modelContext,
                       cost: costSorted[0].Cost,
                       scalePath: RedisScalingUtilities.ComputeShortestPath(shortestPaths, currentClusterSize, costSorted[0].Size)));
        }
Esempio n. 3
0
        public void FailsOnNonExistantRoute()
        {
            var from = RedisClusterSize.Parse("P1/1");
            var to   = RedisClusterSize.Parse("P3/3");
            var path = RedisScalingUtilities.ComputeShortestPath(from, to, size => new RedisClusterSize[] { }, (f, t) => 1);

            path.Should().BeEmpty();
        }
Esempio n. 4
0
        public void SucceedsOnSimpleRoute()
        {
            var from = RedisClusterSize.Parse("P1/1");
            var to   = RedisClusterSize.Parse("P3/3");
            var path = RedisScalingUtilities.ComputeShortestPath(from, to, size => size.ScaleEligibleSizes, (f, t) => 1);

            path.Should().BeEquivalentTo(new RedisClusterSize[] { RedisClusterSize.Parse("P3/1"), RedisClusterSize.Parse("P3/3") });
        }
Esempio n. 5
0
        public void CanFindEmptyRoute()
        {
            var from = RedisClusterSize.Parse("P1/1");
            var to   = RedisClusterSize.Parse("P1/1");
            var path = RedisScalingUtilities.ComputeShortestPath(from, to, size => size.ScaleEligibleSizes, (f, t) => 1);

            path.Should().BeEmpty();
        }
Esempio n. 6
0
        public void CanFindSingleRoute()
        {
            var from = RedisClusterSize.Parse("P1/1");
            var to   = RedisClusterSize.Parse("P1/2");
            var path = RedisScalingUtilities.ComputeShortestPath(from, to, size => size.ScaleEligibleSizes, (f, t) => 1);

            path.Count.Should().Be(1);
            path[0].Should().Be(to);
        }
Esempio n. 7
0
        /// <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>
        private 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));
        }