Contenedores de servlets integrados en Spring Boot

La popularidad de las aplicaciones nativas en la nube y microservicios genera un mayor uso de contenedores de servlets integrados.  Spring Boot ofrece una variedad de contenedores muy fáciles de utilizar. En este POST veremos y compararemos los siguientes: Tomcat, Undertow y Jetty.

En esta comparación se medirán distintos parámetros:

  • Tiempos de arranque.

  • El rendimiento y tiempos en distintas prueba.

  • Uso de la CPU.

Finalmente se darán las conclusiones sacadas

 

Hardware utilizado

  • Pc: HP EliteBook 640 G3

  • SO: Windows 10

  • CPU: i5-6300U

  • RAM: 16GB

  • Disco Duro: 500GB(SSD M.2 NVME) & 500GB(HDD)

  • Versión Java: 1.8.0_211

Software utilizado

  • Rendimiento: Jmeter (Para mas información consultar documentación oficial)

  • Uso de la CPU: VisualVM (Para más información consultar la documentación oficial)

 

Tiempos de arranque

En primer lugar en el análisis vamos a comparar los tiempos de arranque con los distintos servidores, para ello vamos a arrancar nuestro servicio 5 veces con cada uno de los servidores y sacaremos el tiempo medio de inicialización para cada uno:

Server

Initialization (s)

Application Started (s)

Tomcat

5,83s

19.4

Jetty

4,56s

19.64

Undertow

4,78s

20,10

Podemos ver como los tiempos son muy parecidos entre ellos, siendo ligeramente más rápido de media el Tomcat y algo mas lento Undertow, pero en tiempos tan parecidos no vemos ninguna ventaja en la elección de uno u otro.

Pruebas realizadas

Para poder sacar conclusiones de cada uno de los servlets, se ha diseñado un servicio que simula distintas circunstancias que podemos encontrarnos en un entorno real en nuestro servicios, y así ver como reacciona cada uno de los servlets en cada una de ellas, a continuación se detalla brevemente en que consiste cada una de estas pruebas simuladas: 

Envío Json

Con esta prueba buscamos simular el comportamiento de los servelts ante una API, exactamente ante un GET, ya que lo que estaremos haciendo en solicitar a nuestro servicio información y este nos la devolverá con formato JSON. Además para ver como reaccionan ante distintos tamaños, se han preparado 3 tipos de json, el pequeño(5KB), el mediano(50KB) y el grande(250KB). 

Recepción Json

Al igual que en la prueba anterior buscamos simular el comportamiento de una API, sin embargo esta vez ante un POST, en el que es el cliente el que envía la información al servicio. Esta información igual que en el anterior ha sido enviada en formato JSON y con los mismos 3 tamaños que en la prueba anterior.

Envío PDF

En esta prueba volvemos a simular una petición de tipo  GET en la que el cliente solicita información al servidor, pero en este caso el lugar de devolver un JSON, se devuelve un PDF. Con esta prueba se busca ver como actúa cada servlet en el envío de fichero binarios. De nuevo se han utilizado 3 tamaños distintos: el pequeño(3KB), el mediano(200KB), y el grande(20MB). Para el grande por su gran tamaño se ha reducido el numero de iteraciones.

Recepción PDF

Muy similar a la prueba anterior pero simulando una petición de tipo POST, en la que es el cliente el que envía el fichero binario al servidor. De igual manera se han utilizado los 3 tamaños de archivo.

Operaciones Alta carga de procesador

En esta prueba se ha simulado que para procesar la petición el servicio requería de una alta carga de cpu, exactamente un calculo de los 10000 primeros números primos. Con esto se puede ver como se comporta cada uno principalmente en la gestión de hilos, aunque veremos que los resultados son muy similares entre los 3

Operaciones con delay

En esta prueba se busca medir exactamente lo mismo que en la anterior, pero en lugar de que las peticiones tarden un tiempo en procesarse por un proceso, se mantiene la petición a la espera de forma manual, de forma que todas las peticiones tarden exactamente lo mismo en ser procesadas.

Pico de peticiones

En esta prueba se busca  ver como reacción cada uno de los servlets ante picos de peticiones, es decir muchas peticiones en un corto periodo de tiempo.

Carga progresiva

