You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@brooklyn.apache.org by Aled Sage <al...@gmail.com> on 2014/10/27 11:47:03 UTC

AutoScalerPolicy: can resize too large

Hi all,

We have a problem with AutoScalerPolicy, as demonstrated by 
`TomcatAutoScalerPolicyTest` which fails non-deterministically.

*--- SUMMARY OF PROBLEM ---*
(for a more detailed description, jump to the "problem details" section 
at the end).

Imagine one has an enricher to calculate average-cpu-load across a 
cluster, and a policy to keep that average between 30% and 60% per node.

When the AutoScalerPolicy calculates the optimal cluster size based on 
the metric's value and the current cluster size - its "current size" is 
*all* nodes, including those that are still starting, which have not 
contributed to the load. Therefore it acts as though those new nodes 
were already pulling their weight, and resizes too big.

  * AutoScalerPolicy uses a different "current size" operator from that
    used by the enricher calculating the average.
  * AutoScaler's extrapolation logic (to calculate desired size)
    duplicates (part of) the logic in the original enricher (but using a
    different number!).

Note that I really like our separation of enricher from policy. The 
enricher can perform complex aggregation of multiple metrics (e.g. CPU, 
queue-length, request count, etc), which is independent of there being 
an auto-scaler. The metric could also come from an external source that 
is just summarising the cluster (i.e. Brooklyn might not have the 
individual metrics for each member; but I don't think we've hit that 
use-case in the wild yet).

Also note that the AutoScalerPolicy just acts on a Resizable: it calls 
`int getCurrentSize()` and `int resize(int desiredSize)`. Therefore it 
could easily be applied to vertically scaling a component as well as 
horizontally scaling a cluster.

*--- PROPOSAL ---*

Add to Resizable a new method `getActiveSize()`, which returns the 
number of active nodes. This would often be nodes reporting 
serviceUp=true && state=running.

Add support for configuring an Aggregator enricher to only use nodes 
matching some state (e.g. serviceUp=true && state=running).
(Current it just accepts any non-null value from the members).

Rely on the stabilisation-delay config for any races where the policy 
and enricher get different counts for the "active size".
i.e. we only resize up or down if we consistently need to do so for 
several seconds/minutes.

*---**ALTERNATIVE SOLUTIONS ---*
Alternatives include:

 1. Change resizable.getCurrentSize() implementation of cluster to
    return only the active-member count.
    (currently it inherits getCurrentSize() from `AbstractGroup`, which
    just does a simple count of the members.
 2. Don't use the aggregating enricher.
    We could configure AutoScalerPolicy with the metric to read from
    each member, and have the auto-scaler do the aggregation across the
    members.

For (1), I like that it doesn't change the interface (which breaks 
backwards compatibility for implementers).
But I prefer the simplicity of current size telling us the member count.

For (2), that feels like putting too much responsibility into the single 
AutoScalerPolicy class.
Better to strive for composability of policies/enrichers.

*--- PROBLEM DETAILS ---*
The formula used by the AutoScalerPolicy (simplified to strip out 
stabilisation delays, max pool size, etc) is:

    // metric to be kept within the given range
    double currentMetric = ...;
    double metricUpperBound = ...;
    double metricLowerBound = ...;

    int currentSize = ...;
    double currentTotalActivity = currentSize * currentMetric;

    if (currentMetric > metricUpperBound) {
         // scale out
         desiredSize =
    (int)Math.ceil(currentTotalActivity/metricUpperBound);

A simple example would be to use the cpu-usage average per node, to be 
kept in the range 30% to 60%.

  * An enricher would aggregate the CPU usage per node, to have a single
    metric on the cluster (e.g.
    PROCESS_CPU_TIME_FRACTION_IN_WINDOW_PER_NODE).
    This enricher would ignore null values for nodes still starting up.
  * The auto-scaler policy finds the currentSize by calling
    resizable.currentSize(), which just counts the members (rather than
    those with serviceUp).

  * So if we have two running members and a third still starting, with
    cpu values 95%, 95% and null respectively...
    The cluster's PROCESS_CPU_TIME_FRACTION_IN_WINDOW_PER_NODE will be 95%.
    The currentSize will be 3.
    The auto-scaler will calculate desired size as (0.95 * 3) / 0.6 =
    4.75; rounded up to 5.
    It should have done (0.95 * 2) / 0.6 = 3.167; rounded up to 4.