Skip to content

Commit

Permalink
feat(aws-applicationautoscaling): Allow autoscaling with "M out of N"…
Browse files Browse the repository at this point in the history
… datapoints (#17441)

This PR closes #17433. It adds a `datapointsToAlarm` property to the `StepScalingPolicy` construct which allows auto-scaling activities to trigger when only a portion of the data points in the evaluation periods are breaching.

Motivation: Some metrics may have a certain amount of noise/randomness and in these cases it may make more sense to not require that all data points must be breaching for auto-scaling activity to trigger.
----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
stephenwiebe authored Dec 14, 2021
1 parent 9af5b4d commit c21320d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-applicationautoscaling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,37 @@ capacity.scaleOnMetric('ScaleToCPU', {
The AutoScaling construct library will create the required CloudWatch alarms and
AutoScaling policies for you.

### Scaling based on multiple datapoints

The Step Scaling configuration above will initiate a scaling event when a single
datapoint of the scaling metric is breaching a scaling step breakpoint. In cases
where you might want to initiate scaling actions on a larger number of datapoints
(ie in order to smooth out randomness in the metric data), you can use the
optional `evaluationPeriods` and `datapointsToAlarm` properties:

```ts
declare const capacity: ScalableAttribute;
declare const cpuUtilization: cloudwatch.Metric;

capacity.scaleOnMetric('ScaleToCPUWithMultipleDatapoints', {
metric: cpuUtilization,
scalingSteps: [
{ upper: 10, change: -1 },
{ lower: 50, change: +1 },
{ lower: 70, change: +3 },
],

// if the cpuUtilization metric has a period of 1 minute, then data points
// in the last 10 minutes will be evaluated
evaluationPeriods: 10,

// Only trigger a scaling action when 6 datapoints out of the last 10 are
// breaching. If this is left unspecified, then ALL datapoints in the
// evaluation period must be breaching to trigger a scaling action
datapointsToAlarm: 6
});
```

## Target Tracking Scaling

This type of scaling scales in and out in order to keep a metric (typically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,26 @@ export interface BasicStepScalingPolicyProps {
* Raising this value can be used to smooth out the metric, at the expense
* of slower response times.
*
* If `datapointsToAlarm` is not set, then all data points in the evaluation period
* must meet the criteria to trigger a scaling action.
*
* @default 1
*/
readonly evaluationPeriods?: number;

/**
* The number of data points out of the evaluation periods that must be breaching to
* trigger a scaling action
*
* Creates an "M out of N" alarm, where this property is the M and the value set for
* `evaluationPeriods` is the N value.
*
* Only has meaning if `evaluationPeriods != 1`.
*
* @default `evaluationPeriods`
*/
readonly datapointsToAlarm?: number;

/**
* Aggregation to apply to all data points over the evaluation periods
*
Expand Down Expand Up @@ -99,6 +115,10 @@ export class StepScalingPolicy extends CoreConstruct {
throw new Error('You must supply at least 2 intervals for autoscaling');
}

if (props.datapointsToAlarm !== undefined && props.datapointsToAlarm < 1) {
throw new RangeError(`datapointsToAlarm cannot be less than 1, got: ${props.datapointsToAlarm}`);
}

const adjustmentType = props.adjustmentType || AdjustmentType.CHANGE_IN_CAPACITY;
const changesAreAbsolute = adjustmentType === AdjustmentType.EXACT_CAPACITY;

Expand Down Expand Up @@ -130,6 +150,7 @@ export class StepScalingPolicy extends CoreConstruct {
alarmDescription: 'Lower threshold scaling alarm',
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: props.evaluationPeriods ?? 1,
datapointsToAlarm: props.datapointsToAlarm,
threshold,
});
this.lowerAlarm.addAlarmAction(new StepScalingAlarmAction(this.lowerAction));
Expand Down Expand Up @@ -160,6 +181,7 @@ export class StepScalingPolicy extends CoreConstruct {
alarmDescription: 'Upper threshold scaling alarm',
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
evaluationPeriods: props.evaluationPeriods ?? 1,
datapointsToAlarm: props.datapointsToAlarm,
threshold,
});
this.upperAlarm.addAlarmAction(new StepScalingAlarmAction(this.upperAction));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,62 @@ describe('step scaling policy', () => {


});

test('step scaling with evaluation period & data points to alarm configured', () => {
// GIVEN
const stack = new cdk.Stack();
const target = createScalableTarget(stack);

// WHEN
target.scaleOnMetric('Tracking', {
metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }),
scalingSteps: [
{ upper: 0, change: -1 },
{ lower: 100, change: +1 },
{ lower: 500, change: +5 },
],
evaluationPeriods: 10,
datapointsToAlarm: 6,
metricAggregationType: appscaling.MetricAggregationType.MAXIMUM,
});

// THEN
expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
PolicyType: 'StepScaling',
StepScalingPolicyConfiguration: {
AdjustmentType: 'ChangeInCapacity',
MetricAggregationType: 'Maximum',
},
});
expect(stack).toHaveResource('AWS::CloudWatch::Alarm', {
ComparisonOperator: 'GreaterThanOrEqualToThreshold',
EvaluationPeriods: 10,
DatapointsToAlarm: 6,
ExtendedStatistic: 'p99',
MetricName: 'Metric',
Namespace: 'Test',
Threshold: 100,
});
});

test('step scaling with invalid datapointsToAlarm throws error', () => {
const stack = new cdk.Stack();
const target = createScalableTarget(stack);

expect(() => {
target.scaleOnMetric('Tracking', {
metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }),
scalingSteps: [
{ upper: 0, change: -1 },
{ lower: 100, change: +1 },
{ lower: 500, change: +5 },
],
evaluationPeriods: 10,
datapointsToAlarm: 0,
metricAggregationType: appscaling.MetricAggregationType.MAXIMUM,
});
}).toThrow('datapointsToAlarm cannot be less than 1, got: 0');
});
});

/**
Expand Down

0 comments on commit c21320d

Please sign in to comment.