Esta prueba es justo lo contrario a la anterior, en lugar de medir picos de peticiones medimos mucha carga pero a lo largo de tiempo.

 

Resumen

A continuación podemos ver una tabla resumen de los resultados, en la que hemos añadido los rendimientos(peticiones/segundo) de las pruebas de envío y descarga de archivos: (más es mejor)

Server

JSON(5KB)

JSON(50KB)

JSON(250KB)

PDF(3KB)

PDF(200KB)

PDF(20MB)

Envío

Recepción

Envío

Recepción

Envío

Recepción

Envío

Recepción

Envío

Recepción

Envío

Recepción

Tomcat

518

502

513

580

276

333

169

580

145

411

10

16

Jetty

510

489

446

396

72

375

518

360

184

302

4

3

Undertow

492

520

603

566

274

351

155

684

123

448

9

31

Por otro lado tenemos los rendimientos del resto de pruebas: (más es mejor)

Server

Operaciones

Pico de peticiones

Carga progresiva

alta carga de CPU

retraso

Tomcat

2

50

469

361

Jetty

2

50

459

400

Undertow

2

25

529

430

La información completa de las pruebas se puede encontrar en el siguiente Excel:

Conclusiones

A continuación, se dará una serie de indicaciones obtenidas a través de las pruebas de rendimiento realizadas a cada servlet, en las que se podrán ver sus puntos débiles y fuertes, y por tanto deducir cuales serían los mejores casos de uso para cada uno de ellos. 

Jetty

Ha demostrado un rendimiento peor que la competencia a la hora de hacer peticiones para recuperar datos (ya sean ficheros u objetos JSON) almacenados en el servicio. En cuanto al guardado de datos, especialmente de ficheros, Jetty destaca sobre todo si los ficheros no son de un tamaño muy elevado (<1MB). Respecto a pruebas de alta ocupación del procesador y pruebas con operaciones con delay con el procesador ocioso, Jetty está a la par que Tomcat con un buen resultado. También ha obtenido un resultado aceptable en pruebas donde se alcanzan picos de peticiones. 

Como resultado podemos determinar que sería conveniente usar Jetty para ocasiones en las que el servicio realice muchas operaciones donde recibe peticiones del cliente con datos a almacenar o procesar, especialmente si estos datos no son muy voluminosos. 

Undertow

Ha demostrado un rendimiento superior a la competencia a la hora de hacer peticiones para recuperar datos (ya sean ficheros u objetos JSON) almacenados en el servicio. En cuanto al guardado de datos, Undertow puede salir rentable si los ficheros son de un tamaño elevado (>1MB), aunque también tiene buen rendimiento recibiendo objetos JSON de cualquier tamaño. Respecto a pruebas de alta ocupación del procesador y pruebas con operaciones con delay con el procesador ocioso, el rendimiento es inferior, siendo superado por Tomcat y Jetty. A la hora de hacer pruebas donde se alcanzan picos de peticiones, Undertow también ha superado a toda su competencia. 

Como resultado podemos determinar que sería conveniente usar Undertow para ocasiones en las que el cliente haga muchas peticiones para obtener datos almacenados previamente en el servicio. También destaca cuando se realizan operaciones de cualquier tipo con archivos de gran tamaño. Además, es el que mejor se desenvuelve en las pruebas donde el tráfico de peticiones es elevado. 

Tomcat 

Entre los tres servlets, ha demostrado ser el más equilibrado. Se maneja bien en la mayoría de las situaciones sin mostrar grandes resultados en ninguna de ellas. Obtiene buenos resultados a la hora de hacer peticiones para recuperar datos (ya sean ficheros u objetos JSON) almacenados en el servicio, pero no tan buenos como Undertow. Se desenvuelve mejor que Jetty cuando es necesario utilizar un volumen de datos grande. Respecto a pruebas de alta ocupación del procesador y pruebas con operaciones con delay con el procesador ocioso, Tomcat obtenía unos resultados similares a Jetty, siendo ambos superiores a Undertow. 

Podríamos decir que el uso de Tomcat queda reservado a aquellas ocasiones en las que el servicio tiene que atender un flujo de operaciones muy variado (tanto almacenaje como recuperación de datos, incluso de gran tamaño).