CONSEJOS SOBRE PRUEBAS DE RENDIMIENTO Y OPTIMIZACIÓN

VERSION PARA IMPRIMIR
(Versión original en español en
art4-1.html
Enlace a la versión original en inglés)

Floyd Marinescu
Arquitecto Jefe
TheServerSide.com

Traducido con permiso de TheServerSide.com por
Dr. Vicent-Ramon Palasí Lallana.
Gerente General de Aurum Solutions.
http://www.aurumsol.com
Septiembre 2003

El propósito de este documento es explicar qué se debe hacer para llevar a cabo pruebas de escalabilidad, pruebas de rendimiento y optimización en un entorno J2EE (Java 2 Enterprise Edition).

1. DEFINICIONES

Tiempo de respuesta (Response time)– El tiempo que pasa entre la petición inicial y la descarga completa de la respuesta (es decir, el desplegado por entero de la página web).

Carga (Load) - Una medida del uso del sistema. Se dice que un servidor tiene “carga elevada” cuando la aplicación que soporta experimenta un fuerte tráfico.

Escalabilidad (Scalability) – Una aplicación escalable tiene un tiempo de respuesta que aumenta linealmente cuando aumenta la carga. Dicha aplicación es capaz de procesar cada vez más volumen si se añaden recursos adicionales de hardware de forma lineal (es decir, no exponencial).

Herramientas de automatización de pruebas (Automation testing tools) – Herramientas (Silk de Segue Software, WebLoad, etc.) que se usan para simular un usuario haciendo peticiones de páginas o ejecutando flujo de trabajo preprogramado en un sitio.

Herramientas de pruebas de carga (Load testing tools) – La mayoría de herramientas de automatización de pruebas (por ejemplo, WebLoad) pueden usarse como software de pruebas de carga. Estas herramientas simulan cualquier cantidad de usuarios usando un sitio y proveen datos importantes como, por ejemplo, tiempos medios de respuesta.

Perfilador (Profiler) - Un perfilador es un programa que examina una aplicación mientras ésta se ejecuta. Provee útil información de ejecución como el tiempo invertido en determinados bloques de código, el grado de utilización de la memoria y del heap, el número de instancias de cada clase que hay en memoria, etc.
 

2. UN PROCESO PARA LAS PRUEBAS DE RENDIMIENTO

  1. Pruebas funcionales. La mayoría de aplicaciones comienzan el proceso de prueba completando, antes que nada, las pruebas funcionales. Es decir, asegurándose que funcionan todos los casos de uso y flujo de trabajo de la aplicación.

  2. Pruebas de carga y escalabilidad. El proceso de probar la carga y escalabilidad tiene dos aspectos:

    1. Probar el tiempo de respuesta cuando se aumenta el tamaño de la base de datos.
    2. Probar el tiempo de respuesta cuando se aumenta el número de usuarios concurrentes.

  3. Interpretación de los resultados. Después de medir el tiempo de respuesta con diferentes cargas y tamaños de la base de datos, se pueden hacer interpretaciones basándose en el tiempo medio de respuesta obtenido en las pruebas y el uso de recursos del servidor durante las mismas.

  4. Optimización. Después de identificar anomalías en el paso anterior, se interpretan los resultados y se localiza el problema.
     

3. PRUEBAS DE CARGA Y ESCALABILIDAD.

El propósito de las pruebas de carga y escalabilidad es asegurar que la aplicación tendrá un buen tiempo de respuesta durante los picos de uso. También se puede evaluar cómo la aplicación se comportará a lo largo del tiempo (conforme el sitio Web contenga cada vez más información en la base de datos). Para comenzar a probar, escriba algunos guiones de prueba que llenen la base de datos con una cantidad promedio de datos. Ejecute las pruebas de rendimiento y mida el tiempo de respuesta. A continuación, llene la base de datos con una cantidad extremada de datos (3 o 4 veces más datos de los que se prevee que haya en 3 años). Ejecute de nuevo las pruebas de rendimiento. Si los tiempos de respuesta son significativamente mayores para la segunda prueba, entonces algo falla.

Para ejecutar las pruebas de rendimiento, deberá simular el uso del servidor con diferentes cargas. Como regla general, yo simulo carga baja (de uno a cinco usuarios concurrentes), carga media (de 10 a 50 usuarios concurrentes) , carga alta (100 usuarios concurrentes) y carga extrema (1000 o más usuarios concurrentes). Nótese que esos números son arbitrarios y dependen de las necesidades del negocio. Además, simular 10 usuarios concurrentes con software de pruebas de carga no es representativo de 10 personas, ya que cada “robot” en la prueba de carga puede esperar sólo milisegundos antes de acceder de nuevo al servidor. Por lo tanto, usar un probador de carga para simular 10 usuarios es probablemente representativo de los comportamientos de navegación en el web de 30 o 40 personas.

