GraalVM: build regular Micronaut Kotlin/Java lambdas, run natively
Tags: kotlin, graalvm, aws, lambda
Published: 2019-11-26 20:21:19

Finally! It’s time to forget about those cold starts and scheduled warm-up calls. Simply deploy Java (or Kotlin) natively compiled code to AWS Lambda! Or maybe not yet? The post is an attempt to answer this question with an experiment like:
* deploy natively compiled (by GraalVM [native-image tool](https://www.graalvm.org/docs/reference-manual/native-image/)) Kotlin and Java lambdas to AWS cloud
* have the same setup with Node.js lambda nearby
* bridge incoming calls through AWS API Gateway
* run basic tests to measure cold start timings and see behaviour after warm-up

## Project setup
Project boilerplate has been generated with [Micronaut](https://micronaut.io/) tooling and provisioned to AWS infrastructure using [Serverless](https://serverless.com/) framework. Load testing delivered with [K6](https://k6.io/). Feel free to explore related [step-by-step setup guide](https://github.com/bearmug/serverless-playground/blob/master/doc/aws-lambda.md) to deploy the same setup and play with load tests.

## Load scenarios

### #1 “cold” starts evaluation
* each lambda deployed 10 times (which guarantees to have init run for the first call after deployment)
* the deployment followed by 10 seconds load test. The load test is strictly sequential, all requests issued one after another.
* client-side statistics captured from K6 log output, AWS-side figures taken from [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html) with simplest [queries](https://github.com/bearmug/serverless-playground/blob/master/doc/aws-lambda.md#load-test-main-statistics-query).

### #2 “warm” runs
* each lambda deployed once and called to trigger initial iteration
* deployment followed by 10 iterations, each with the same 10 seconds sequential load test

> *load test scenarios could be found here: [cold-start run](https://github.com/bearmug/serverless-playground/blob/master/config/load-test/aws-deploy-cold-call.sh), [warm run](https://github.com/bearmug/serverless-playground/blob/master/config/load-test/aws-deploy-warm-call.sh)*

## Measurements and their visualization

### Client-side measurements
* “cold” start outliers are clearly visible. Their value reaches **~7.5** sec with no effort
* on a bright side, other min/max/percentile figures distributed quite evenly across tested lambdas. JVM/native max numbers leaning a little towards bigger values
* these slight fluctuations could be caused by the network layer as well. Next AWS-side measurements section might be used for cross-checks.
![client-side latency measurements data](https://thepracticaldev.s3.amazonaws.com/i/ur9hyxw0xoihmdoer5xw.png)
<figcaption>client-side latency measurements data</figcaption>


### AWS-side measurements
* Node.js min numbers **0.84/0.79 ms(!)** are invisible at this log-scale chart
* alas, JVM-based lambdas show “cold” start numbers up to **5 sec**. Add **~2.5 sec** init time on top of this. Predictably sad
* native Kotlin/Java lambdas’ performance looks fairly acceptable. Even the worst iteration kept close to **180 ms**
* it is worth to mention that enterprise GraalVM version features an opportunity to tweak performance with [profile-guided optimizations](https://www.graalvm.org/docs/reference-manual/native-image/#profile-guided-optimizations). At the moment GraalVM Enterprise Edition [licensed](https://www.graalvm.org/docs/faq/) for free testing, evaluation, or for developing non-production applications only. Still, there is a hope to see this tooling within CE version as well…
* init timings for native images (**~330 ms**) are much closer to Node.js ones (~160 ms)
![AWS-side latency measurements data (init time is out of scope)](https://thepracticaldev.s3.amazonaws.com/i/j6v6p4lhwthecoh6r2m3.png)
<figcaption>AWS-side latency measurements data (init time is out of scope)</figcaption>

> *here you may find exact numbers for [client-side](https://github.com/bearmug/serverless-playground/blob/master/doc/aws-lambda.md#client-side-measurements) and [AWS-side](https://github.com/bearmug/serverless-playground/blob/master/doc/aws-lambda.md#aws-side-measurements) runs, including init timings and billing calculations*

## Summary notes
* Java/Kotlin JVM lambdas are still experiencing “cold” start issues. These **~5–7** seconds is just a matter of fact today. Further [manipulations](https://github.com/andthearchitect/aws-lambda-java-runtime) with artefact size and classpath might help with init timings, but…
* GraalVM native lambdas have sustainable performance profile. Init time (**~330ms**) comparable to Node.js reference lambda (**~160ms**) and way better than for JVM (**~2.5 sec**)
* At the same time, GraalVM/Micronaut/Serverless combo instantly leverages from Java/Kotlin existing ecosystem, reach tooling and active community
* Node.js timings are really good and very stable. Median is barely different from 99%-ile
* in fact, Java and Kotlin -based native images have the same size, **14.62 MB**. JVM images sizing is a little different: **38.58 MB** (Java) and **44.37 MB** (Kotlin). And Node.js size is just **297 bytes** :)
* Last but not least, this project is intentionally bent from autogenerated Micronaut template. This short journey has proven two things. #1: a lot of issues may arise during [migration to *.gradle.kts](https://guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/), restructuring multi-module Gradle project and [native-image](https://www.graalvm.org/docs/reference-manual/native-image/) configuration tweaks. #2: either these issues solved multiple times or solvable with reasonable efforts.

> *You may run described deployments and tests using project’s [github repo](https://github.com/bearmug/serverless-playground/blob/master/doc/aws-lambda.md).*