GCP Serverless Function in Java with 15Mb RAM
Tags: java, cloud, http, native
Published: 2023-10-16 14:58:39

The blog is more about experimenting with Java Native build to achieve less RAM use.

Usually, when using Java as a runtime for Google Cloud functions (https://cloud.google.com/functions/docs/concepts/java-runtime) the average RAM footprint is about 140 MB for a simple REST API Jax-RS based endpoint with JSON (text) static response. For example, the same example in Golang (one HTTP GET method) with JSON response is around 30 MB RAM.

To try to achieve less RAM footprint with Java (e.g. to use 128 MB GCP tier), let us try to use:

1. Java Socket as very simple HTTP server
2. GraalVM Native build
3. Docker
4. Google Cloud Run to use custom Docker image as an app
 

## Java code

**Source code**: https://github.com/ahndmal/http-simple-sock-native

We create ServerSocket and accept connections on port 8080:

```java
try (ServerSocket serverSocket = new ServerSocket(8080)) {
                System.out.println(">>> Server started on port 8080");

 while(true) {
    Socket accept = serverSocket.accept();

    System.out.println("> client connected");

    InputStream inputStream = accept.getInputStream();
    OutputStream outputStream = accept.getOutputStream();
   ...
}
```

We create IO streams to get requests and write data to the output (browser) as a static HTTP GET response.

```java
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream));

writer.write("HTTP/1.1 200 OK\n");
writer.write("Content-Type: text/html\r\n\n");
writer.write("""
   <!doctype html><html>
   <head><title>Java</title></head>
   <body>
   <h2>Java Server</h2>
   <div>Test</div>
   </body>
   </html>""");
```
We can just use any HTML file and read its contents for a response instead of this hardcoded text.

## Docker image

We use 3 stages for Docker build:

1. Build Jar file
2. Use GraalVM findepi image to build native image from this Jar
3. Use Debian slim to run the native app


```docker
# maven build
FROM maven:3.8.6-amazoncorretto-19 AS BUILD
COPY . .
RUN mvn clean package

# native build
FROM ghcr.io/graalvm/jdk:java
FROM findepi/graalvm:22.3.1-java19-native AS GRAAL
COPY --from=BUILD target/http-sock-3-0.0.2.jar /http-sock.jar
RUN gu install native-image && native-image -jar http-sock.jar

# run app
FROM debian:12-slim
#FROM debian:stable-slim
COPY --from=GRAAL /http-sock /tmp/http-sock
CMD ["/tmp/http-sock"]
```
Build Docker image locally:

```bash
docker build --tag java-http-simple:0.0.1 .
```
Tag docker image for GCP Artifact registry:

```bash
docker tag java-http-simple:0.0.1 us-east1-docker.pkg.dev/{PROJECT}/{ART_REG}/java-http-simple:0.0.2
```

Deploy image from Artifact Registry to Cloud Run:
```bash
gcloud run deploy <service-name> --image <image_name>
```

## Metrics

Trigger the URL of the deployed Cloud Run service and check the results:

HTTP response:

- 120 byte data
- 160 ms response time

![HTTP response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1yig416xjfmnn3cru6gz.png)

**RAM metrics**:

12% of RAM from 128MB service (~15.3 MB)

![RAM Metrics](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q23xuutfk9hyqtqt7iiv.png)

As we can see, the memory footprint is around 14MB.

Of course this is a silly app with a primitive response, but it is possible to use such an approach to create a simple REST API app with the DB data and JSON parsing with the little use of RAM.

Thank you for reading!