-
Notifications
You must be signed in to change notification settings - Fork 937
Description
Expected Behavior
All DynamoDB queries should take in the low 10s of milliseconds.
Current Behavior
DynamoDB queries are generally in the 10s of milliseconds however the first time a Dynamo function is executed I am seeing response times around 2s, subsequent queries then fall back to the expected levels.
Possible Solution
Unsure, I haven't been able to identify anything from stepping through the code
Steps to Reproduce (for bugs)
I have written the following code to replicate the creation of the DynamoDbClient
and the execution of four put requests. It assumes you have a table named "item" in your AWS account.
The results are below.
import java.util.UUID;
import org.junit.jupiter.api.Test;
import com.google.common.collect.ImmutableMap;
import com.ixxus.cnc.utils.SimpleTimer;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
class DynamoClientCT {
private static final SimpleTimer TIMER = new SimpleTimer();
private static final String CONTEXT_ALL = "CONTEXT_ALL";
private static final String CONTEXT_HTTP_CLIENT_BUILD = "CONTEXT_HTTP_CLIENT_BUILD";
private static final String CONTEXT_CREDENTIALS_PROVIDER_BUILD = "CONTEXT_CREDENTIALS_PROVIDER_BUILD";
private static final String CONTEXT_DYNAMO_CLIENT_BUILD = "CONTEXT_DYNAMO_CLIENT_BUILD";
private static final String CONTEXT_DYNAMO_CLIENT_EXECUTE = "CONTEXT_DYNAMO_CLIENT_EXECUTE_";
@Test
void test() {
TIMER.start(CONTEXT_ALL);
createClientAndExecuteRequest();
TIMER.stop(CONTEXT_ALL);
TIMER.logAllSummaries("DynamoClientCT");
}
private void createClientAndExecuteRequest() {
TIMER.start(CONTEXT_HTTP_CLIENT_BUILD);
final SdkHttpClient httpClient = UrlConnectionHttpClient.builder().build();
TIMER.stop(CONTEXT_HTTP_CLIENT_BUILD);
TIMER.start(CONTEXT_CREDENTIALS_PROVIDER_BUILD);
final DefaultCredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();
TIMER.stop(CONTEXT_CREDENTIALS_PROVIDER_BUILD);
TIMER.start(CONTEXT_DYNAMO_CLIENT_BUILD);
final DynamoDbClientBuilder builder = DynamoDbClient.builder();
builder.httpClient(httpClient);
builder.region(Region.EU_WEST_1);
builder.credentialsProvider(credentialsProvider);
final DynamoDbClient client = builder.build();
TIMER.stop(CONTEXT_DYNAMO_CLIENT_BUILD);
putItem(client, 1);
putItem(client, 2);
putItem(client, 3);
putItem(client, 4);
}
private void putItem(final DynamoDbClient client, final int count) {
TIMER.start(CONTEXT_DYNAMO_CLIENT_EXECUTE + count);
final PutItemRequest request = PutItemRequest.builder()
.tableName("item")
.item(ImmutableMap.of("id", AttributeValue.builder().s(UUID.randomUUID().toString()).build()))
.build();
client.putItem(request);
TIMER.stop(CONTEXT_DYNAMO_CLIENT_EXECUTE + count);
}
}
The following is the code for the SimpleTimer
:
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SimpleTimer {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleTimer.class);
Map<String, List<Long>> times = new TreeMap<String, List<Long>>();
Map<String, Long> currentTime = new HashMap<>();
public void start(final String context) {
currentTime.put(context, System.currentTimeMillis());
}
public void stop(final String context) {
if (!currentTime.containsKey(context)) {
throw new IllegalStateException("Start has not been called for context: " + context);
}
times.computeIfAbsent(context, s -> new ArrayList<Long>())
.add(System.currentTimeMillis() - currentTime.get(context));
}
public DoubleSummaryStatistics getSummaryStatistics(final String context) {
return times.getOrDefault(context, new ArrayList<Long>())
.stream()
.mapToDouble(a -> a)
.summaryStatistics();
}
public void logAllSummaries(final String prefix) {
LOGGER.debug("logAllSummaries: {}");
times.keySet().forEach(ctx -> LOGGER.info("{} - {} - {}", prefix, ctx, getSummaryStatistics(ctx)));
}
}
Results:
2019-07-11 15:14:17.746 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_ALL - DoubleSummaryStatistics{count=1, sum=1633.000000, min=1633.000000, average=1633.000000, max=1633.000000}
2019-07-11 15:14:17.747 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_CREDENTIALS_PROVIDER_BUILD - DoubleSummaryStatistics{count=1, sum=4.000000, min=4.000000, average=4.000000, max=4.000000}
2019-07-11 15:14:17.748 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_DYNAMO_CLIENT_BUILD - DoubleSummaryStatistics{count=1, sum=594.000000, min=594.000000, average=594.000000, max=594.000000}
2019-07-11 15:14:17.748 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_DYNAMO_CLIENT_EXECUTE_1 - DoubleSummaryStatistics{count=1, sum=884.000000, min=884.000000, average=884.000000, max=884.000000}
2019-07-11 15:14:17.748 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_DYNAMO_CLIENT_EXECUTE_2 - DoubleSummaryStatistics{count=1, sum=29.000000, min=29.000000, average=29.000000, max=29.000000}
2019-07-11 15:14:17.749 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_DYNAMO_CLIENT_EXECUTE_3 - DoubleSummaryStatistics{count=1, sum=70.000000, min=70.000000, average=70.000000, max=70.000000}
2019-07-11 15:14:17.749 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_DYNAMO_CLIENT_EXECUTE_4 - DoubleSummaryStatistics{count=1, sum=29.000000, min=29.000000, average=29.000000, max=29.000000}
2019-07-11 15:14:17.750 INFO SimpleTimer:49 - DynamoClientCT - CONTEXT_HTTP_CLIENT_BUILD - DoubleSummaryStatistics{count=1, sum=23.000000, min=23.000000, average=23.000000, max=23.000000}
As you can see the first put takes 884ms then the three subsequent ones take 29, 70, and 29ms making the first request at least an order of magnitude slower. Add in the 600ms for the DynamoDbClient
creation and that's quite a long time.
Context
This code is used in a Lambda function serving a REST API which is one of the canonical use cases for Lambdas. The expectation is that the function should be quick to respond and as shown by the subsequent requests, this is a reasonable expectation. The results above are actually far better than we are seeing when deployed to Lambda - our first Dynamo execution is consistently around 2s +/-0.2s. We are not using a VPC and the Lambdas have 1024MB memory.
Your Environment
The above code was run on my machine to get the provided results but we are deploying virtually identical code to an AWS Lambda with 1024MB memory.
- AWS Java SDK version used: 2.5.48
- JDK version used: 1.8
- Operating System and version: N/A - using AWS Lambda
I have some questions:
- Are there other settings or configuration we could be providing to reduce the time to build the client and execute the first request?
- What causes the first request to be so much slower than subsequent requests?