Java Cloud Ready — Spring Native für die Überholspur

Dienstag, 5.7.2022

Spring_Native-_Java_der_Cloud

Java und Spring gehören zu den etabliertesten Technologien für Unternehmensanwendungen. In Cloud-Umgebungen stehen sie jedoch oft aufgrund von langen Startzeiten und hohen Ressourcenverbräuchen in der Kritik. 
Mit diesem Artikel möchten wir daher die Wolkendecke rund um Spring Native lichten und Potentiale und Herausforderungen aufdecken.

 

Was ist die Motivation für Spring Native bzw. Java im Cloud-Umfeld?

Cloud Architekturen unterscheidet im Gegensatz zu klassischen Betriebsumgebungen insbesondere, dass Anwendungen robust und effizient mit Neustarts umgehen sollten. Zudem werden Ressourcenverbräuche explizit abgerechnet und bedeuten einen viel direkteren Kostenhebel.
In diesem Kontext ist Java mit seiner Just in Time (JIT) Kompilierung und dem hohen Speicherverbrauch im Vergleich zu Script-Sprachen wie Typescript schlecht aufgestellt. 
Das Spring Native-Projekt ist daher angetreten, hierfür eine Lösung zu finden und dank nativer Kompilierung mittels Ahead of Time (AoT) Kompilierung aus Java nativen, sprich Betriebssystem-spezifischen, aber dafür deutlich effektiveren Maschinen Code zu erzeugen.

 

Potential von Spring Native Einsatz

Das Einsparungspotential von Spring Native ist enorm. Bei unseren eigenen Tests konnten wir Berichte über Einsparungen von 90 % im Vergleich zum ursprünglichen Verbrauch insbesondere am Anfang der Pod-Lebenszeit bestätigen.

Das folgende Diagramm zeigt den Speicherverbrauch von zwei Pods mit einer Java-Anwendung, die einmal klassisch (gelb) und einmal nativ (grün) kompiliert wurde.
Bei der klassischen Variante sieht man anfänglich einen Grund-Speicherverbrauch von fast 200 MB, während bei der nativen Variante nur gute 20 MB verbraucht werden. Über ein paar Stunden hinweg schwingt sich das ganze so ein, dass die klassische Variante nur noch bei knapp 140 MB und die native Variante bei knapp 50 MB Memory Usage liegt. Eine signifikante Einsparung an Speicher-Verbrauch, die sich insbesondere bei kurzlebigen Pods bemerkbar macht.

Speicherverbrauch der beiden Variante visualisiert mit Grafana

 

Was steckt hinter Spring Native?

Das Spring Native-Projekt gehört zum Spring-Ökosystem und hat sich das Ziel gesetzt, die GraalVM für Spring Anwendungen leicht nutzbar zu machen. 
Die GraalVM ist eine alternative JVM aus den Oracle Labs, die auch ein JDK inklusive eines Ahead of Time Compilers (AOT) mitbringt, der bereits zur Compile-Zeit alle Abhängigkeiten auflöst und plattformspezifischen Maschinencode erzeugt.

Die Auflösung zur Compile-Zeit steht Springs Dependency Injection Ansatz zur Laufzeit entgegen. Das Spring Native-Projekt bietet Werkzeuge, um diesen Brückenschlag zu ermöglichen.

 

Wie nutzt man Spring Native im eigenen Projekt?

Die Basis ist recht schnell gelegt: In der POM wird Spring Native als Dependency eingebunden. Zu beachten ist nur, dass die Spring Native Version zu den verwendeten Java und Spring Versionen kompatibel ist.

Mit den Dependencies lassen sich bereits Annotationen hinzufügen - auf die wir später eingehen - und im Hintergrund läuft ab jetzt einige Unterstützung für die GraalVM-Integration.
Wir haben für den schnellen Start im Selbstversuch ein Beispielprojekt auf GitHub bereitgestellt: https://github.com/viadee/spring-native-demo

Interessant wird es nun, wenn es an die Kompilierung selbst geht.

 

Native Kompilierung in der eigenen Entwicklungsumgebung

Für die Kompilierung lassen sich drei Varianten unterscheiden:

  • Nativ auf Linux oder Windows Subsystem for Linux (WSL)
  • Nativ auf Windows
  • Mittels einem von GraalVM bereitgestellten Docker-Image

Wir haben mit den drei Varianten recht unterschiedliche Erfahrungen gemacht. Um den Gewinner direkt vorwegzunehmen: Am unkompliziertesten hat die Kompilierung mit dem bereitgestellten Docker Image funktioniert.

Während die native Kompilierung unter Linux selbst recht gradlinig lief, hat es mit dem Windows Subsystem for Linux in einigen Fällen gehakt. Wovon wir nach unseren eigenen Erfahrungen abraten können, ist die native Kompilierung unter Windows. Zum Zeitpunkt des Schreibens, ist es notwendig unter Windows ein Visual Studio mit seiner Nativen Developer Konsole zu installieren und selbst damit waren wir nicht erfolgreich.

Wie zuvor erwähnt, ist unsere Empfehlung mit der Docker Image Variante zu arbeiten, wenn das die eigene Entwicklungsumgebung zulässt.
Mit einer lokalen Docker Umgebung kann das bereitgestellte Beispielprojekt einfach geklont und mit folgendem Befehl kompiliert werden:

Neben der Kompilierung per Dockerfile lassen sich die Spring-Native-Anwendungen auch mit einem Buildpack von Paketo als Docker-Image zusammenbauen. Hierzu agiert Paketo als Hilfsprogramm, um für bekannte Anwendungstypen (wie Spring-Boot), eine einheitliche Docker-Build-Pipeline zur Verfügung zu stellen. Praktischerweise muss durch den Einsatz von Paketo keine eigene Dockerfile entwickelt und gepflegt werden. Es reicht lediglich Paketo seiner Spring-Native-Anwendung als Maven-Plugin hinzuzufügen:

Entscheidend für den Build als Native-Image ist die Umgebungsvariable BP_NATIVE_IMAGE, die wir dem Plugin mitgeben. Diese Variable teilt Paketo mit, dass wir für den Buildprozess die GraalVM als Basis verwenden wollen.

Dadurch lässt sich das native Docker-Image für unser Beispielprojekt mit dem folgenden Maven-Goal von Spring-Boot erzeugen:

Der Einsatz von Paketo für das Erstellen des nativen Docker-Images ist vom eingesetzten Betriebssystem und zum Teil von der installierten Software abhängig. Wie bereits erwähnt, empfehlen wir das GraalVM Docker-Image zu verwenden, um einen einheitlichen Workflow über alle Betriebssysteme hinweg sicherzustellen und somit Probleme mit Betriebssystem-spezifische Konfiguration zu vermeiden.

Ein zusätzlicher Vorteil neben dem zuverlässigen Setup ist die Nähe zum langfristigen Entwicklungsprozess.

 

Was ändert sich im Entwicklungsprozess?

Das zuvor beschriebene Setup für die lokale Entwicklung relativiert sich recht schnell, wenn man den gesamten Entwicklungsprozess betrachtet. Fakt ist, dass bei der nativen Kompilierung die Einsparung im Betrieb auf Kosten des Build-Prozesses gehen. Aus ein paar Minuten werden dann schnell ein paar Stunden. Dies spiegelt sich auch in unserem Beispiel wieder, wenn man die Builds bei GitHub betrachtet:

Laufzeit der CI/CD Workflows bei klassischer und nativer Kompilierung

Selbst für unser Minimalbeispiel, welches bewusst auf viele Komponenten des Spring-Ökosystems verzichtet, steigt die Build-Time von 1:50 Minuten auf rund 7 Minuten an. Das entspricht einem Anstieg der Build-Time zum Faktor 3.87. Dieser Faktor kann, abhängig vom Einsatz der verwendeten Spring-Teilprojekte, signifikant ansteigen. Die wohl zu überlegende Frage ist nun, wann man im gesamten Entwicklungsprozess eine klassische und wann eine native Kompilierung nutzt.

