Skip to content

Commit 6a5a4f2

Browse files
authored
feat(dynamodb): custom timeout for replication operation (#13354)
Closes #10249 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4e03667 commit 6a5a4f2

File tree

4 files changed

+62
-20
lines changed

4 files changed

+62
-20
lines changed

packages/@aws-cdk/aws-dynamodb/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ globalTable.autoScaleWriteCapacity({
109109
}).scaleOnUtilization({ targetUtilizationPercent: 75 });
110110
```
111111

112+
When adding a replica region for a large table, you might want to increase the
113+
timeout for the replication operation:
114+
115+
```ts
116+
const globalTable = new dynamodb.Table(this, 'Table', {
117+
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
118+
replicationRegions: ['us-east-1', 'us-east-2', 'us-west-2'],
119+
replicationTimeout: Duration.hours(2), // defaults to Duration.minutes(30)
120+
});
121+
```
122+
112123
## Encryption
113124

114125
All user data stored in Amazon DynamoDB is fully encrypted at rest. When creating a new table, you can choose to encrypt using the following customer master keys (CMK) to encrypt your table:

packages/@aws-cdk/aws-dynamodb/lib/replica-provider.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,26 @@ import { Construct } from 'constructs';
99
// eslint-disable-next-line no-duplicate-imports, import/order
1010
import { Construct as CoreConstruct } from '@aws-cdk/core';
1111

12+
/**
13+
* Properties for a ReplicaProvider
14+
*/
15+
export interface ReplicaProviderProps {
16+
/**
17+
* The timeout for the replication operation.
18+
*
19+
* @default Duration.minutes(30)
20+
*/
21+
readonly timeout?: Duration;
22+
}
23+
1224
export class ReplicaProvider extends NestedStack {
1325
/**
1426
* Creates a stack-singleton resource provider nested stack.
1527
*/
16-
public static getOrCreate(scope: Construct) {
28+
public static getOrCreate(scope: Construct, props: ReplicaProviderProps = {}) {
1729
const stack = Stack.of(scope);
1830
const uid = '@aws-cdk/aws-dynamodb.ReplicaProvider';
19-
return stack.node.tryFindChild(uid) as ReplicaProvider || new ReplicaProvider(stack, uid);
31+
return stack.node.tryFindChild(uid) as ReplicaProvider ?? new ReplicaProvider(stack, uid, props);
2032
}
2133

2234
/**
@@ -34,7 +46,7 @@ export class ReplicaProvider extends NestedStack {
3446
*/
3547
public readonly isCompleteHandler: lambda.Function;
3648

37-
private constructor(scope: Construct, id: string) {
49+
private constructor(scope: Construct, id: string, props: ReplicaProviderProps = {}) {
3850
super(scope as CoreConstruct, id);
3951

4052
const code = lambda.Code.fromAsset(path.join(__dirname, 'replica-handler'));
@@ -80,6 +92,7 @@ export class ReplicaProvider extends NestedStack {
8092
onEventHandler: this.onEventHandler,
8193
isCompleteHandler: this.isCompleteHandler,
8294
queryInterval: Duration.seconds(10),
95+
totalTimeout: props.timeout,
8396
});
8497
}
8598
}

packages/@aws-cdk/aws-dynamodb/lib/table.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
33
import * as iam from '@aws-cdk/aws-iam';
44
import * as kms from '@aws-cdk/aws-kms';
55
import {
6-
Aws, CfnCondition, CfnCustomResource, CustomResource, Fn,
7-
IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
6+
Aws, CfnCondition, CfnCustomResource, CustomResource, Duration,
7+
Fn, IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token,
88
} from '@aws-cdk/core';
99
import { Construct } from 'constructs';
1010
import { DynamoDBMetrics } from './dynamodb-canned-metrics.generated';
@@ -218,6 +218,13 @@ export interface TableOptions {
218218
* @experimental
219219
*/
220220
readonly replicationRegions?: string[];
221+
222+
/**
223+
* The timeout for a table replication operation in a single region.
224+
*
225+
* @default Duration.minutes(30)
226+
*/
227+
readonly replicationTimeout?: Duration;
221228
}
222229

223230
/**
@@ -1135,7 +1142,7 @@ export class Table extends TableBase {
11351142
}
11361143

11371144
if (props.replicationRegions && props.replicationRegions.length > 0) {
1138-
this.createReplicaTables(props.replicationRegions);
1145+
this.createReplicaTables(props.replicationRegions, props.replicationTimeout);
11391146
}
11401147
}
11411148

@@ -1451,14 +1458,14 @@ export class Table extends TableBase {
14511458
*
14521459
* @param regions regions where to create tables
14531460
*/
1454-
private createReplicaTables(regions: string[]) {
1461+
private createReplicaTables(regions: string[], timeout?: Duration) {
14551462
const stack = Stack.of(this);
14561463

14571464
if (!Token.isUnresolved(stack.region) && regions.includes(stack.region)) {
14581465
throw new Error('`replicationRegions` cannot include the region where this stack is deployed.');
14591466
}
14601467

1461-
const provider = ReplicaProvider.getOrCreate(this);
1468+
const provider = ReplicaProvider.getOrCreate(this, { timeout });
14621469

14631470
// Documentation at https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2gt_IAM.html
14641471
// is currently incorrect. AWS Support recommends `dynamodb:*` in both source and destination regions

packages/@aws-cdk/aws-dynamodb/test/dynamodb.test.ts

+23-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling';
44
import * as iam from '@aws-cdk/aws-iam';
55
import * as kms from '@aws-cdk/aws-kms';
66
import { App, Aws, CfnDeletionPolicy, ConstructNode, Duration, PhysicalName, RemovalPolicy, Resource, Stack, Tags } from '@aws-cdk/core';
7+
import * as cr from '@aws-cdk/custom-resources';
78
import { testLegacyBehavior } from 'cdk-build-tools/lib/feature-flag';
89
import { Construct } from 'constructs';
910
import {
@@ -20,6 +21,8 @@ import {
2021
CfnTable,
2122
} from '../lib';
2223

24+
jest.mock('@aws-cdk/custom-resources');
25+
2326
/* eslint-disable quote-props */
2427

2528
// CDK parameters
@@ -2295,12 +2298,6 @@ describe('global', () => {
22952298
// THEN
22962299
expect(stack).toHaveResource('Custom::DynamoDBReplica', {
22972300
Properties: {
2298-
ServiceToken: {
2299-
'Fn::GetAtt': [
2300-
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
2301-
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
2302-
],
2303-
},
23042301
TableName: {
23052302
Ref: 'TableCD117FA1',
23062303
},
@@ -2311,12 +2308,6 @@ describe('global', () => {
23112308

23122309
expect(stack).toHaveResource('Custom::DynamoDBReplica', {
23132310
Properties: {
2314-
ServiceToken: {
2315-
'Fn::GetAtt': [
2316-
'awscdkawsdynamodbReplicaProviderNestedStackawscdkawsdynamodbReplicaProviderNestedStackResource18E3F12D',
2317-
'Outputs.awscdkawsdynamodbReplicaProviderframeworkonEventF9504691Arn',
2318-
],
2319-
},
23202311
TableName: {
23212312
Ref: 'TableCD117FA1',
23222313
},
@@ -2814,6 +2805,26 @@ describe('global', () => {
28142805
// THEN
28152806
expect(SynthUtils.toCloudFormation(stack).Conditions).toBeUndefined();
28162807
});
2808+
2809+
test('can configure timeout', () => {
2810+
// GIVEN
2811+
const stack = new Stack();
2812+
2813+
// WHEN
2814+
new Table(stack, 'Table', {
2815+
partitionKey: {
2816+
name: 'id',
2817+
type: AttributeType.STRING,
2818+
},
2819+
replicationRegions: ['eu-central-1'],
2820+
replicationTimeout: Duration.hours(1),
2821+
});
2822+
2823+
// THEN
2824+
expect(cr.Provider).toHaveBeenCalledWith(expect.anything(), expect.any(String), expect.objectContaining({
2825+
totalTimeout: Duration.hours(1),
2826+
}));
2827+
});
28172828
});
28182829

28192830
test('L1 inside L2 expects removalpolicy to have been set', () => {

0 commit comments

Comments
 (0)