Skip to content

Commit c21320d

Browse files
authoredDec 14, 2021
feat(aws-applicationautoscaling): Allow autoscaling with "M out of N" 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*
1 parent 9af5b4d commit c21320d

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed
 

‎packages/@aws-cdk/aws-applicationautoscaling/README.md

+31
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,37 @@ capacity.scaleOnMetric('ScaleToCPU', {
106106
The AutoScaling construct library will create the required CloudWatch alarms and
107107
AutoScaling policies for you.
108108

109+
### Scaling based on multiple datapoints
110+
111+
The Step Scaling configuration above will initiate a scaling event when a single
112+
datapoint of the scaling metric is breaching a scaling step breakpoint. In cases
113+
where you might want to initiate scaling actions on a larger number of datapoints
114+
(ie in order to smooth out randomness in the metric data), you can use the
115+
optional `evaluationPeriods` and `datapointsToAlarm` properties:
116+
117+
```ts
118+
declare const capacity: ScalableAttribute;
119+
declare const cpuUtilization: cloudwatch.Metric;
120+
121+
capacity.scaleOnMetric('ScaleToCPUWithMultipleDatapoints', {
122+
metric: cpuUtilization,
123+
scalingSteps: [
124+
{ upper: 10, change: -1 },
125+
{ lower: 50, change: +1 },
126+
{ lower: 70, change: +3 },
127+
],
128+
129+
// if the cpuUtilization metric has a period of 1 minute, then data points
130+
// in the last 10 minutes will be evaluated
131+
evaluationPeriods: 10,
132+
133+
// Only trigger a scaling action when 6 datapoints out of the last 10 are
134+
// breaching. If this is left unspecified, then ALL datapoints in the
135+
// evaluation period must be breaching to trigger a scaling action
136+
datapointsToAlarm: 6
137+
});
138+
```
139+
109140
## Target Tracking Scaling
110141

111142
This type of scaling scales in and out in order to keep a metric (typically

‎packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts

+22
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,26 @@ export interface BasicStepScalingPolicyProps {
5858
* Raising this value can be used to smooth out the metric, at the expense
5959
* of slower response times.
6060
*
61+
* If `datapointsToAlarm` is not set, then all data points in the evaluation period
62+
* must meet the criteria to trigger a scaling action.
63+
*
6164
* @default 1
6265
*/
6366
readonly evaluationPeriods?: number;
6467

68+
/**
69+
* The number of data points out of the evaluation periods that must be breaching to
70+
* trigger a scaling action
71+
*
72+
* Creates an "M out of N" alarm, where this property is the M and the value set for
73+
* `evaluationPeriods` is the N value.
74+
*
75+
* Only has meaning if `evaluationPeriods != 1`.
76+
*
77+
* @default `evaluationPeriods`
78+
*/
79+
readonly datapointsToAlarm?: number;
80+
6581
/**
6682
* Aggregation to apply to all data points over the evaluation periods
6783
*
@@ -99,6 +115,10 @@ export class StepScalingPolicy extends CoreConstruct {
99115
throw new Error('You must supply at least 2 intervals for autoscaling');
100116
}
101117

118+
if (props.datapointsToAlarm !== undefined && props.datapointsToAlarm < 1) {
119+
throw new RangeError(`datapointsToAlarm cannot be less than 1, got: ${props.datapointsToAlarm}`);
120+
}
121+
102122
const adjustmentType = props.adjustmentType || AdjustmentType.CHANGE_IN_CAPACITY;
103123
const changesAreAbsolute = adjustmentType === AdjustmentType.EXACT_CAPACITY;
104124

@@ -130,6 +150,7 @@ export class StepScalingPolicy extends CoreConstruct {
130150
alarmDescription: 'Lower threshold scaling alarm',
131151
comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
132152
evaluationPeriods: props.evaluationPeriods ?? 1,
153+
datapointsToAlarm: props.datapointsToAlarm,
133154
threshold,
134155
});
135156
this.lowerAlarm.addAlarmAction(new StepScalingAlarmAction(this.lowerAction));
@@ -160,6 +181,7 @@ export class StepScalingPolicy extends CoreConstruct {
160181
alarmDescription: 'Upper threshold scaling alarm',
161182
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
162183
evaluationPeriods: props.evaluationPeriods ?? 1,
184+
datapointsToAlarm: props.datapointsToAlarm,
163185
threshold,
164186
});
165187
this.upperAlarm.addAlarmAction(new StepScalingAlarmAction(this.upperAction));

‎packages/@aws-cdk/aws-applicationautoscaling/test/step-scaling-policy.test.ts

+56
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,62 @@ describe('step scaling policy', () => {
227227

228228

229229
});
230+
231+
test('step scaling with evaluation period & data points to alarm configured', () => {
232+
// GIVEN
233+
const stack = new cdk.Stack();
234+
const target = createScalableTarget(stack);
235+
236+
// WHEN
237+
target.scaleOnMetric('Tracking', {
238+
metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }),
239+
scalingSteps: [
240+
{ upper: 0, change: -1 },
241+
{ lower: 100, change: +1 },
242+
{ lower: 500, change: +5 },
243+
],
244+
evaluationPeriods: 10,
245+
datapointsToAlarm: 6,
246+
metricAggregationType: appscaling.MetricAggregationType.MAXIMUM,
247+
});
248+
249+
// THEN
250+
expect(stack).toHaveResourceLike('AWS::ApplicationAutoScaling::ScalingPolicy', {
251+
PolicyType: 'StepScaling',
252+
StepScalingPolicyConfiguration: {
253+
AdjustmentType: 'ChangeInCapacity',
254+
MetricAggregationType: 'Maximum',
255+
},
256+
});
257+
expect(stack).toHaveResource('AWS::CloudWatch::Alarm', {
258+
ComparisonOperator: 'GreaterThanOrEqualToThreshold',
259+
EvaluationPeriods: 10,
260+
DatapointsToAlarm: 6,
261+
ExtendedStatistic: 'p99',
262+
MetricName: 'Metric',
263+
Namespace: 'Test',
264+
Threshold: 100,
265+
});
266+
});
267+
268+
test('step scaling with invalid datapointsToAlarm throws error', () => {
269+
const stack = new cdk.Stack();
270+
const target = createScalableTarget(stack);
271+
272+
expect(() => {
273+
target.scaleOnMetric('Tracking', {
274+
metric: new cloudwatch.Metric({ namespace: 'Test', metricName: 'Metric', statistic: 'p99' }),
275+
scalingSteps: [
276+
{ upper: 0, change: -1 },
277+
{ lower: 100, change: +1 },
278+
{ lower: 500, change: +5 },
279+
],
280+
evaluationPeriods: 10,
281+
datapointsToAlarm: 0,
282+
metricAggregationType: appscaling.MetricAggregationType.MAXIMUM,
283+
});
284+
}).toThrow('datapointsToAlarm cannot be less than 1, got: 0');
285+
});
230286
});
231287

232288
/**

0 commit comments

Comments
 (0)
Please sign in to comment.