Skip to content

Commit 0b74403

Browse files
authored
feat(dynamodb): simplify dashboard for PAY_PER_REQUEST tables (#186)
Some metrics do not make sense for on-demand dynamo DB table. Therefore, including a new billing mode parameter and adjusting the dashboard contents for the same. Related: #185 --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 9e517fd commit 0b74403

File tree

5 files changed

+1519
-5
lines changed

5 files changed

+1519
-5
lines changed

API.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12407,7 +12407,8 @@ const dynamoTableMetricFactoryProps: DynamoTableMetricFactoryProps = { ... }
1240712407

1240812408
| **Name** | **Type** | **Description** |
1240912409
| --- | --- | --- |
12410-
| <code><a href="#cdk-monitoring-constructs.DynamoTableMetricFactoryProps.property.table">table</a></code> | <code>aws-cdk-lib.aws_dynamodb.ITable</code> | *No description.* |
12410+
| <code><a href="#cdk-monitoring-constructs.DynamoTableMetricFactoryProps.property.table">table</a></code> | <code>aws-cdk-lib.aws_dynamodb.ITable</code> | table to monitor. |
12411+
| <code><a href="#cdk-monitoring-constructs.DynamoTableMetricFactoryProps.property.billingMode">billingMode</a></code> | <code>aws-cdk-lib.aws_dynamodb.BillingMode</code> | table billing mode. |
1241112412

1241212413
---
1241312414

@@ -12419,6 +12420,21 @@ public readonly table: ITable;
1241912420

1242012421
- *Type:* aws-cdk-lib.aws_dynamodb.ITable
1242112422

12423+
table to monitor.
12424+
12425+
---
12426+
12427+
##### `billingMode`<sup>Optional</sup> <a name="billingMode" id="cdk-monitoring-constructs.DynamoTableMetricFactoryProps.property.billingMode"></a>
12428+
12429+
```typescript
12430+
public readonly billingMode: BillingMode;
12431+
```
12432+
12433+
- *Type:* aws-cdk-lib.aws_dynamodb.BillingMode
12434+
- *Default:* best effort auto-detection or PROVISIONED as a fallback
12435+
12436+
table billing mode.
12437+
1242212438
---
1242312439

1242412440
### DynamoTableMonitoringOptions <a name="DynamoTableMonitoringOptions" id="cdk-monitoring-constructs.DynamoTableMonitoringOptions"></a>
@@ -12711,7 +12727,8 @@ const dynamoTableMonitoringProps: DynamoTableMonitoringProps = { ... }
1271112727

1271212728
| **Name** | **Type** | **Description** |
1271312729
| --- | --- | --- |
12714-
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.table">table</a></code> | <code>aws-cdk-lib.aws_dynamodb.ITable</code> | *No description.* |
12730+
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.table">table</a></code> | <code>aws-cdk-lib.aws_dynamodb.ITable</code> | table to monitor. |
12731+
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.billingMode">billingMode</a></code> | <code>aws-cdk-lib.aws_dynamodb.BillingMode</code> | table billing mode. |
1271512732
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.alarmFriendlyName">alarmFriendlyName</a></code> | <code>string</code> | Plain name, used in naming alarms. |
1271612733
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.humanReadableName">humanReadableName</a></code> | <code>string</code> | Human-readable name is a freeform string, used as a caption or description. |
1271712734
| <code><a href="#cdk-monitoring-constructs.DynamoTableMonitoringProps.property.localAlarmNamePrefixOverride">localAlarmNamePrefixOverride</a></code> | <code>string</code> | If this is defined, the local alarm name prefix used in naming alarms for the construct will be set to this value. |
@@ -12744,6 +12761,21 @@ public readonly table: ITable;
1274412761

1274512762
- *Type:* aws-cdk-lib.aws_dynamodb.ITable
1274612763

12764+
table to monitor.
12765+
12766+
---
12767+
12768+
##### `billingMode`<sup>Optional</sup> <a name="billingMode" id="cdk-monitoring-constructs.DynamoTableMonitoringProps.property.billingMode"></a>
12769+
12770+
```typescript
12771+
public readonly billingMode: BillingMode;
12772+
```
12773+
12774+
- *Type:* aws-cdk-lib.aws_dynamodb.BillingMode
12775+
- *Default:* best effort auto-detection or PROVISIONED as a fallback
12776+
12777+
table billing mode.
12778+
1274712779
---
1274812780

1274912781
##### `alarmFriendlyName`<sup>Optional</sup> <a name="alarmFriendlyName" id="cdk-monitoring-constructs.DynamoTableMonitoringProps.property.alarmFriendlyName"></a>

lib/monitoring/aws-dynamo/DynamoTableMetricFactory.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IMetric } from "aws-cdk-lib/aws-cloudwatch";
2-
import { ITable, Operation } from "aws-cdk-lib/aws-dynamodb";
2+
import { BillingMode, ITable, Operation } from "aws-cdk-lib/aws-dynamodb";
33

