DEV Community

A B Vijay Kumar
A B Vijay Kumar

Posted on

Java Serverless on Steroids with fn+GraalVM Hands-On

Java Serverless on Steroids with fn+GraalVM Hands-On

Function-as-a-Service or Serverless is the most economical way to run code and use the cloud resources to the minimum. Serverless approach runs the code when a request is received. The code boots up, executes, handles the requests, and shuts down. Thus, utilizing the cloud resources to the optimum. This provides a high, available, scalable architecture, at the most optimum costs. However, Serverless architecture demands a faster boot, quicker execution, and shutdown.

GraalVM native images (ahead of time) is the best runtime. GraalVM native images have a very small footprint, they are fast to boot & they come with embedded VM (Substrate VM).

I had blogged about GraalVM here. Please refer to the following blogs, for better understanding of the architecture of Graal VM and how it builds on top of Java Virtual Machine
*Episode 1: “The Evolution” *— Java JIT Hotspot & C2 compilers (the current episode…scroll down)
*Episode 2: “The Holy Grail” *— GraalVM
In these blogs, I will talk about how GraalVM embraces polyglot, providing interoperability between various programming languages. I will then cover how it extends from Hotspot, and provides faster execution, and smaller footprints with “Ahead-of-time” compilations & other optimisations

fn Project

fn project is a great environment to build serverless applications. fn supports building serverless applications in Go, Java, JavaScript, Python, Ruby, C#. It is a very simple and rapid application development environment that comes with fn daemon & a CLI which provides most of the scaffolding to build serverless applications.

In this blog let's focus on building a simple KG to Pounds converter function in Java. First, we will build a serverless application with Java, and then later build it using GraalVM native image. We will then compare how fast and small GraalVM implementation is.

Prerequisite

1. Starting fn daemon

Start the fn daemon server using fn start

The fn server runs in docker, you can check that by running docker ps The screenshot below shows, what I am able to see in my computer.

2. Generating the fn boilerplate code

Now we can generate the boilerplate code with

fn init --runtime java converterFunc

This creates a folder converterFunc with all the boilerplate code

cd converterFunc

Let’s inspect what is inside that folder. You will see a func.yaml, pom.xml & a src folder

func.yml is the main manifest yaml file that has the key information about the class that implements the function and the entry point. Let's inspect that

schema_version: 20180708
name: converterfunc
version: 0.0.1
runtime: java
build_image: fnproject/fn-java-fdk-build:jdk11-1.0.118
run_image: fnproject/fn-java-fdk:jre11-1.0.118
cmd: com.example.fn.HelloFunction::handleRequest
Enter fullscreen mode Exit fullscreen mode
  • name: The name of the function, we can see the name of the function that we specified in our command line fn init

  • version: Version of this function

  • runtime: Java Virtual machine as the runtime

  • build_image: The docker image that should be used to build the java code, in this case, we see its JDK 11

  • run_image: The docker image that should be used as a runtime. In this case, it is JRE11

  • cmd: This is the entry point, which is the ClassName:MethodName

fn has all the information that it needs in this yaml to build and run the function when it is invoked.

Now let's look at the maven file (pom.xml).

We see the repository from where the fn dependencies are to be pulled

and the dependencies com.fnproject.fn.api, com.fnproject.fn.testing-core, com.fnproject.fn.testing-junit4.

In the src the folder we will find HelloFunction.java, which is the default boilerplate code that is generated by fn.

The code is very straightforward. It has a handleRequest () method, which takes in the String an input and returns String as an output. we can write our function logic in this method. This is the method that fn, calls when we invoke the function.

3. Writing our logic

Let's build our converter application. I am going to deploy it into the path src/main/java/com/abvijay/converter , and the name of my Class is ConverterFunction.java

The code is very straightforward. I am just expecting a kgs value in String, converting that to Double and calcualting pound value and returning that back as a String. ( I did not write a lot of exception handling, to check for edge conditions, to keep it simple).

Now we need to update the func.yaml to point to our new Class

Check line number 7, Which is changed to point to the new class and method.

4. Build & Deploy the serverless container to the local docker

Functions are grouped into applications. an application can have multiple functions. That helps in grouping them and managing them. So we need to create a converter-app

fn create app converter-app

Once the app is created, we can now deploy the app.

fn deploy --app converter-app --local

fn deploy command will build the code using maven, package it as a docker image, and deploy it to the local docker runtime. fn can also be used to deploy to the cloud or k8s cluster directly.

Lets now use docker images command to check if our image is built.

We can also use fn inspect to get all the details about the function, this helps in the discovery of the services.

fn inspect function converter-app converterfunc

5. Running and Testing

Now lets invoke the service, since our function expects a input argument in number, we can pass it using a echo command and pipe the output to fn invoke to invoke our function

echo -n ‘10’ | fn invoke converter-app converterfunc

We can see the result coming from the function. Now let's run the same logic on GraalVM

6. Run on GraalVM, as a native-image

The base image for GraalVM is different, we use fnproject/fn-java-native-init, as the base, and initialize our fn project with that

fn init --init-image fnproject/fn-java-native-init converterfuncongraal

cd cnverterfuncongraal
Enter fullscreen mode Exit fullscreen mode

This fn configuration works differently. It also generates a Dockerfile, with all the necessary docker build commands. This is a multi-stage docker build file. Lets inspect this dockerfile

  • line 17: The image will be built using fnproject/fn-java-fdk-build .

  • line 18: setting the working directory to /function

  • line 19–23: Then the maven environment is configured.

  • line 25–40: Using base image as fnproject/fn-java-native, the GraalVM is configured and fn runtime is compiled. This is a very important step, this is what makes our serverless runtime faster and with smaller footprint.

  • line 43–47: Using busybox:glibc (which is the minimal version of linux+glibc) base image the native images are copied.

  • Line 48: is the function entry point. the func.yml in this way of building the serverless image has no information. fn will use dockerfile to perform the build (along with maven) and deploy the image to the repository

Now we need to change line 48 to point to our class. let's replace that with

CMD [ “com.abvijay.converter.ConverterFunction::handleRequest” ]

Another important configuration file, that we need to change reflection.json under src/main/conf This json file has the manifest information about the class name and the method.

Let's change that to

now let’s create a new app and deploy this app and run and see

fn create app graal-converter-app
fn deploy --app graal-converter-app --local
echo -n '20' | fn invoke graal-converter-app converterfuncongraal
Enter fullscreen mode Exit fullscreen mode

There you go, our code is now running on GraalVM. So what's the big deal. When I ran docker images , I see the size of the Java image as 223 MB and the GraalVM image is just 20MB. That is a 10 times smaller footprint.

When I timed the function calls, the Java function took around 700ms while GraalVM took around 460ms. That is almost 30% faster. For functions with more complex logic, the differences will be much more significant.

Java hotspot might catch up with this number, but that is provided the function runs longer, and the Just in time Compiler kicks in to optimize the code. Since most of the functions are expected to be quick and short running, it does not make sense to compare these JIT benchmarks.

There you go…I hope this was fun…ttyl :-)

References:

Top comments (0)