DEV Community

Cover image for Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 3 Introducing Lambda SnapStart
Vadym Kazulkin for AWS Heroes

Posted on

Serverless applications on AWS with Lambda using Java 25, API Gateway and DynamoDB - Part 3 Introducing Lambda SnapStart

Introduction

In part 1, we introduced our sample application. In part 2, we measured the performance (cold and warm start times) of the Lambda function without any optimizations. We observed quite a large cold start time. In this article, we'll introduce AWS Lambda SnapStart as one of the approaches to reducing the cold start times of the Lambda function. We'll also provide the cold and warm start measurements of the sample application when the SnapStart is enabled for the Lambda function.

AWS Lambda SnapStart

As we saw in part 2, without any optimizations, Lambda performance measurements showed quite high values, especially for the cold start times. The article Understanding the Lambda execution environment lifecycle provides a good overview of this topic. Lambda SnapStart is one of the optimization approaches to reduce the cold start times.

Lambda SnapStart can provide a start time of a Lambda function of less than one second. SnapStart simplifies the development of responsive and scalable applications without provisioning resources or implementing complex performance optimizations.

The largest portion of startup latency (often referred to as cold start time) is the time Lambda spends initializing the function, which includes loading the function code, starting the runtime, and initializing the function code. With SnapStart, Lambda initializes our function when we publish a function version. Lambda takes a Firecracker microVM snapshot of the memory and disk state of the initialized execution environment. Then it encrypts the snapshot and intelligently caches it to optimize retrieval latency.

To ensure reliability, Lambda manages multiple copies of each snapshot. Lambda automatically patches snapshots and their copies with the latest runtime and security updates. When we invoke the function version, Lambda restores a new execution environment from the cached snapshot, instead of initializing it from scratch, which improves startup latency. You can find more information about the Lambda SnapStart in the article Reducing Java cold starts on AWS Lambda functions with SnapStart. You can also read about the internals of the SnapStart in the article Under the hood: how AWS Lambda SnapStart optimizes function startup latency. I have published the whole series about Lambda SnapStart for Java applications.

Measurements of cold and warm start times of the Lambda function of the sample application

We'll reuse the sample application from part 1 and do exactly the same performance measurement as we described in part 2. We'll measure the performance of the GetProductByIdJava25WithDynamoDB Lambda function mapped to the GetProductByIdHandler. We will trigger it by invoking curl -H "X-API-Key: a6ZbcDefQW12BN56WEVDDB25" https://{$API_GATEWAY_URL}/prod/products/1.

One important aspect is that we instantiate Jackson ObjectMapper and ProductDao directly in the static initializer block of the GetProductByIdHandler Lambda function:

public class GetProductByIdHandler
        implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ProductDao productDao= new ProductDao();
...
Enter fullscreen mode Exit fullscreen mode

When you create an ObjectMapper for the first time, it initializes a lot of other classes. As a part of this process, it instantiates a lot of singletons. It takes, depending on the hardware, more than a hundred milliseconds. If you create the second ObjectMapper in the same Java process, it takes only 1 millisecond because all the singletons are already there.Β  By moving the ObjectMapper instantiation to the static initializer block of the Lambda function, we decrease the cold start time. The reason for that is that this initialized object becomes a part of the SnapStart snapshot.

The same is true for ProductDao, especially taking into account that we directly create the instance of the DynamoDbClient there:

public class ProductDao {

private static final DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
  .credentialsProvider(DefaultCredentialsProvider.builder().build())
  .region(Region.of(REGION.toLowerCase()))
  .overrideConfiguration(ClientOverrideConfiguration.builder().build())
  .build();
....
Enter fullscreen mode Exit fullscreen mode

This, in turn, loads a lot of classes, which also become a part of the SnapStart snapshot.

Also, please make sure that we have enabled Lambda SnapStart inΒ template.yaml as shown below:

Globals:
  Function:
    Handler: 
    SnapStart:
      ApplyOn: PublishedVersions 
    ....
    Environment:
      Variables:
        JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
Enter fullscreen mode Exit fullscreen mode

Please note that I measured only the performance of the Lambda function. On top of that comes also the latency of the trigger - in our case, the API Gateway REST API.

Please also note the effect of the Lambda SnapStart snapshot tiered cache. This means that in the case of SnapStart activation, we get the largest cold starts during the first measurements. Due to the tiered cache, the subsequent cold starts will have lower values. For more details about the technical implementation of AWS SnapStart and its tiered cache, I refer you to the presentation by Mike Danilov: "AWS Lambda Under the Hood" and already mentioned article Under the hood: how AWS Lambda SnapStart optimizes function startup latency. Therefore, I will present the Lambda performance measurements with SnapStart being activated for all approx. 100 cold start times (labelled as all in the table), but also for the last approx. 70 (labelled as last 70 in the table), so that the effect of the snapshot tiered cache becomes visible to you. Depending on how often the respective Lambda function is updated and thus some layers of the cache are invalidated, a Lambda function can experience thousands or tens of thousands of cold starts during its life cycle, so that the first longer-lasting cold starts no longer carry much weight.

To show the impact of the SnapStart, we'll also present the Lambda performance measurements without SnapStart being activated from part 2.

I did the measurements with java:25.v19 Amazon Corretto version, and the deployed artifact size of this application was 13.796 KB.

Cold (c) and warm (w) start time with -XX:+TieredCompilation -XX:TieredStopAtLevel=1 compilation in ms:

Approach c p50 c p75 c p90 c p99 c p99.9 c max w p50 w p75 w p90 w p99 w p99.9 w max
No SnapStart enabled 3800 3967 4183 4411 4495 4499 5.55 6.15 7.00 12.18 56.37 4000
SnapStart enabled but no priming applied, all 2294 2366 3530 3547 3548 3551 5.68 6.30 7.33 13.43 44.74 2923
SnapStart enabled but no priming applied, last 70 2247 2324 2389 2637 2637 2637 5.68 6.35 7.39 13.65 44.03 2051

Conclusion

In this article of the series, we introduced AWS Lambda SnapStart as one of the approaches to reduce the cold start times of the Lambda function. We observed that by enabling the SnapStart on the Lambda function, the cold start time goes down. It's especially noticeable when looking at the "last 70" measurements with the snapshot tiered cache effect. But still, the cold start remains quite high. In the next article, we'll explore the first Lambda SnapStart priming technique. I call it the database (in our case, DynamoDB) request priming. The goal of applying priming is to preload and preinitialize as much as possible in the SnapStart snapshot during the deployment phase. With that, all those things will already be available directly after the SnapStart snapshot restore.

Please also watch out for another series where I use a relational serverless Amazon Aurora DSQL database and additionally the Hibernate ORM framework instead of DynamoDB to do the same Lambda performance measurements.

If you like my content, please follow me on GitHub and give my repositories a star!

Please also check out my website for more technical content and upcoming public speaking activities.

Top comments (0)