December 1, 2020
Write an AWS Lambda that manually scales a global DynamoDB table
DynamoDB tables can be set to automatically scale based on load. However, this process can take quite a while. We needed to scale up at a precise time to be ready for large batch loads.
According to the auto scaling guide, DynamoDB auto scaling modifies provisioned throughput settings only when the actual workload stays elevated (or depressed) for a sustained period of several minutes. If you have a batch load that will greatly increase load on the system with no ramp-up time, this will cause early slowdowns. If you have sudden, short-duration spikes of activity, consult the burst capacity guide. If you can wait several minutes for ramp-up, auto-scaling should work fine for your use case.
Scale a single (non-global) table
private ClientConfiguration clientConfiguration() {
final ClientConfiguration clientConfiguration = new ClientConfiguration();
// These constants are typically environment variables specific to your environment
// Note: proxy host and port are required in SDK v1
clientConfiguration.setProxyHost(AWS_PROXY_HOST);
clientConfiguration.setProxyPort(Integer.valueOf(AWS_PROXY_PORT));
clientConfiguration.setClientExecutionTimeout(CLIENT_TIMEOUT);
clientConfiguration.setProtocol(Protocol.HTTP);
return clientConfiguration;
}
private AmazonDynamoDB getClient() {
// SDK v1 uses the AmazonDynamoDBClientBuilder (v2 does not)
return AmazonDynamoDBClientBuilder.standard()
.withClientConfiguration(clientConfiguration())
.withRegion(Regions.valueOf(REGION))
.build();
}
// The RCU/WCU can be set directly in this case
ProvisionedThroughput table_throughput = new ProvisionedThroughput(
read_capacity, write_capacity);
try {
// Update the DynamoDB table with the ProvisionedThroughput
getClient().updateTable(table_name, table_throughput);
} catch (AmazonServiceException e) {
System.err.println(e.getErrorMessage());
System.exit(1);
}
Scale a global table
private DynamoDbClient getClient() {
// These constants are typically environment variables specific to your environment
ApacheHttpClient.Builder httpClientBuilder = ApacheHttpClient.builder()
.connectionTimeout(CONNECTION_TIMEOUT)
.socketTimeout(SOCKET_TIMEOUT);
RetryPolicy.Builder retryBuilder = RetryPolicy.builder()
.numRetries(RETRY_LIMIT);
ClientOverrideConfiguration.Builder clientOverrideBuilder =
ClientOverrideConfiguration.builder()
.apiCallAttemptTimeout(API_CALL_ATTEMPT_TIMEOUT)
.apiCallTimeout(API_CALL_TIMEOUT)
.retryPolicy(retryBuilder.build());
// New DynamoDbClient.builder() for SDK v2
return DynamoDbClient.builder()
.httpClientBuilder(httpClientBuilder)
.overrideConfiguration(clientOverrideBuilder.build())
.build();
}
// A series of updates are required to change the auto scaling settings
AutoScalingTargetTrackingScalingPolicyConfigurationUpdate policyConfigurationUpdate =
AutoScalingTargetTrackingScalingPolicyConfigurationUpdate.builder()
.targetValue(SCALING_POLICY_TARGET_VALUE)
.build();
AutoScalingPolicyUpdate policyUpdate = AutoScalingPolicyUpdate.builder()
.targetTrackingScalingPolicyConfiguration(policyConfigurationUpdate)
.build();
// The maximum auto scale capacity is not really used here and can be set to an arbitrarily high number
AutoScalingSettingsUpdate.Builder updateBuilder = AutoScalingSettingsUpdate.builder()
.maximumUnits(MAX_AUTOSCALE_CAPACITY)
.scalingPolicyUpdate(policyUpdate);
// The minimum units are changed to force a manual scale up/down
AutoScalingSettingsUpdate rcuUpdate = updateBuilder.minimumUnits(HIGH_READ_CAPACITY_UNITS).build();
AutoScalingSettingsUpdate wcuUpdate = updateBuilder.minimumUnits(HIGH_WRITE_CAPACITY_UNITS).build();
// The read capacity units must be updated for each replica
ReplicaAutoScalingUpdate eastReplicaUpdate = ReplicaAutoScalingUpdate.builder()
.regionName("us-east-1")
.replicaProvisionedReadCapacityAutoScalingUpdate(rcuUpdate)
.build();
ReplicaAutoScalingUpdate westReplicaUpdate = ReplicaAutoScalingUpdate.builder()
.regionName("us-west-2")
.replicaProvisionedReadCapacityAutoScalingUpdate(rcuUpdate)
.build();
// The write capacity units impact all replicas
UpdateTableReplicaAutoScalingRequest autoScalingRequest = UpdateTableReplicaAutoScalingRequest.builder()
.tableName(TABLE_NAME)
.replicaUpdates(eastReplicaUpdate, westReplicaUpdate)
.provisionedWriteCapacityAutoScalingUpdate(wcuUpdate)
.build();
try {
getClient().updateTableReplicaAutoScaling(autoScalingRequest);
} catch (AmazonServiceException e) {
System.err.println(e.getErrorMessage());
System.exit(1);
}
The code above worked great until the Lambda was deployed in us-west-2 for redundancy. The SDK calls from us-west-2 were timing out.
getTable(TABLE_NAME)
from the SDK v1 before the call to updateTableReplicaAutoScaling
These issues may have been specific to my particular environment. If you experience similar issues, this should help you resolve them. If you have found a solution to the us-west-2 problem, please reach out!