Introduction
In the . I took me a while to figure out the minimal information to be passed, as it's a (faked) internal invocation without authorization, header and other metadata required. I came up with the following solution :
final AwsProxyRequest awsProxyRequest = new AwsProxyRequest ();
awsProxyRequest.setHttpMethod("GET");
awsProxyRequest.setPath("/products/0");
awsProxyRequest.setResource("/products/{id}");
awsProxyRequest.setPathParameters(Map.of("id","0"));
final AwsProxyRequestContext awsProxyRequestContext = new AwsProxyRequestContext();
final ApiGatewayRequestIdentity apiGatewayRequestIdentity= new ApiGatewayRequestIdentity();
apiGatewayRequestIdentity.setApiKey("blabla");
awsProxyRequestContext.setIdentity(apiGatewayRequestIdentity);
awsProxyRequest.setRequestContext(awsProxyRequestContext);
We'll also use com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext from the same artefact aws-serverless-java-container-core to mock the com.amazonaws.services.lambda.runtime.Context.
So let's use this in . I then converted the AwsProxyRequest object to the byte array using Jackson
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
return ow.writeValueAsBytes(getAwsProxyRequest());
and then proxied the request during priming like this
@Override
public void beforeCheckpoint(org.crac.Context<? extends Resource> context) throws Exception {
new QuarkusStreamHandler().handleRequest
(new ByteArrayInputStream(convertAwsProxRequestToJsonBytes()), new ByteArrayOutputStream(), new MockLambdaContext());
}
This will proxy the request "/products/0" to the handleRequest method of the . SpringBootLambdaContainerHandler already supports proxying the AwsProxyRequest directly as one of the offered options. So priming looks like this
@Override
public void beforeCheckpoint(org.crac.Context<? extends Resource> context) throws Exception {
handler.proxy(getAwsProxyRequest(), new MockLambdaContext());
}
This will proxy the request "/products/0" to part and the whole (faked) request invocation through the proxy including subsequent DynamoDB getItem invocation (prefix a) described in this article. Please note that this optimization doesn't have any effect on the pure Lambda example without using any framework.
| Framework | d p50 | a p50 | d p90 | a p90 | d p99 | a p99 |
|---|---|---|---|---|---|---|
| Pure Lambda | 352.45 | 352.45 | 401.43 | 401.43 | 433.76 | 433.76 |
| Micronaut | 597.91 | 431.64 | 732.01 | 515.78 | 755.53 | 526.11 |
| Quarkus | 459.24 | 413.48 | 493.33 | 458.42 | 510.32 | 500.21 |
| Spring Boot | 600.66 | 419.47 | 1065.37 | 582.64 | 1173.93 | 622.23 |
We see a very big effect of this optimization especially for Micronaut and Spring Boot frameworks. The cold start times of all 3 frameworks are now really much closer to the pure Lambda ones.
Measuring end to end AWS API Gateway latency
Now it's time to re-measure APIGateway end to end request latencies in case of cold starts from the previous article. We'll use the same prefixes as in the previous table. Please note that this optimization doesn't have any effect on the pure Lambda example without using any framework.
| Framework | d p50 | a p50 | d p90 | a p90 | d p99 | a p99 |
|---|---|---|---|---|---|---|
| Pure Lambda | 877 | 877 | 1090 | 1090 | 1098 | 1098 |
| Micronaut | 1083 | 900 | 1221 | 1247 | 1570 | 1325 |
| Quarkus | 946 | 920 | 1094 | 1049 | 1243 | 1111 |
| Spring Boot | 1068 | 950 | 2021 | 1341 | 2222 | 1689 |
We also observe a very big improvement here especially for Spring Boot, but also for Micronaut and Quarkus frameworks at various percentiles.
Conclusions
With priming of the invocation of the entire request we could achieve further significant reduction of the cold start times using all 3 frameworks Micronaut, Quarkus and Spring Boot for our scenario. The measure cold start times became much closer to the ones of the pure Lambda function. Of course end to end APIGateway request latency also reduced. We required to write additional code for that, but the already existing AwsProxyRequest class made our life a bit easier, as we had to set only small amount of properties to make if work. Maybe adding some additional utilities provided out of the box for this purpose can reduce our amount of work further. Anyway we have to understand the internals of frameworks used and whether there is an optimization potential through the whole invocation chain. This optimization works the same way for all downstream services from AWS or not, that you invoke in your Lambda implementation through abstractions provided by Micronaut, Quarkus and Spring Boot frameworks.
Is this the end state of what we can optimize for the Serverless architectures like API Gateway -> SnapStart enabled Lambda written in Java (optionally using frameworks) (-> DynamoDB)?
Maybe not. I'll have to think about other optimization ideas and try them out. Stay tuned!
SOCIAL SHARE CARD GENERATOR