Skip to content

Commit e9cb81c

Browse files
authored
feat(dynamo): add GSI throttling alarms (#376)
# Change Info GSI throttles can occur at times when the underlying table is not throttled. To notify GSI throttles, I added throttle alarms, adapting the pattern in `DynamoTableMonitoring`. # Testing Done Besides the package build, I deployed a variation of this change on an AWS stack. # Backwards Compatibility New `DynamoTableGlobalSecondaryIndexMonitoringProps` members are optional, so the change is backward-compatible. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 8999ae6 commit e9cb81c

File tree

4 files changed

+434
-3
lines changed

4 files changed

+434
-3
lines changed

API.md

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/monitoring/aws-dynamo/DynamoTableGlobalSecondaryIndexMonitoring.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
import { GraphWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch";
1+
import {
2+
GraphWidget,
3+
HorizontalAnnotation,
4+
IWidget,
5+
} from "aws-cdk-lib/aws-cloudwatch";
26

37
import {
48
DynamoTableGlobalSecondaryIndexMetricFactory,
59
DynamoTableGlobalSecondaryIndexMetricFactoryProps,
610
} from "./DynamoTableGlobalSecondaryIndexMetricFactory";
711
import {
812
BaseMonitoringProps,
13+
CapacityType,
914
CountAxisFromZero,
1015
DefaultGraphWidgetHeight,
1116
DefaultSummaryWidgetHeight,
17+
DynamoAlarmFactory,
1218
MetricWithAlarmSupport,
1319
Monitoring,
1420
MonitoringScope,
1521
ThirdWidth,
22+
ThrottledEventsThreshold,
1623
} from "../../common";
1724
import {
1825
MonitoringHeaderWidget,
@@ -22,7 +29,14 @@ import {
2229
export interface DynamoTableGlobalSecondaryIndexMonitoringProps
2330
extends DynamoTableGlobalSecondaryIndexMetricFactoryProps,
2431
BaseMonitoringProps {
25-
// no alarms
32+
readonly addReadThrottledEventsCountAlarm?: Record<
33+
string,
34+
ThrottledEventsThreshold
35+
>;
36+
readonly addWriteThrottledEventsCountAlarm?: Record<
37+
string,
38+
ThrottledEventsThreshold
39+
>;
2640
}
2741

2842
export class DynamoTableGlobalSecondaryIndexMonitoring extends Monitoring {
@@ -38,6 +52,9 @@ export class DynamoTableGlobalSecondaryIndexMonitoring extends Monitoring {
3852
protected readonly writeThrottleCountMetric: MetricWithAlarmSupport;
3953
protected readonly indexThrottleCountMetric: MetricWithAlarmSupport;
4054

55+
protected readonly gsiAlarmFactory: DynamoAlarmFactory;
56+
protected readonly throttledEventsAnnotations: HorizontalAnnotation[];
57+
4158
constructor(
4259
scope: MonitoringScope,
4360
props: DynamoTableGlobalSecondaryIndexMonitoringProps
@@ -74,6 +91,36 @@ export class DynamoTableGlobalSecondaryIndexMonitoring extends Monitoring {
7491
metricFactory.metricThrottledWriteRequestCount();
7592
this.indexThrottleCountMetric =
7693
metricFactory.metricThrottledIndexRequestCount();
94+
95+
const alarmFactory = scope.createAlarmFactory(
96+
namingStrategy.resolveAlarmFriendlyName()
97+
);
98+
this.gsiAlarmFactory = new DynamoAlarmFactory(alarmFactory);
99+
this.throttledEventsAnnotations = [];
100+
101+
for (const disambiguator in props.addReadThrottledEventsCountAlarm) {
102+
const alarmProps = props.addReadThrottledEventsCountAlarm[disambiguator];
103+
const createdAlarm = this.gsiAlarmFactory.addThrottledEventsAlarm(
104+
this.readThrottleCountMetric,
105+
CapacityType.READ,
106+
alarmProps,
107+
disambiguator
108+
);
109+
this.throttledEventsAnnotations.push(createdAlarm.annotation);
110+
this.addAlarm(createdAlarm);
111+
}
112+
for (const disambiguator in props.addWriteThrottledEventsCountAlarm) {
113+
const alarmProps = props.addWriteThrottledEventsCountAlarm[disambiguator];
114+
const createdAlarm = this.gsiAlarmFactory.addThrottledEventsAlarm(
115+
this.writeThrottleCountMetric,
116+
CapacityType.WRITE,
117+
alarmProps,
118+
disambiguator
119+
);
120+
this.throttledEventsAnnotations.push(createdAlarm.annotation);
121+
this.addAlarm(createdAlarm);
122+
}
123+
props.useCreatedAlarms?.consume(this.createdAlarms());
77124
}
78125

79126
summaryWidgets(): IWidget[] {
@@ -137,6 +184,7 @@ export class DynamoTableGlobalSecondaryIndexMonitoring extends Monitoring {
137184
this.indexThrottleCountMetric,
138185
],
139186
leftYAxis: CountAxisFromZero,
187+
leftAnnotations: this.throttledEventsAnnotations,
140188
});
141189
}
142190
}

test/monitoring/aws-dynamo/DynamoTableGlobalSecondaryIndexMonitoring.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { Stack } from "aws-cdk-lib";
22
import { Template } from "aws-cdk-lib/assertions";
33
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";
44

5-
import { DynamoTableGlobalSecondaryIndexMonitoring } from "../../../lib";
5+
import {
6+
AlarmWithAnnotation,
7+
DynamoTableGlobalSecondaryIndexMonitoring,
8+
} from "../../../lib";
69
import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil";
710
import { TestMonitoringScope } from "../TestMonitoringScope";
811

@@ -19,11 +22,59 @@ test("snapshot test: no alarms", () => {
1922
},
2023
});
2124

25+
let numAlarmsCreated = 0;
26+
2227
const monitoring = new DynamoTableGlobalSecondaryIndexMonitoring(scope, {
2328
table,
2429
globalSecondaryIndexName: "non-existing-index",
30+
useCreatedAlarms: {
31+
consume(alarms: AlarmWithAnnotation[]) {
32+
numAlarmsCreated = alarms.length;
33+
},
34+
},
35+
});
36+
37+
addMonitoringDashboardsToStack(stack, monitoring);
38+
expect(numAlarmsCreated).toStrictEqual(0);
39+
expect(Template.fromStack(stack)).toMatchSnapshot();
40+
});
41+
42+
test("snapshot test: all alarms", () => {
43+
const stack = new Stack();
44+
45+
const scope = new TestMonitoringScope(stack, "Scope");
46+
47+
const table = new Table(stack, "Table", {
48+
tableName: "DummyTable",
49+
partitionKey: {
50+
name: "id",
51+
type: AttributeType.STRING,
52+
},
53+
});
54+
55+
let numAlarmsCreated = 0;
56+
57+
const monitoring = new DynamoTableGlobalSecondaryIndexMonitoring(scope, {
58+
table,
59+
globalSecondaryIndexName: "non-existing-index",
60+
addReadThrottledEventsCountAlarm: {
61+
Warning: {
62+
maxThrottledEventsThreshold: 5,
63+
},
64+
},
65+
addWriteThrottledEventsCountAlarm: {
66+
Warning: {
67+
maxThrottledEventsThreshold: 5,
68+
},
69+
},
70+
useCreatedAlarms: {
71+
consume(alarms: AlarmWithAnnotation[]) {
72+
numAlarmsCreated = alarms.length;
73+
},
74+
},
2575
});
2676

2777
addMonitoringDashboardsToStack(stack, monitoring);
78+
expect(numAlarmsCreated).toStrictEqual(2);
2879
expect(Template.fromStack(stack)).toMatchSnapshot();
2980
});

0 commit comments

Comments
 (0)