Una vez ha probado estos tres niveles de carga, puede comparar tiempos medios de respuesta para ver si su sistema es escalable, es decir, si el tiempo de respuesta aumenta linealmente.
 

4. INTERPRETANDO LOS RESULTADOS.

La parte divertida de este proceso es interpretar los resultados de las pruebas de carga. Examinemos algunas de las diferentes posibilidades:

  1. El tiempo de respuesta aumenta demasiado cuando la base de datos se llena mucho
    El tiempo de respuesta no debería aumentar demasiado si se pasa de una base de datos con 100 filas en sus tablas a una con 50,000 filas. La tecnología de indexado de bases de datos hace que hallar una fila en una tabla tarde unos cuantos milisegundos, incluso si hay cientos de miles de filas. Por lo tanto, si el tiempo de respuesta aumenta mucho después de pasar de una base de datos de tamaño moderado a una de tamaño extremo, entonces probablemente aún no se han indexado las columnas apropiadas.

  2. El tiempo de respuesta aumenta exponencialmente cuando aumenta la carga
    Si el sistema se vuelve inutilizable conforme se aumenta el número de usuarios concurrentes, entonces el sistema no es escalable. Interpretar estos resultados es difícil pues el problema podría ser causado por el hardware, la configuración de despliegue, la arquitectura, etc. Asegúrese de que observa los recursos del servidor durante las pruebas:

    1. Observe los requerimientos de memoria.

    2. Observe el uso de CPU. Si la CPU se usa demasiado, se necesita un procesador más rápido o más procesadores. Si la CPU se usa poco, entonces probablemente el problema esté en la entrada/salida. Compruebe las conexiones de bases de datos, el número de threads (hilos) que se ejecutan y la configuración de la red en las máquinas de prueba.

Si el problema no se resuelve después de comprobar la configuración, de verificar que la lentitud no se debe a un cuello de botella del hardware y de revisar rápidamente la arquitectura buscando código para optimizar, es el momento de ejecutar un perfilador de código.
 

5. OPTIMIZACIÓN.

Se necesita optimizar la base de datos, la arquitectura, la configuración y el hardware. Como se mencionó en el apartado anterior, el motivo más común de que la aplicación no sea escalable es que no se haya afinado la base de datos. Un administrador de base de datos es siempre una persona vital en cualquier equipo de desarrollo, pero si no tiene ninguno, a continuación se muestra lo que puede hacer.

Revise bien sus Enterprise Javabeans y verifique que su base de datos no realiza búsquedas lineales para ninguna de las consultas SQL que hay en el código. Para hacer esto, copie el SQL del código y, en la ventana SQL de la base de datos, ejecute una cláusula EXPLAIN:

Explain select * from tabla where campo = valor

Aunque la sintaxis de EXPLAIN difiere de una base de datos a otra, siempre existe algo similar. Después de ejecutar esta línea de código, la base de datos le dirá si está buscando por un índice o realizando una búsqueda lineal. Asegúrese de que cada fragmento de SQL en la aplicación usa los índices de la base de datos y, si no es así, cree dichos índices.

Después de optimizar la base de datos y optimizar la configuración de hardware (como se explicó en el apartado anterior), el siguiente paso es optimizar el código y esto se hace con un perfilador.

Un perfilador es un programa que analiza la aplicación mientras ésta se ejecuta. Un perfilador provee información a la que no se podría acceder de otra manera, como, por ejemplo:

  1. Cuantos objetos de cada clase hay en memoria y el comportamiento del recolector de basura (garbage collector).

    • Esta información puede ayudar a identificar clases que deberían estar en un “pool”.
    • También puede ayudar a afinar el “heap” de Java.
  2. Cuanto tiempo la aplicación pasa en determinadas clases.
    Esta es la función más importante. El perfilador indicará qué clases son los cuellos de botella.

Un programa de éstos que me ayudó mucho fue Optimize-It. Optimize-It puede usarse con cualquier programa Java o con cualquier servidor de aplicaciones basado en Java. La configuración con Weblogic es sencilla y Optimize-It puede usarse como perfilador de una aplicación que se halla en un servidor remoto.

La optimización de la arquitectura es sumamente específica para cada proyecto, pero a continuación se incluyen algunos consejos:

  1. Asegúrese de que se han minimizado las llamadas a la red, especialmente las llamadas a la base de datos.

    • Es mejor tener una base de datos grande que muchas pequeñas.
    • Confirme que ejbStore no almacena nada para operaciones de sólo lectura.
    • Use objetos de detalle para obtener el estado de los beans de entidad.

  2. Asegúrese de aprovecharse de la “cache” cuando sea posible.
    Su servidor de aplicaciones probablemente permite colocar los beans de entidad en la “cache” de memoria. Asegúrese de que se aprovecha de ello, ya que reducirá dramáticamente las llamadas a la base de datos y acelerará el acceso a los datos.

  3. Asegúrese de que está usando beans de sesión como una “fachada” de los beans de entidad.
    Puede encapsular el flujo de trabajo de un caso de uso completo en una llamada de red a un único método de un bean de sesión (y en una única transacción).
     

