Virtual Threads
En esta ocasión, nuestros Hunters nos hablan de Virtual Threads, hebras virtuales ligeras, una forma de reducir los consumos.
Una hebra o proceso ligero en computación es básicamente un conjunto mínimo de tareas que puede ser ejecutada por el sistema operativo y se ejecutan en el contexto de un proceso. Dependiendo del número de Cores del CPU, se podrán ejecutar simultáneamente múltiples tareas (paralelismo). Si hay más hebras que Cores de CPU entonces habrá que establecer una planificación (Scheduling) consistente en que las hebras toman un CPU por un tiempo determinado y luego lo suelta para que otra hebra pueda cogerlo, dando la sensación de paralelismo.
La máquina virtual Java (JVM) en sí funciona en base a hebras habiendo una equivalencia de 1 a 1 con las hebras del sistema operativo subyacente.
Ahora bien, en un sistema Web, cada petición de Usuario es atendida por una hebra creada a tal efecto (modelo Thread per request), entonces... ¿Qué pasa cuando 1000 usuarios hacen una invocación al mencionado sistema Web?
Modelo Thread per request
Como su nombre indica, cada petición que llega es atendida por un thread. Cuando se alcanza el número máximo de threads corriendo simultáneamente, el resto de peticiones deben ser encoladas a la espera de algún thread disponible.
Cuando se está programando hay fundamentalmente dos estilos, Síncrono y Asíncrono.
En programación síncrona, una hebra realiza una petición a un servicio externo (Base de datos, otro servicio…) y espera la respuesta sin hacer nada. Por el contrario, en Asíncrono, cuando se hace dicha petición, la hebra no se bloquea y puede seguir haciendo cosas mientras recibe la respuesta.
La programación síncrona es una programación más sencilla ya que las respuestas se tienen en el contexto en el que se hizo la petición. Por el contrario, es más ineficiente, pues hay muchos tiempos de espera a que se resuelvan las llamadas.
En el caso asíncrono es todo lo contrario, resulta más complejo pues hay que programar sabiendo que la respuesta puede llegar en un contexto de ejecución distinto al que se hizo la petición, lo que complica la programación. Sin embargo, dado que las hebras nunca paran, resulta más eficiente.
Virtual Threads
Las hebras virtuales son hebras ligeras pensadas para reducir el esfuerzo de escribir y mantener aplicaciones concurrentes de alto rendimiento. Para más información revisar la JEP 444.
Las hebras virtuales NO son gestionadas por el sistema operativo sino por la propia JVM. Se ha visto antes que cada hebra de la JVM tiene su correspondiente hebra del SO (Platform Thread).
La idea ahora es que estas hebras de la JVM llamadas carrier threads, sean asignadas a las hebras virtuales cuando les llega el momento de ejecutarse, con lo cual se consigue tener multitud de hebras virtuales con pocas hebras portadoras y, por tanto, pocas hebras del sistema operativo, ganando así en eficiencia y mejor gestión de recursos.
Ejecuciones de ejemplo
El escenario que se plantea es la invocación simple a un servicio rest que devuelve un string. Se utiliza como framework base Spring Boot 3.2.3 y el servidor web por defecto: Tomcat.
Para la ejecución de hebras tradicionales no se modificará la configuración del servidor, pero para la ejecución de hebras virtuales, se obligará a Tomcat a utilizar el un Executor que utiliza hebras virtuales para atender peticiones (Executors.newVirtualThreadPerTaskExecutor).
Ejecución con hebras tradicionales
Estadísticas con 100 usuarios concurrentes:
Estadísticas con 1000 usuarios concurrentes.
Recursos con 100 usuarios concurrentes:
Recursos usados con 1000 usuarios concurrentes.
Ejecución con hebras virtuales
Estadísticas con 100 usuarios concurrentes:
Estadísticas con 1000 usuarios concurrentes:
Recursos con 100 usuarios concurrentes:
Recursos usados con 1000 usuarios concurrentes:
Conclusiones
Como puede verse, el uso de hebras virtuales no solo reduce la consumición de recursos, sino que, además, ante el incremento de cargas de trabajo, mantiene constante la consumición de los mismos.
Nótese que el número de hebras (carrier threads) creadas en el escenario de hebras virtuales se ha mantenido más o menos constante (28-30) aun cuando el incremento de la carga ha aumentado un orden de magnitud (de 100 a 1000 usuarios).
En el caso de las hebras tradicionales no ha sido así, viéndose un incremento de casi 100 hebras (125 a 222) cuando se ha incrementado la carga en los mismos términos que en el escenario de hebras virtuales.
En cuanto a las estadísticas, puede observarse que tanto a baja carga (100 usuarios) como a alta (1000), el comportamiento de las hebras virtuales es mucho mejor.
¿Conoces el programa Hunters?
Ser un hunter es aceptar el reto de probar nuevas soluciones que aporten resultados diferenciales. Únete al programa Hunters y forma parte de un grupo transversal con capacidad de generar y transferir conocimiento.
Anticípate a las soluciones digitales que nos harán crecer. Consulta más información sobre Hunters en la web.