GraalVM: ¿Cómo mejorar el rendimiento de los microservicios en 2022?

GraalVM proporciona las capacidades para hacer de Java un lenguaje de primera clase para crear microservicios al mismo nivel que Go-Lang, Rust, NodeJS y otros.

Foto de Caspar Camille Rubin en Unsplash

El lenguaje Java ha sido el líder de lenguajes durante generaciones. Prácticamente cada pieza de software ha sido creada con Java: Servidores Web, Sistemas de Mensajería, Aplicaciones Empresariales, Frameworks de Desarrollo, y así sucesivamente. Esta predominancia se ha mostrado en los índices más importantes como el índice TIOBE, como se muestra a continuación:

Imagen del índice TIOBE de https://www.tiobe.com/tiobe-index/

Pero siempre, Java tiene algunas compensaciones que necesitas hacer. La promesa del código portátil es porque la JVM nos permite ejecutar el mismo código en diferentes sistemas operativos y ecosistemas. Sin embargo, al mismo tiempo, el enfoque del intérprete también proporcionará un poco de sobrecarga en comparación con otras opciones compiladas como C.

Esa sobrecarga nunca fue un problema hasta que entramos en la ruta de los microservicios. Cuando tenemos un enfoque basado en servidores con una sobrecarga de 100–200 MB, no es un gran problema en comparación con todos los beneficios que proporciona. Sin embargo, si transformamos ese servidor en, por ejemplo, cientos de servicios, y cada uno de ellos tiene una sobrecarga de 50 MB, esto comienza a ser algo de lo que preocuparse.

Otra compensación fue el tiempo de inicio, nuevamente la capa de abstracción proporciona un tiempo de inicio más lento, pero en la arquitectura cliente-servidor, eso no era un problema importante si necesitamos unos segundos más para comenzar a atender solicitudes. Sin embargo, hoy en la era de la escalabilidad, esto se vuelve crítico si hablamos de tiempo de inicio basado en segundos en comparación con tiempo de inicio basado en milisegundos porque esto proporciona mejor escalabilidad y un uso más optimizado de la infraestructura.

Entonces, ¿cómo proporcionar todos los beneficios de Java y ofrecer una solución para estas compensaciones que ahora comenzaban a ser un problema? Y GraalVM se convierte en la respuesta a todo esto.

GraalVM se basa en sus propias palabras: “una distribución de JDK de alto rendimiento diseñada para acelerar la ejecución de aplicaciones escritas en Java y otros lenguajes JVM,” que proporciona un proceso de Compilación Anticipada para generar un proceso binario a partir del código Java que elimina la sobrecarga tradicional del proceso de ejecución de la JVM.

En cuanto a su uso en microservicios, este es un enfoque específico que han dado, y la promesa de un inicio alrededor de 50 veces más rápido y un uso de memoria 5 veces menor es simplemente asombrosa. Y es por eso que GraalVM se convierte en la base para frameworks de desarrollo de microservicios de alto nivel en Java como Quarkus de RedHat, Micronaut, o incluso la versión de Spring-Boot potenciada por GraalVM.

Entonces, probablemente te estés preguntando: ¿Cómo puedo empezar a usar esto? Lo primero que necesitamos hacer es ir a la página de lanzamientos de GitHub del proyecto y encontrar la versión para nuestro sistema operativo y seguir las instrucciones proporcionadas aquí:

Cuando tengamos esto instalado, es el momento de comenzar a probarlo, y qué mejor manera de hacerlo que creando un servicio REST/JSON y comparándolo con una solución tradicional potenciada por OpenJDK 11.

Para crear este servicio REST de la manera más simple posible para centrarnos en la diferencia entre ambos modos, usaré el Framework Spark Java, que es un framework mínimo para crear servicios REST.

Compartiré todo el código en este repositorio de GitHub, así que si deseas echar un vistazo, clónalo desde aquí:

El código que vamos a usar es muy simple, solo una línea para crear un servicio REST:

Luego, usaremos un plugin de maven de GraalVM para todos los procesos de compilación. Puedes verificar todas las opciones aquí:

El proceso de compilación lleva un tiempo (alrededor de 1–2 min). Sin embargo, necesitas entender que esto compila todo a un proceso binario porque la única salida que obtendrás de esto es un solo proceso binario (nombrado en mi caso rest-service-test) que tendrá todas las cosas que necesitas para ejecutar tu aplicación.

Y finalmente, tendremos un solo binario que es todo lo que necesitamos para ejecutar nuestra aplicación:

Este binario es excepcional porque no requiere ninguna JVM en tu máquina local, y puede comenzar en unos pocos milisegundos. Y el tamaño total del binario es de 32M en disco y menos de 5MB de RAM.

La salida de esta primera aplicación pequeña es sencilla, como viste, pero creo que puedes captar la idea. Pero veámoslo en acción, lanzaré una pequeña prueba de carga con mi computadora con 16 hilos lanzando solicitudes a este endpoint:

Como puedes ver, esto es simplemente increíble, incluso con la falta de latencia ya que esto es solo activado por la misma máquina, estamos alcanzando con un solo servicio una tasa de TPS en 1 minuto de más de 1400 solicitudes/segundo con un tiempo de respuesta de 2ms para cada una de ellas.

¿Y cómo se compara eso con una aplicación normal basada en JAR con el mismo código exactamente? Por ejemplo, puedes ver en la tabla a continuación:

En resumen, hemos visto cómo usando herramientas como GraalVM podemos hacer que nuestros programas basados en JVM estén listos para nuestro entorno de microservicios evitando los problemas normales relacionados con el alto uso de memoria o el tiempo de inicio pequeño que son críticos cuando estamos adoptando una estrategia completamente nativa de la nube en nuestras empresas o proyectos.

Pero, la verdad debe ser dicha. Esto no siempre es tan simple como mostramos en este ejemplo porque dependiendo de las bibliotecas que estés usando, generar la imagen nativa puede ser mucho más complejo y requerir mucha configuración o simplemente ser imposible. Así que no todo está ya hecho, pero el futuro se ve brillante y lleno de esperanza.

Alexandre Vazquez: