Spring Boot 3 application on AWS Lambda - Part 7 Measuring cold and warm starts with AWS Lambda Web Adapter
Tags: aws, serverless, java, springboot
Published: 2024-06-10 15:01:37

## Introduction

In the [part 5](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-5-introduction-to-aws-lambda-web-adapter-m21) of the series we introduced AWS Lambda Web Adapter tool, and in the [part 6](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-6-develop-application-with-aws-lambda-web-adapter-h88) we explained how to develop Lambda function with AWS Lambda Web Adapter using Java 21 and Spring Boot 3. 

In this article of the series, we'll measure the cold and warm start time including enabling SnapStart on the Lambda function but also applying various priming techniques like priming the DynamoDB invocation and priming the whole web request. We'll use Spring Boot 3.2  [sample application](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/tree/master/spring-boot-3.2-with-lambda-web-adapter) for our measurements, and for all Lambda functions use JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" and give them Lambda 1024 MB memory. Current Spring Boot version 3.3. should work out of the box by switching the version in the POM file.

## Measuring cold starts and warm time with AWS Lambda Web Adapter and using Java 21 and Spring Boot 3

Enabling SnapStart on Lambda function is only a matter of configuration like :

```
SnapStart:
  ApplyOn: PublishedVersions 
```

applied in The Lambda Function Properties or Global Functions section of the SAM template, I'd like to dive deeper to how to use priming on top of it for our use case. I explained the ideas behind priming (in our case of DynamoDB request) in my article [AWS Lambda SnapStart - Part 5 Measuring priming, end to end latency and deployment time](https://dev.to/aws-builders/measuring-java-11-lambda-cold-starts-with-snapstart-part-5-priming-end-to-end-latency-and-deployment-time-jem)

The code for priming of DynamoDB request can be found [here](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/Priming.java).

This class Priming is additionally annotated with 
@Configuration so it will be initialized as well before the initialization of bean, repositories and controllers is completed. 

It implements import org.crac.**Resource** interface of the [CraC project](https://openjdk.org/projects/crac/).
With this invocation

```
Core.getGlobalContext().register(this);
```
Priming class registers itself as CRaC resource. We additionally prime the DynamoDB invocation by implementing **beforeCheckpoint** method from the [CRaC API](https://openjdk.org/projects/crac/).

```
      @Override
      public void beforeCheckpoint(org.crac.Context<? extends Resource> context) throws Exception {
             productDao.getProduct("0");
      }

```
which we'll be invoked during the deployment phase of the Lambda function and before Firecracker microVM snapshot is taken.

Web request invocation priming that we explored with AWS Serverless Java Container in the article [Spring Boot 3 application on AWS Lambda - Part 4 Measuring cold and warm starts with AWS Serverless Java Container](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-4-measuring-cold-and-warm-starts-with-aws-serverless-java-container-mb0) is not applicable to AWS Lambda Web Adapter as the latter doesn't offer the formal API for the request invocation as AWS Serverless Java Container does.


The results of the experiment below were based on reproducing more than 100 cold and approximately 100.000 warm starts with Lambda function with 1024 MB memory setting for the duration of 1 hour. For it I used the load test tool [hey](https://github.com/rakyll/hey), but you can use whatever tool you want, like [Serverless-artillery](https://www.npmjs.com/package/serverless-artillery) or [Postman](https://www.postman.com/). 

I ran all these experiments on our **GetProductByIdWithSpringBoot32WithLambdaWebAdapter** Lambda function with 3 different scenarios with compilation option "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" (client compilation without profiling). For further explanation please read my article about this topic [AWS SnapStart - Part 14 Measuring cold starts and deployment time with Java 21 using different compilation options ](https://dev.to/aws-builders/aws-snapstart-part-14-measuring-cold-and-warm-starts-with-java-21-using-different-compilation-options-el4)


1) No SnapStart enabled 

in template.yaml use the following configuration:

```
Globals:
  Function:
    Handler: run.sh
    CodeUri: target/aws-spring-boot-3.2-lambda-web-adapter-1.0.0-SNAPSHOT.jar
    Runtime: java21
    ....
    #SnapStart:
      #ApplyOn: PublishedVersions          
```

2) SnapStart enabled but no priming applied

in template.yaml use the following configuration:

```
Globals:
  Function:
    Handler: run.sh
    CodeUri: target/aws-spring-boot-3.2-lambda-web-adapter-1.0.0-SNAPSHOT.jar
    Runtime: java21
    ....
    SnapStart:
      ApplyOn: PublishedVersions          
```
If we'd like to measure cold and warm starts, so the priming effect won't take place for the SnapStart enabled Lambda function we have to additionally remove **@Configuration** annotation from the [Priming class](https://github.com/Vadym79/AWSLambdaJavaWithSpringBoot/blob/master/spring-boot-3.2-with-lambda-web-adapter/src/main/java/software/amazonaws/Priming.java) or delete the entire Priming class.

3) SnapStart enabled with DynamoDB invocation priming

in template.yaml use the following configuration:

```
Globals:
  Function:
    Handler: run.sh
    CodeUri: target/aws-spring-boot-3.2-lambda-web-adapter-1.0.0-SNAPSHOT.jar
    Runtime: java21
    ....
    SnapStart:
      ApplyOn: PublishedVersions          
```
and leave the Priming class described above as it is, so the priming effect takes place.

I will refer to those scenarios by their numbers in the tables below, for example scenario number 3) stays for "SnapStart enabled with DynamoDB invocation priming". Abbreviation **c** is for the cold start and **w** is for the warm start.


**Cold (c) and warm (w) start time in ms:**

|Scenario Number| 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 |      
|-----------|----------|-----------|----------|----------|----------|----------|-----------|----------|----------|----------|----------|----------|
|1|4777.94|4918.46|5068.17|5381.41|5392.18|5392.33|8.60|9.77|11.92|25.82|887.92|1061.54|
|2|1575.49|1649.61|2132.75|2264.57|2273.64|2274.45|8.26|9.38|11.09|23.46|1136.71|1161.12|
|3|674.35|709.61|974.14|1114.87|1195.66|1196.79|8.00|8.80|10.16|21.84|317.66|624.33|

## Conclusion

By enabling SnapStart on the Lambda function alone, it reduces the cold start time of the Lambda function significantly. By additionally using DynamoDB invocation priming we are be able to achieve cold starts only slightly higher than cold starts described in my article [AWS SnapStart -Measuring cold and warm starts with Java 21 using different memory settings](https://dev.to/aws-builders/aws-snapstart-part-14-measuring-cold-and-warm-starts-with-java-21-using-different-compilation-options-el4) where we measured cold and warm starts for the pure Lambda function without the usage of any frameworks including 1024MB memory setting like in our scenario.

Comparing the cold and warm start times we measured with AWS Serverless Java Container in the article [Measuring cold and warm starts with AWS Serverless Java Container](https://dev.to/aws-builders/spring-boot-3-application-on-aws-lambda-part-4-measuring-cold-and-warm-starts-with-aws-serverless-java-container-mb0) we observe that AWS Lambda Web Adaptor offers much lower cold start times for all scenarios. Even priming DynamoDB invocation with AWS Lambda Web Adapter offers lower cold start times than the web request invocation priming for the Serverless Java Container. Warm start times are very similar for both approaches being very slightly lower for Serverless Java Container.

In the next part of the series, I'll introduce Spring Cloud Function project and concepts behind it as another alternative to develop and run Spring Boot applications on AWS Lambda.