Micronaut Framework AWS Lambda Deployments
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.
Deployments
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.
Module Architecture
Code is shared between every deployment.
Requirements
Build
Micronaut framework is build-agnostic. You can build applications with Maven or Gradle. However, this project uses with Gradle.
Deployment
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.sh
The 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.
Load Tests
- The application uses Gatling to load test the application. Module
loadtests
contains 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.sh
This is a demanding load test, to change it. Edit TodoSimulation.java
CloudWatch Logs Insights
Using CloudWatch Logs Insights, you can analyse the latency of the requests made to the Lambda functions.
Max cold start with Cloud Watch Log Insights
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
Separate Cold Starts without SnapStart
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
Percentiles with SnapStart
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
CloudWatch Log Insights Java Runtime
CloudWatch Log Insights Java Runtime + SnapStart
CloudWatch Log Insights Custom Runtime
Destroy
To destroy the CDK stack run:
./destroy.sh
Measurements
Runtime | Max Cold Startup ms |
---|---|
Java runtime | 8243 |
Native | 922 |
Java SnapStart | 3625 |
Java SnapStart Priming | 3493 |