Introduction
In the article series Spring Boot 3 application on AWS Lambda we already introduced AWS Serverless Java Container in the part 2. In the part 3 we explained how to implement AWS Lambda function with AWS Serverless Java Container using Java 21 and Spring Boot 3.2 and in the part 4 we measured performance (cold and warm start time) of the Lambda function, including enabling Lambda SnapStart and introducing and applying various priming techniques on top of SnapStart.
As Spring Boot 3.2 used in the example was released more than 1 year ago and the current version at the time of preparing this article (end of December 2024) was already 3.4, I decided to update all my examples and re-measure Lambda performance. Also, the version of Spring Boot 3 Serverless Java Container and many other dependencies used the example application got their version updates. I also decided to do some extended Lambda performance measurements using different Java compilation options and also better visualize the effect of the Lambda SnapStart snapshot tiered cache.
How to write AWS Lambda function with AWS Serverless Java Container using Java 21 managed runtime and Spring Boot 3.4
AWS Serverless Java Container concepts introduced in the part 2 and explanations from the part 3 about how to implement AWS Lambda with AWS Serverless Java Container are still valid.
The sample simple application also remains the same, see the architecture below:
But I updated all the dependencies to the newest version at the time of writing (end of December 2024) and published the source code in the spring-boot-3.4-with-aws-serverless-java-container repository. We use Spring Boot version 3.4.0 and AWS Serverless Java Container for Spring Boot 3 version 2.1.0. Of course, since then other minor or major updates of the libraries have been released, but I assume that no other changes besides the version updates in the pom.xml are required to make the applications work. As far as I remember I also didn't have to change the application code doing such big version updates one year later.
Following needs to be installed in order to build and deploy the sample application:
- Java 21, for example Amazon Corretto 21
- Apache Maven
- AWS CLI
- AWS SAM
In order to build the application, execute mvn clean package
.
In order to build the application, execute sam deploy -g
.
In order to create the product with id equal to 1, execute
curl -m PUT -d '{ "id": 1, "name": "Print 10x13", "price": 0.15 }‘ -H "X-API-Key: a6ZbcDefQW12BN56WEI34 " https://{$API_GATEWAY_URL}/prod/products
In order to retrieve the product with id equal to 1, execute
curl -H "X-API-Key: a6ZbcDefQW12BN56WEI34" https://{$API_GATEWAY_URL}/prod/products/1
What I noticed was that the compiled artifact size to be deployed on AWS Lambda with Spring Boot version 3.4 was bigger than with 3.2 (25.500 vs 22.000 KB). I assume that the artifacts of the updated versions of all dependencies got bigger which contributed to this effect. We know that the bigger the artifact size is, the higher the Lambda function cold start will be. So, let's see.
Measuring cold and warm start time of the AWS Lambda function with AWS Serverless Java Container using Java 21 managed runtime and Spring Boot 3.4
All techniques to measure AWS Lambda performance introduced in the part 4 are also still valid. We will apply them all: SnapStart and also additionally DynamoDB and API Gateway request invocation priming.
The results of the experiment below were as well based on reproducing more than 100 cold and approximately 100.000 warm starts with Lambda function GetProductByIdFunction with 1024 MB memory setting for the duration of 1 hour. The experiments have been performed with the Java Corretto version java:21.v27. For it I used the load test tool hey, but you can use whatever tool you want, like Serverless-artillery or Postman.
I additionally measured Lambda performance using 2 different Java compilation options : tiered compilation, which is the default compilation option in Java 21 and compilation option XX:TieredStopAtLevel=1 for which you need to additionally set JAVA_TOOL_OPTIONS environment variable of the Lambda function to "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" in the template.yaml as showed below:
Globals:
Function:
Runtime: java21
Environment:
Variables:
JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
I'd also like to better visualize the effect of the Lambda SnapStart snapshot tiered cache, showing you the performance measurements for all 100 cold starts, but also for the last 70 cold starts dropping the approximately first 30 slower cold starts. Depending on how often the respective Lambda function is updated and some layers of the cache are invalidated, Lambda function can experience thousands or tens of thousands of cold starts, so that the first longer lasting cold starts are no longer significant. You can read more about the effect Lambda SnapStart snapshot tiered cache in the article and talk by Mike Danilov AWS Lambda Under the Hood. I also investigated this effect for using pure Java 21 on AWS Lambda in my article AWS SnapStart - Part 17 Impact of the snapshot tiered cache on the cold starts with Java 21.
So let's provide the results of the measurement. Abbreviation c stays for the cold start and w is for the warm start.
Cold (c) and warm (w) start time in ms with tiered compilation:
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 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
No SnapStart enabled | 6611.74 | 6725.05 | 6799.39 | 6978.40 | 6999.36 | 7003.54 | 6.45 | 7.21 | 8.33 | 20.01 | 49.23 | 1741.84 |
SnapStart enabled but no priming applied, all | 2141.30 | 2197.67 | 3511.92 | 3543.66 | 3547.20 | 3549.65 | 6.61 | 7.39 | 8.60 | 19.53 | 53.31 | 2742.84 |
SnapStart enabled but no priming applied, last 70 | 2127.51 | 2176.16 | 2236.17 | 2360.17 | 2360.17 | 2360.17 | 6.61 | 7.39 | 8.67 | 19.53 | 49.23 | 1796.23 |
SnapStart enabled with DynamoDB invocation priming, all | 925.73 | 966.39 | 2296.48 | 2331.17 | 2342.84 | 2343.82 | 6.40 | 7.16 | 8.53 | 18.92 | 42.99 | 1565.13 |
SnapStart enabled with DynamoDB invocation priming, last 70 | 910.57 | 942.65 | 973.39 | 1047.29 | 1047.29 | 1047.29 | 6.30 | 7.10 | 8.39 | 18.47 | 41.64 | 474.53 |
SnapStart enabled with API Gateway request invocation priming, all | 668.31 | 710.32 | 1528.95 | 1555.15 | 1558.26 | 1558.57 | 6.35 | 7.16 | 8.53 | 19.22 | 42.99 | 715.99 |
SnapStart enabled with API Gateway request invocation priming, last 70 | 658.01 | 685.51 | 720.31 | 910.7 | 907.7 | 910.7 | 6.35 | 7.16 | 8.53 | 19.22 | 41.97 | 213.41 |
Cold (c) and warm (w) start time in ms with -XX:+TieredCompilation -XX:TieredStopAtLevel=1 compilation option:
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 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
No SnapStart enabled | 6799.39 | 6964.47 | 7083.82 | 7604.78 | 8164.06 | 8165.55 | 6.61 | 7.51 | 8.80 | 20.41 | 62.99 | 1843 |
SnapStart enabled but no priming applied, all | 2191.09 | 2248.78 | 3755.16 | 3789.09 | 3792.88 | 3793.42 | 6.61 | 7.51 | 8.80 | 20.09 | 52.90 | 2883 |
SnapStart enabled but no priming applied, last 70 | 2151 | 2176.16 | 2202.21 | 2260.85 | 2432.66 | 2432.66 | 6.51 | 7.39 | 8.66 | 20.09 | 46.59 | 1906 |
SnapStart enabled with DynamoDB invocation priming, all | 926.66 | 972.20 | 2716.36 | 2738.16 | 2749.13 | 2749.51 | 6.40 | 7.21 | 8.60 | 20.17 | 76.30 | 1812 |
SnapStart enabled with DynamoDB invocation priming, last 70 | 913.04 | 935.13 | 1003.67 | 1453.38 | 1453.38 | 1453.38 | 6.30 | 7.10 | 8.46 | 19.38 | 65.58 | 484 |
SnapStart enabled with API Gateway request invocation priming, all | 678.40 | 737.82 | 1244.44 | 1267.03 | 1302.99 | 1303.27 | 6.30 | 7.04 | 8.33 | 18.92 | 44.03 | 456.75 |
SnapStart enabled with API Gateway request invocation priming, last 70 | 665.72 | 685.75 | 754.55 | 849.83 | 849.83 | 849.83 | 6.25 | 6.99 | 8.20 | 18.62 | 41.64 | 218.7 |
Conclusion
In this article, we updated our sample application to use Spring Boot 3.4, AWS Serverless Java Container Spring Boot 3 version 2.1.0 and other dependencies to their recent version at the time of end of December 2024. We also measured Lambda performance with different methods and with the different Java compilation options. My general impression is that tiered compilation produces better result in terms of the lower cold start times.
Compared to the Lambda performance measurements for Spring Boot 3.2 which we only did for the -XX:+TieredCompilation -XX:TieredStopAtLevel=1 compilation option we observe the higher cold start times for Spring Boot 3.4 (which is the result of the bigger deployment artifact size), but lower warm start times. It's worth investigating which not required dependencies can be additionally excluded in the pom.xml as we've already done for the spring-boot-starter-logging and spring-boot-starter-tomcat dependencies, see the following code snippet from the pom.xml :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
As we use Amazon API Gateway our application doesn't require Apache Tomcat which alone would otherwise contribute 4 to 5 additional MBs to the deployment artifact size. Maybe there are also other dependencies to be excluded, for example JPA implementation as we use DynamoDB NoSQL database. In such a case we can further reduce the Lambda deployment artifact size and therefore the cold start times.
What we also clearly observe is the described effect of the Lambda SnapStart snapshot tiered cache. So don't stop by measuring only the first several cold start times as there are really quite slow but significantly improve with subsequent invocations. The first longer lasting cold starts might not significantly impact the overall performance of your application.
In next part of this series, we'll update our sample Spring Boot application based on AWS Lambda Web Adapter to use the newest versions of dependencies.
If you have read my article(s) and liked its content, please support me by following me on my GitHub account and giving my repos a star.
Top comments (0)