{"id":16553458,"url":"https://github.com/scalastic/hotspot-vs-native","last_synced_at":"2026-04-08T16:02:34.141Z","repository":{"id":52631312,"uuid":"355651301","full_name":"scalastic/hotspot-vs-native","owner":"scalastic","description":"Performances comparison of Spring boot apps between HotSpot JVM and Native executions","archived":false,"fork":false,"pushed_at":"2021-05-23T15:39:36.000Z","size":7848,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-16T23:06:54.910Z","etag":null,"topics":["benchmarks","docker","docker-containers","graalvm","graalvm-native-image","grafana","kubernetes","microservices","native-executions","prometheus","python","spring-boot","spring-native","spring-webflux"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scalastic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-07T18:52:34.000Z","updated_at":"2023-03-25T12:26:21.000Z","dependencies_parsed_at":"2022-08-21T21:20:55.012Z","dependency_job_id":null,"html_url":"https://github.com/scalastic/hotspot-vs-native","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/scalastic/hotspot-vs-native","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scalastic%2Fhotspot-vs-native","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scalastic%2Fhotspot-vs-native/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scalastic%2Fhotspot-vs-native/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scalastic%2Fhotspot-vs-native/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scalastic","download_url":"https://codeload.github.com/scalastic/hotspot-vs-native/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scalastic%2Fhotspot-vs-native/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31562697,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmarks","docker","docker-containers","graalvm","graalvm-native-image","grafana","kubernetes","microservices","native-executions","prometheus","python","spring-boot","spring-native","spring-webflux"],"created_at":"2024-10-11T19:48:18.499Z","updated_at":"2026-04-08T16:02:34.125Z","avatar_url":"https://github.com/scalastic.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HotSpot vs Native : an effective comparison of performances\n\n## Introduction\nWhen it comes to comparing JVM-HotSpot and GraalVM-native executions, \nit is often hard to decide on application's architecture and technology to test and even what to measure.\n\nRecently I came across an interesting training course about [containers and orchestration](https://github.com/jpetazzo/container.training) \nwritten by Jérôme Petazzoni. He uses a bunch of interacting Python and Ruby apps encapsulated in Docker containers. They act as \na microservices mesh and measuring the number of completed cycles per second provides a good estimation of the \nsystem effectiveness. Being able to play with the number of running containers would be also a good illustration of what \nactually happens.\n\nActively following `Spring Native` developments, I therefore decided to port his demonstration application into Java using\nthe latest development versions of `Spring Boot` and reactive programming `WebFlux`.\n\n---\n\n## The demo\n\n### Objective\n\nThe main goal of this demo is to tweak the microservices' resources configuration and see how it affects the global\napplication's performance.\n\nWhat are our levers for action?\n- First, we could easily play with the **number of containers** running each\n  microservice.\n- Secondly, Java-based microservices are built on two types which can be easily switched: **JVM-based** or **Native**.\n\nSo let's do it.\n\n### Requirements\n\nIn order to implement this solution, we'll need:\n- A **Kubernetes** cluster\n- **Prometheus**, **Grafana**\n- **Metrics** coming from our microservices\n- **Bytecode** and **native** built Java apps\n\nWell it's not a big deal and this already exists:\n\n- ***Spring Boot*** and ***Micrometer*** enable metrics exposure of Java applications\n- Python code is instrumented with ***prometheus_client*** library which also exposed metrics to prometheus\n- I explained and scripted a complete Kubernetes stack installation in a previous article: [Locally install Kubernetes, Prometheus, and Grafana](https://scalastic.io/install-kubernetes/)\n- ***Spring Boot Native*** can build natively or in Bytecode any Java app\n\n\u003e Spring Versions \n\u003e \n\u003e We'll be using the latest development versions of Spring Experimental stacks as it's continuously fix bugs and \n\u003e improve performances. However, you have to keep in mind this is still Beta versions and does not represent a final step:\n\u003e - Spring Boot `2.5.0-RC1`\n\u003e - Spring Native `0.10.0-SNAPSHOT`\n\n## Application Architecture\n\n![Application Architecture](_img/application-architecture.jpg)\n\nThe application is composed of 5 microservices :\n- `worker` the algorithm orchestrator [`Python`] which gets `1` a random number, `2` hash it, and `3` increment a counter in redis database.\n- `rng` the random number generator [`Spring Boot`]\n- `hasher` the hasher processor [`Spring Boot`]\n- `redis` the database recording each complete execution cycle\n\n---\n\n## Build the app\n\nThe goal of these builds is to produce a Docker image for each microservice. For Java-based ones, there will be two images: \none built as ***JVM-based*** image and the other one as ***native*** one.\n\n\u003e Optional\n\u003e \n\u003e I've pulled this stuf into a public registry on Docker Hub so you don't even need to worry about these builds.\n\n### Requirements\n\nHowever, if you wish to **build** the app, you will need to install :\n- [GraalVM 21.1.0 Java 11 based](https://www.graalvm.org/docs/getting-started/#install-graalvm)\n- [GraalVM Native Images](https://www.graalvm.org/docs/getting-started/#native-images)\n- [Docker](https://www.docker.com/products/docker-desktop)\n\n### The easy way\n\nIt should work on linux and macOS based systems - *and on Windows with some small modifications*\n\n\u003e Note\n\u003e \n\u003e It will take time....... 15-20 min depending on your internet connection and processor! That's the price to compile \n\u003e to native code.\n\nTo do so, execute the script at the project root:\n```bash\n./build_docker_images.sh\n```\n\n### The other way\n\n- For a non-java app, just enter:\n``` bash\ndocker build -t \u003capp_docker_tag\u003e ./\u003capp_dir\u003e\n```\n\n- For a Java app and JVM-based image:\n``` bash\ncd \u003capp_dir\u003e\nmvn clean package\ndocker build -t \u003capp_docker_tag\u003e .\n```\n\n- For a Java app and native image:\n``` bash\ncd \u003capp_dir\u003e\nmvn spring-boot:build-image\n```\n\n### Pre-built app \n\nYou can pull pre-built images from Docker Hub by typing:\n```bash\ndocker pull jeanjerome/rng-jvm:1.0.0\ndocker pull jeanjerome/hasher-jvm:1.0.0\ndocker pull jeanjerome/worker-python:1.0.0\ndocker pull jeanjerome/rng-native:1.0.0\ndocker pull jeanjerome/hasher-native:1.0.0\n```\n\n### List local images\n\nTo list your local docker images, enter:\n``` bash\ndocker images\n```\nAt least, you should see these images in your local registry:\n```\nREPOSITORY                TAG        IMAGE ID       CREATED             SIZE\nrng-jvm                   1.0.0      f4bfdacdd2a1   4 minutes ago       242MB\nhasher-jvm                1.0.0      ab3600420eab   11 minutes ago      242MB\nworker-python             1.0.0      e2e76d5f8ad4   38 hours ago        55MB\nhasher-native             1.0.0      629bf3cb8760   41 years ago        82.2MB\nrng-native                1.0.0      68e484d391f3   41 years ago        82.2MB\n```\n\n\u003e Note\n\u003e \n\u003e Native images created time seems inaccurate. It's not, the explanation is here: \n\u003e [Time Travel with Pack](https://medium.com/buildpacks/time-travel-with-pack-e0efd8bf05db)\n\n---\n## Configure Kubernetes\n\nFirst, we need to define the kubernetes configuration of our application and configure Grafana to monitor accurate metrics.\n\n### Kubernetes Stack Architecture\n\nLet's have a look at how to set up these microservices into our kubernetes cluster.\n\nRemember the application architecture :\n- It will be deployed in a dedicated namespace `demo`\n- Monitoring tool are located in the `monitoring` namespace\n\n![Kubernetes Architecture](_img/kubernetes-architecture.jpg)\n\n1. We want to manage the number of ~~containers~~ - pods in this case -  per microservice . We could want to \n   scale up automatically this number depending on metrics. We also would like to change the image of the pod, passing \n   from a JVM image to a native image without the need to restart from scratch... Such Kubernetes resource already \n   exists: [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)\n\n2. We want our microservices to communicate each others in the Kubernetes cluster. That's the job of \n   [Service](https://kubernetes.io/docs/concepts/services-networking/) resource.\n\n3. We'd like to access the web UI from outside the cluster: a Service typed with\n   [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport) resource would be sufficient.\n\n4. The Redis database does not need to be reached from the outside but only from the inside: that's already done by \n   [ClusterIP](https://kubernetes.io/docs/concepts/services-networking/service/) which is the default Service type in \n   Kubernetes.\n5. We also want to monitor the application's metrics on Grafana via Prometheus: [found these good detailed explanations](https://developer.ibm.com/technologies/containers/tutorials/monitoring-kubernetes-prometheus/)\n\nHave a look at the `_kube/k8s-app-jvm.yml` extract showing the Hasher Java microservice resources' configuration:\n\n\u003cdetails\u003e\n\u003csummary\u003e_kube/k8s-app-jvm.yml extract\u003c/summary\u003e\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hasher\n  namespace: demo\n  labels:\n    app: hasher\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: hasher\n  template:\n    metadata:\n      name: hasher\n      labels:\n        app: hasher\n    spec:\n      containers:\n        - image: hasher-jvm:1.0.0\n          imagePullPolicy: IfNotPresent\n          name: hasher\n          ports:\n            - containerPort: 8080\n              name: http-hasher\n              protocol: TCP\n          readinessProbe:\n            failureThreshold: 3\n            httpGet:\n              path: /actuator/health\n              port: 8080\n              scheme: HTTP\n            initialDelaySeconds: 10\n            periodSeconds: 30\n            successThreshold: 1\n            timeoutSeconds: 2\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hasher\n  namespace: demo\n  labels:\n    app: hasher\n  annotations:\n    prometheus.io/scrape: 'true'\n    prometheus.io/scheme: http\n    prometheus.io/path: /actuator/prometheus\n    prometheus.io/port: '8080'\nspec:\n  ports:\n    - port: 8080\n      protocol: TCP\n      targetPort: http-hasher\n  selector:\n    app: hasher\n```\n\n\u003c/details\u003e\n\n## Configure Grafana dashboard\n\n- Connect to your Grafana interface \n  \u003e If you've followed my previous article [Locally install Kubernetes, Prometheus, and Grafana](https://scalastic.io/install-kubernetes/) you can reach Grafana at [http://localhost:3000/](http://localhost:3000/)\n- Import the dashboard from the JSON definition `_grafana/demo-dashboard.json` from this repo\n- Display the dashboard\n\nYou should see an empty dashboard as follows:\n![Empty Demo Grafana dashboard](_img/grafana-demo-empty.png)\n\n\n### Description of the ***Demo Dashboard***\n\n![Description of the Demo Grafana dashboard](_img/grafana-demo-description.png)\n\nThe ***Grafana Demo Dashboard*** is composed of 3 rows (labeled from `A` to `C`), one for each microservice's pods \n(Worker, Random Number Generator -RNG- and Hasher) and monitored metrics (numbered `1` to `4`).\n\n- In cells #1, `number of running pods` and `process speed` (functionally speaking) are represented.\n- In cells #2, `historical process speed` is first monitored in the A row. On B and C, `Request Latency` to the underlying \nmicroservices `RNG` and `Hasher` are displayed.\n- Cells #3 display the `pods' CPU consumption`.\n- Cells #4 monitor the `pods' RAM consumption`.\n\n---\n\n## Start the app\n\nIn this first step, all microservices' replicas are configured with 1 pod, and the Java-based microservices run on JVM.\nAll of this will also be created in a specific `demo` namespace.\n\n- To start app's microservices, apply this configuration to the cluster:\n``` bash\nkubectl apply -f _kube/k8s-app-jvm.yml\n```\n\nYou should see the output:\n```\nnamespace/demo created\ndeployment.apps/hasher created\nservice/hasher created\ndeployment.apps/rng created\nservice/rng created\ndeployment.apps/redis created\nservice/redis created\ndeployment.apps/worker created\nservice/worker created\n```\n\n- Visualize the starting app in Grafana:\n  ![Grafana dashboard starting app](_img/grafana-demo-starting-app.png)\n\n\u003e Results\n\u003e \n\u003e - The speed metric located in the first cell of the first row give us a base measure of our application effectiveness: \n\u003e `3.20` cycles/sec.\n\u003e \n\u003e - Depending on your Kubernetes cluster's resources, you could get another result.\n\n---\n\n## Play with k8s config\n\n### Overview\n\n- Let's see the actual deployment's situation by entering:\n``` bash\nkubectl get deployment -n demo\n```\n\n- Which should return:\n```\nNAME     READY   UP-TO-DATE   AVAILABLE   AGE\nhasher   1/1     1            1           13m\nredis    1/1     1            1           13m\nrng      1/1     1            1           13m\nworker   1/1     1            1           13m\n```\n\n### Increase pods' number\n\n- Scale up `worker` pod to 2: \n\n``` bash\nkubectl scale deployment worker --replicas=2 -n demo\n```\n\nWhich returns:\n```\ndeployment.apps/worker scaled\n```\n\n### Impact on application\n\n- Let's have a look on Grafana dashboard:\n\n![Grafana dashboard 2 workers](_img/grafana-demo-2-workers.png)\n\n\u003e Results\n\u003e \n\u003e You can notice an increase by 2 of the application process.\n\n### Increase pods' number even more\n\n- Let's try increasing to 10 workers:\n\n``` bash\nkubectl scale deployment worker --replicas=10 -n demo\n```\n\n![Grafana dashboard 10 workers](_img/grafana-demo-10-workers.png)\n\n\u003e Results\n\u003e \n\u003e The process speed grows up but does not reach exactly 10 times more: latency of the 2 microservices, rng and hasher, \n\u003e has slightly increases.\n\n-  Let's increase `hasher` and `rng` pods' number: \n\n``` bash\nkubectl scale deployment hasher rng --replicas=4 -n demo\n```\n![Grafana dashboard 4 RNGs \u0026 Hashers](_img/grafana-demo-4-rng-hasher.png)\n\nOr even more:\n``` bash\nkubectl scale deployment hasher rng --replicas=5 -n demo\n```\n\n\u003e Results\n\u003e \n\u003e For this 2 microservices, increasing the pods' number reduces their latency, but its remain a little above their initial values:\n\u003e another factor is influencing the app (?)\n\n### Switch to native built app\n\n- Replace jvm-based images with native ones by updating deployments with rollout:\n```\nkubectl set image deployment/hasher hasher=hasher-native:1.0.0 -n demo\nkubectl set image deployment/rng rng=rng-native:1.0.0 -n demo\n```\n\n- Watch the deployment rollout:\n```\nkubectl rollout status deployment/hasher -n demo\n```\n- And the Grafana dashboard:\n\n![Grafana dashboard native RNGs \u0026 Hashers](_img/grafana_demo_native_rng_hasher.png)\n\n\u003e Results\n\u003e\n\u003e About Latency\n\u003e - No change for the responsiveness of the microservices: sure, the code is too simple to benefit from a native build.\n\u003e \n\u003e About CPU usage\n\u003e - JVM-based CPU usage tends to decrease with time. This is due to the HotSpot's `C2` compiler which produces very\n\u003e optimized native code in the long run.\n\u003e - By contrast, native-based CPU usage is low from the outset.\n\u003e \n\u003e About RAM usage\n\u003e - Surprisingly Native-based apps are using more memory than JVM-based ones: I can't explain it as reduce footprint of\n\u003e native Java applications is one of the benefits claimed by the community.\n\u003e - Is it because of the Spring Native still in Beta version, or a memory leak in the implementation?\n\n---\n\n## Stop all\n\nTo simply stop the app and all its microservices, enter:\n```\nkubectl delete -f _kube/k8s-app-jvm.yml \n```\nwhich will remove all the Kubernetes configuration created previously:\n\n```\nnamespace \"demo\" deleted\ndeployment.apps \"hasher\" deleted\nservice \"hasher\" deleted\ndeployment.apps \"rng\" deleted\nservice \"rng\" deleted\ndeployment.apps \"redis\" deleted\nservice \"redis\" deleted\ndeployment.apps \"worker\" deleted\nservice \"worker\" deleted\n```\n\n--- \n\n## What is missing for a more realistic evaluation\n\n- Kubernetes' configuration should always include resources limit (and also request) that has not been done in this demo.\n- I could have used [Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)\n  (HPA) and even better HPA on custom metrics (see [this post](https://itnext.io/horizontal-pod-autoscale-with-custom-metrics-8cb13e9d475) \n  for more details). I wish I found some Automatic Scaler that regulate all pods in an application to maximize a specific \n  metric but nothing about such a thing... Did you ever hear something like that?\n  \n---\n\n## Based on\n\nHere are some links for further reading:\n\n- Jérôme Patazzoni's container training: \u003chttps://github.com/jpetazzo/container.training\u003e\n- Kubernetes Concepts : \u003chttps://kubernetes.io/docs/concepts/\u003e\n- Monitoring your apps in Kubernetes with Prometheus and Spring Boot: \u003chttps://developer.ibm.com/technologies/containers/tutorials/monitoring-kubernetes-prometheus/\u003e\n- Prometheus Python Client: \u003chttps://github.com/prometheus/client_python\u003e\n- Custom Prometheus Metrics for Apps Running in Kubernetes: \u003chttps://zhimin-wen.medium.com/custom-prometheus-metrics-for-apps-running-in-kubernetes-498d69ada7aa\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscalastic%2Fhotspot-vs-native","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscalastic%2Fhotspot-vs-native","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscalastic%2Fhotspot-vs-native/lists"}