6. PROBANDO Y OPTIMIZANDO THESERVERSIDE.COM

TheServerSide.com experimentó numerosos problemas de escalabilidad antes de su lanzamiento. Usando los consejos sugeridos en este artículo, resolvimos todos los problemas, por lo que TheServerSide.com es uno de los portales basados en Java más rápidos que hay.

El primer paso para probar TheServerSide.com fue llenar la base de datos con datos de prueba. Después de llenarla con una cantidad moderada y con una cantidad extrema (añadiendo 16,000 mensajes y 40,000 usuarios a nuestra base de datos), encontramos un problema serio. El tiempo de respuesta de nuestras páginas de nivel máximo saltó desde los 2 segundos a 12 segundos para un único usuario.

No habiendo leído este documento, cometimos el más común de los errores: doblamos la velocidad de nuestra CPU y la memoria de nuestra máquina. Esto sólo redujo el tiempo de respuesta a 8 segundos y, por lo tanto, no era la causa del cuello de botella.

El problema que teníamos indicaba que algo fallaba en la base de datos. Después de comprobar cómo nuestra base de datos procesaba nuestras consultas, descubrimos que nuestras columnas de clave primaria (y otras) no se indexaban correctamente. Esto significa que la base de datos tenía que hacer búsquedas lineales, incluso para ejbFindByPrimaryKey, que es la más común de las llamadas.

Después de hacer cambios a la base de datos y a nuestra estrategia de clave primaria (nuestra base de datos PostgreSQL presentaba errores al manejar el indexado de enteros de 8 bytes), pudimos indexar todas las columnas apropiadas y bajar el tiempo de respuesta a 3 o 4 segundos.

Una vez hubimos optimizado la base de datos, comenzamos a ejecutar pruebas de carga apropiadas. Usamos WebLoad, una potente herramienta para pruebas de cargas. La copia de evaluación permite probar con 12 usuarios concurrentes (probablemente representativos de 30 o 40 personas reales). Después de ejecutar las pruebas al máximo (12 usuarios concurrentes), encontramos que nuestro sitio Web era muy poco escalable. El tiempo de respuesta saltó de los 3 o 4 segundos de un único usuario a 15 o 20 segundos por página bajo cargas más pesadas. Obviamente, estos números no eran lo suficientemente buenos.

Habiendo optimizado la base de datos y actualizado el hardware, pasamos a examinar nuestra arquitectura. Se hicieron pequeñas modificaciones, pero aún no podíamos encontrar la causa del problema. Cuando empezamos a usar un perfilador, todo cambió. Después de ejecutar remotamente Optimize-It (tenía una ventana en mi máquina local mostrando las estadísticas del servidor ejecutándose en nuestro proveedor de servicios de Internet), descubrí la causa del problema. 30% del tiempo de CPU se perdía en comunicaciones de socket con nuestra base de datos. Optimize-It me permitió seguir la pista y ver qué objetos y métodos iniciaban estas llamadas. Así identifiqué un problema de diseño que hacía que consultáramos un conteo en la base de datos cada vez que queríamos mostrar un mensaje en TheServerSide.com. Después de arreglar ese problema tonto, el número de llamadas a la base de datos usadas para mostrar una página bajó de 15 a 1, y, de repente, nuestro tiempo de respuesta disminuyó a cerca de un segundo. Esto era exactamente lo que queríamos.
 

7. CONCLUSIÓN.

Realizar pruebas de rendimiento y optimizar una aplicación puede ser una tarea muy desafiante. Afortunadamente hay herramientas en el mercado que pueden simplificar el proceso. Usando esas herramientas y siguiendo los pasos sencillos que se han explicado en este artículo, usted debería ser capaz de seguir la pista a los cuellos de botella de un sistema de forma efectiva.

Gracias especiales a Mark Turnbull, el mejor administrador de base de datos que conozco.

Compruebe la calidad de nuestro desarrollo offshore.
Aurum Solutions tiene experiencia en desarrollo offshore en J2EE y .NET de alta calidad y reducidos costos para empresas europeas y norteamericanas. Conozca nuestro desarrollo offshore haciendo clic aquí o en la página Web ../../offshore.html.

Cursos de Aurum Solutions relacionados con el tema de este artículo:
“Enterprise Javabeans”
“Servlets y JSP”
“Buenas prácticas en arquitecturas J2EE”
“Buenas prácticas en programación en Java”
“Herramientas de pruebas para Java”