Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-applicationautoscaling): Allow autoscaling with "M out of N" datapoints #17441

Merged
merged 6 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
stephenwiebe marked this conversation as resolved.
Show resolved Hide resolved
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,
stephenwiebe marked this conversation as resolved.
Show resolved Hide resolved
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', () => {
stephenwiebe marked this conversation as resolved.
Show resolved Hide resolved
// 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