44
import { MetricFactory, MetricStatistic } from "../../common";
55

@@ -13,7 +13,16 @@ const WriteThrottleEventsLabel = "Write";
1313
const SystemErrorsLabel = "System Errors";
1414

1515
export interface DynamoTableMetricFactoryProps {
16+
/**
17+
* table to monitor
18+
*/
1619
readonly table: ITable;
20+
/**
21+
* table billing mode
22+
*
23+
* @default best effort auto-detection or PROVISIONED as a fallback
24+
*/
25+
readonly billingMode?: BillingMode;
1726
}
1827

1928
export class DynamoTableMetricFactory {

lib/monitoring/aws-dynamo/DynamoTableMonitoring.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import {
66
IWidget,
77
LegendPosition,
88
} from "aws-cdk-lib/aws-cloudwatch";
9-
import { CfnTable, ITable, Operation } from "aws-cdk-lib/aws-dynamodb";
9+
import {
10+
BillingMode,
11+
CfnTable,
12+
ITable,
13+
Operation,
14+
} from "aws-cdk-lib/aws-dynamodb";
1015

1116
import {
1217
AlarmFactory,
@@ -106,6 +111,7 @@ export interface DynamoTableMonitoringProps
106111
export class DynamoTableMonitoring extends Monitoring {
107112
protected readonly title: string;
108113
protected readonly tableUrl?: string;
114+
protected readonly tableBillingMode: BillingMode;
109115

110116
protected readonly alarmFactory: AlarmFactory;
111117
protected readonly errorAlarmFactory: ErrorAlarmFactory;
@@ -148,6 +154,9 @@ export class DynamoTableMonitoring extends Monitoring {
148154
.createAwsConsoleUrlFactory()
149155
.getDynamoTableUrl(props.table.tableName);
150156

157+
this.tableBillingMode =
158+
props.billingMode ?? this.resolveTableBillingMode(props.table);
159+
151160
this.alarmFactory = this.createAlarmFactory(
152161
namingStrategy.resolveAlarmFriendlyName()
153162
);
@@ -415,6 +424,17 @@ export class DynamoTableMonitoring extends Monitoring {
415424
}
416425

417426
protected createReadCapacityWidget(width: number, height: number) {
427+
if (this.tableBillingMode === BillingMode.PAY_PER_REQUEST) {
428+
// simplified view for on-demand table
429+
return new GraphWidget({
430+
width,
431+
height,
432+
title: "Read Usage",
433+
left: [this.consumedReadUnitsMetric],
434+
leftYAxis: CountAxisFromZero,
435+
leftAnnotations: this.dynamoReadCapacityAnnotations,
436+
});
437+
}
418438
return new GraphWidget({
419439
width,
420440
height,
@@ -429,6 +449,17 @@ export class DynamoTableMonitoring extends Monitoring {
429449
}
430450

431451
protected createWriteCapacityWidget(width: number, height: number) {
452+
if (this.tableBillingMode === BillingMode.PAY_PER_REQUEST) {
453+
// simplified view for on-demand table
454+
return new GraphWidget({
455+
width,
456+
height,
457+
title: "Write Usage",
458+
left: [this.consumedWriteUnitsMetric],
459+
leftYAxis: CountAxisFromZero,
460+
leftAnnotations: this.dynamoWriteCapacityAnnotations,
461+
});
462+
}
432463
return new GraphWidget({
433464
width,
434465
height,
@@ -454,4 +485,14 @@ export class DynamoTableMonitoring extends Monitoring {
454485
// try to take the name (if specified) instead of token
455486
return (dynamoTable.node.defaultChild as CfnTable)?.tableName;
456487
}
488+
489+
private resolveTableBillingMode(dynamoTable: ITable): BillingMode {
490+
const billingMode = (dynamoTable.node.defaultChild as CfnTable)
491+
?.billingMode;
492+
if (billingMode) {
493+
return billingMode as BillingMode;
494+
}
495+
// fallback to default (for backwards compatibility)
496+
return BillingMode.PROVISIONED;
497+
}
457498
}

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

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Duration, Stack } from "aws-cdk-lib";
22
import { Template } from "aws-cdk-lib/assertions";
3-
import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";
3+
import { AttributeType, BillingMode, Table } from "aws-cdk-lib/aws-dynamodb";
44

55
import { AlarmWithAnnotation, DynamoTableMonitoring } from "../../../lib";
66
import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil";
@@ -128,3 +128,128 @@ test("snapshot test: all alarms", () => {
128128
expect(numAlarmsCreated).toStrictEqual(14);
129129
expect(Template.fromStack(stack)).toMatchSnapshot();
130130
});
131+
132+
test("snapshot test: pay-per-request, no alarms", () => {
133+
const stack = new Stack();
134+
135+
const scope = new TestMonitoringScope(stack, "Scope");
136+
137+
const table = new Table(stack, "Table", {
138+
tableName: "DummyTable",
139+
billingMode: BillingMode.PAY_PER_REQUEST,
140+
partitionKey: {
141+
name: "id",
142+
type: AttributeType.STRING,
143+
},
144+
});
145+
146+
const monitoring = new DynamoTableMonitoring(scope, {
147+
table,
148+
});
149+
150+
addMonitoringDashboardsToStack(stack, monitoring);
151+
expect(Template.fromStack(stack)).toMatchSnapshot();
152+
});
153+
154+
test("snapshot test: pay-per-request, all alarms", () => {
155+
const stack = new Stack();
156+
157+
const scope = new TestMonitoringScope(stack, "Scope");
158+
159+
const table = new Table(stack, "Table", {
160+
tableName: "DummyTable",
161+
billingMode: BillingMode.PAY_PER_REQUEST,
162+
partitionKey: {
163+
name: "id",
164+
type: AttributeType.STRING,
165+
},
166+
});
167+
168+
let numAlarmsCreated = 0;
169+
170+
const monitoring = new DynamoTableMonitoring(scope, {
171+
table,
172+
addConsumedWriteCapacityAlarm: {
173+
Warning: {
174+
maxConsumedCapacityUnits: 100,
175+
evaluationPeriods: 6,
176+
},
177+
},
178+
addConsumedReadCapacityAlarm: {
179+
Warning: {
180+
maxConsumedCapacityUnits: 100,
181+
evaluationPeriods: 7,
182+
},
183+
},
184+
addSystemErrorCountAlarm: {
185+
Warning: {
186+
maxErrorCount: 5,
187+
evaluationPeriods: 8,
188+
},
189+
},
190+
addReadThrottledEventsCountAlarm: {
191+
Warning: {
192+
maxThrottledEventsThreshold: 5,
193+
},
194+
},
195+
addWriteThrottledEventsCountAlarm: {
196+
Warning: {
197+
maxThrottledEventsThreshold: 5,
198+
},
199+
},
200+
addAverageSuccessfulGetRecordsLatencyAlarm: {
201+
Warning: {
202+
maxLatency: Duration.millis(500),
203+
},
204+
},
205+
addAverageSuccessfulQueryLatencyAlarm: {
206+
Warning: {
207+
maxLatency: Duration.millis(501),
208+
},
209+
},
210+
addAverageSuccessfulScanLatencyAlarm: {
211+
Warning: {
212+
maxLatency: Duration.millis(502),
213+
},
214+
},
215+
addAverageSuccessfulPutItemLatencyAlarm: {
216+
Warning: {
217+
maxLatency: Duration.millis(503),
218+
},
219+
},
220+
addAverageSuccessfulGetItemLatencyAlarm: {
221+
Warning: {
222+
maxLatency: Duration.millis(504),
223+
},
224+
},
225+
addAverageSuccessfulUpdateItemLatencyAlarm: {
226+
Warning: {
227+
maxLatency: Duration.millis(505),
228+
},
229+
},
230+
addAverageSuccessfulDeleteItemLatencyAlarm: {
231+
Warning: {
232+
maxLatency: Duration.millis(506),
233+
},
234+
},
235+
addAverageSuccessfulBatchGetItemLatencyAlarm: {
236+
Warning: {
237+
maxLatency: Duration.millis(507),
238+
},
239+
},
240+
addAverageSuccessfulBatchWriteItemLatencyAlarm: {
241+
Warning: {
242+
maxLatency: Duration.millis(508),
243+
},
244+
},
245+
useCreatedAlarms: {
246+
consume(alarms: AlarmWithAnnotation[]) {
247+
numAlarmsCreated = alarms.length;
248+
},
249+
},
250+
});
251+
252+
addMonitoringDashboardsToStack(stack, monitoring);
253+
expect(numAlarmsCreated).toStrictEqual(14);
254+
expect(Template.fromStack(stack)).toMatchSnapshot();
255+
});

0 commit comments

Comments
 (0)