Serverless Native Java Functions using GraalVM and Fn Project

GraalVM: a game changer for Java and Serverless?

Native Java Functions really rocks!

TL;DR;

  • Java Functions compiled into a native executable using GraalVM reduces cold start times and memory footprint by order of magnitude compared to running on JVM.
  • Native Java Functions performs equal to or better than Go functions in terms of execution time and memory used.
  • Native Java Functions executable runs from scratch base image, thus have a similar size compared to Go executable images.

Source code is available at https://github.com/panga/fn-native-java

What's GraalVM?

GraalVM is a High-performance polyglot VM open sourced by Oracle and currently in active development.

It also has the ability to compile JVM languages and a few other languages like JS, Python, Ruby and R into native executable. Some examples are demonstrated in @graalvm blog.

The executable actually runs in a optimized native VM called "SubstrateVM". Although is quite stable nowadays, it still contains some limitations that are currently being worked on and can be released in near future.

What's Fn Project?

The Fn Project is an open-source container-native serverless platform that you can run anywhere — any cloud or on-premise. It’s easy to use, supports every programming language, and is extensible and performant.

It is an evolution of the IronFunctions project from iron.io and is mainly maintained by Oracle. So you can expect enterprise grade solution, like first class support for building and testing.

It basically leverages of the container technology to run and you can get started very quickly, the only prerequisite is Docker installed.

First, you need to install and start Fn Server using the following commands:

curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
fn start

The Fn introduction tutorial explains how to run your first function, while the Java introduction tutorial shows the Java function example.

Creating a Native Java Function using GraalVM

What's a Native Java Function?

I will call any Java code that is compiled Ahead-of-Time (AOT) into a native executable that does not require a JVM to run as Native Java Function.

What problem does it solve?

Java is often blamed as being heavy and not suitable for running as serverless function.

That fame does not come from nothing, it sometimes needs a full JRE to run with slow startup time and high memory consumption compared to other native executables like Go.

Fortunately this isn't true anymore, with new versions of Java you can create modular applications, compile Ahead-of-Time and have new and improved garbage collectors using both OpenJDK and OpenJ9 implementations.

GraalVM is new flavor that delivers a JVM that supports multiple languages and compilation into native executable or shared library.

Steps to create your first Native Java Function using GraalVM

  1. Create a new Java function with fn init --runtime java8 myfunction
  2. Create reflection.json for enabling reflection in the function entrypoint

3. Create Dockerfile using GraalVM CE base image provided my me

4. Change func.yaml to use docker runtime and add build commands:

5. Build and deploy the function

fn deploy --app myapp --local

6. Call the function

curl -d 'Leonardo' http://localhost:8080/r/myapp/myfunction

Note: More advanced use cases using fnproject/fdk-java are also working.

Benchmarking

All the benchmarks are done using my local machine (MBP 2015) with default function templates created using Fn Cli.

The executions were done using the following command:

curl -d 'Leonardo' http://localhost:8080/r/myapp/myfunction

Cold Execution Times

Java Function -> 660 ms
Native Java Function -> 460 ms
Go Function -> 470 ms

Note: Most of the time consumed in cold execution of Native Java Function and Go Function was the container startup, the real execution time is near 0 ms.

Hot Execution Times

Java Function -> 30 ms
Native Java Function -> 30 ms
Go Function -> 30 ms

Container Memory usage of Hot Function

Java Function -> 17.8 MiB
Native Java Function -> 0.9 MiB
Go Function -> 1.2 MiB

Container Image Size

Java Function -> 266 MB
Native Java Function -> 13.6 MB 
Go Function -> 15.1 MB

Conclusion

It is a good time to start trying Java Functions on Serverless solutions!

Update on 2018–06–24

Changed Dockerfile from alpine base image to scratch in order to demonstrate the independence of generated native executable.

Thanks Codrut Stancu!

Like what you read? Give Leonardo Zanivan a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.