Die langen Kompilierungszeiten machen die native Entwicklung unbrauchbar für lokale Entwicklungsumgebungen aufgrund der nicht sinnvollen langen Feedbackzyklen. Ebenso ist die native Kompilierung für viele Entwicklungsumgebungen zu Ressourcen-intensiv.
Auf der anderen Seite stellt sich die Frage, wie Build-Automatisierung und - Pipelines organisiert werden sollten. Beispielsweise gibt es immer wieder Code-Stellen, die der Compiler nicht von sich aus findet und unterstützende Code-Annotationen nachträglich zu ergänzen sind. Diese Stellen gilt es noch während der Entwicklung und nicht erst in Produktion zu identifizieren. 

Fazit: Eine native Kompilierung macht lokal in den wenigsten Fällen Sinn und es gilt, viel mehr seine CI/CD Pipelines entsprechend zu planen. Hierbei ist eine ausreichend hohe Testabdeckung notwendig, damit alle Code-Stellen identifiziert werden, die vom AOT Compiler nicht automatisch gefunden wurden. Für diese Fälle bedarf es zudem Feedbackmechanismen, damit sie in der Entwicklung ergänzt werden können.

Die hier beschriebenen Herausforderungen treffen zu Teilen nicht nur auf Spring Native, sondern auch auf andere Frameworks mit ähnlicher Zielsetzung wie Micronaut und Quarkus zu. Letztere haben nur den Vorteil, dass sie die native Kompilierung als Grundkonzept verankert haben, während bei Spring viele Teilprojekte schon viel länger existieren und (noch) nicht explizit dafür vorbereitet wurden.

 

Wann und wie den Einstieg wagen?

Native Kompilierung und der Ahead of Time / AOT Ansatz sind nicht neu. Die GraalVM, als Basis von Spring Native, wird in den Oracle Labs schon weit mehr als ein Jahrzehnt entwickelt. Im Spring Ökosystem gehört Spring Native hingegen zu den etwas jüngeren und insbesondere noch als experimental gekennzeichneten Projekten. Nichtsdestotrotz steckt in der Technologie so viel Potential, dass es sich aus unserer Sicht heute schon lohnt, sich damit auseinanderzusetzen. Insbesondere wenn beispielsweise die verfügbaren Ressourcen im eigenen Cluster knapp werden, Startup-Zeiten der Java Anwendungen nicht zur angestrebten Cloud-Architektur passen oder einfach die Kosten für RAM und CPU sehr stark zu Buche schlagen.

Ein guter, gangbarer Weg ist der Start mit einem Proof of Concept mit einer ersten eigenen Anwendung. Auf diesem Wege kann das betroffene Team noch eng unterstützt werden. Wenn die ersten Erfahrungen mit der neuen Technologie im eigenen Kontext gemacht wurden, lässt sich auch der großflächigere Einsatz besser planen.

Bereit für den Einstieg?

Die viadee bietet ein breites Leistungsspektrum und individuelle Lösungen in den Bereichen Java und Cloud. Wir freuen uns über den Austausch zur eigenen Reise in die Native Welt von Java. Dementsprechend kontaktieren Sie uns gerne mit Fragen, Anregungen und Diskussionsbedarf.

 

 


benjamin-klatt-viadeeDr. Benjamin Klatt ist Integrationsarchitekt und Agile Coach. Seine Schwerpunkte liegen in der Digitalisierung von Produkten und ProzessenIntegrationsarchitekturen sowie agilen Arbeitsweisen und Transformationen.

 

Matthias Kutz_400pxMatthias Kutz ist schwerpunktmäßig im Bereich Software-Analyse und -Entwicklung tätig. Matthias besitzt mehrjährige Erfahrung im Java-Umfeld. Außerdem engagiert er sich in unserem F&E-Bereich für Cloud-Themen rund um Docker und Continuous Delivery sowie für Künstliche Intelligenz.

 


zurück zur Blogübersicht

Diese Beiträge könnten Sie ebenfalls interessieren

Keinen Beitrag verpassen – viadee Blog abonnieren

Jetzt Blog abonnieren!

Kommentare

Dr. Benjamin Klatt

Dr. Benjamin Klatt

Dr. Benjamin Klatt ist IT-Architekt und Agile Coach. Seine Schwerpunkte liegen in der Digitalisierung von Produkten und Prozessen, Cloud- und Software-Lösungen sowie agilen Arbeitsweisen und Transformationen.
Dr. Benjamin Klatt bei LinkedIn   Dr. Benjamin Klatt auf Twitter