This is a serverless application built in Java with the Micronaut Framework. It consists of an Amazon API Gateway backed by an AWS Lambda function and an Amazon DynamoDB table for storage.
This application compares four deployment scenarios:
- FAT Jar to Java Runtime
- FAT Jar to Java Runtime + SnapStart
- FAT Jar to Java Runtime + SnapStart + Priming
- Native executable built with GraalVM to a custom AWS Lambda runtime.
Code is shared between every deployment.
Micronaut framework is build-agnostic. You can build applications with Maven or Gradle. However, this project uses with Gradle.
Deploy the demo to your AWS account using AWS CDK. Module infra contains the CDK code.
If you have never run CDK in your AWS account, you will have to cdk bootstrap first.
To ease deployment, the project contains a bash script. You can deploy via:
./release.shThe bash script builds the FAT JARs and the native executable with GraalVM, and runs cdk deploy. It uses AWS CloudFormation to deploy the resources to your account.
CDK creates three outputs with the API Gateway endpoint URLs to use in our load tests.
- The application uses Gatling to load test the application. Module
loadtestscontains the Gatling load tests code.
The load test executes a simulation which runs a POST, GET, DELETE scenario with 50 concurrent users for 3 minutes and then ramps up to 100 concurrent users for extra 2 minutes.
After deployment, you can run the load script with:
./load.shThis is a demanding load test, to change it. Edit TodoSimulation.java
Using CloudWatch Logs Insights, you can analyse the latency of the requests made to the Lambda functions.
If you are not using SnapStart run:
filter @type="REPORT"
| fields greatest(@initDuration, 0) + @duration as duration
| max(duration) as max
with SnapStart use:
filter @message like "REPORT"
| filter @message not like "RESTORE_REPORT"
| parse @message /Restore Duration: (?<@restore_duration_ms>[0-9\.]+)/
| parse @message / Duration: (?<@invoke_duration_ms>[0-9\.]+)/
| fields
greatest(@restore_duration_ms, 0) as restore_duration_ms,
greatest(@invoke_duration_ms, 0) as invoke_duration_ms
| fields
restore_duration_ms + invoke_duration_ms as total_invoke_ms
| stat
max(total_invoke_ms) as max
The query separates cold starts from other requests and then gives you p50, p90 and p99 percentiles.
filter @type="REPORT"
| fields greatest(@initDuration, 0) + @duration as duration, ispresent(@initDuration) as coldStart
| stats count(*) as count, pct(duration, 50) as p50, pct(duration, 90) as p90, pct(duration, 99) as p99, max(duration) as max by coldStart
if you are using SnapStart you can use:
filter @message like "REPORT"
| filter @message not like "RESTORE_REPORT"
| parse @message /Restore Duration: (?<@restore_duration_ms>[0-9\.]+)/
| parse @message / Duration: (?<@invoke_duration_ms>[0-9\.]+)/
| fields
greatest(@restore_duration_ms, 0) as restore_duration_ms,
greatest(@invoke_duration_ms, 0) as invoke_duration_ms
| fields
restore_duration_ms + invoke_duration_ms as total_invoke_ms
| stat
pct(total_invoke_ms, 50) as total_invoke_ms_p50,
pct(total_invoke_ms, 99) as total_invoke_ms_p99,
pct(total_invoke_ms, 99.9) as total_invoke_ms_p99.9,
max(total_invoke_ms) as max
To destroy the CDK stack run:
./destroy.sh| Runtime | Max Cold Startup ms |
|---|---|
| Java runtime | 8243 |
| Native | 922 |
| Java SnapStart | 3625 |
| Java SnapStart Priming | 3493 |




