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
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.
Pruebas de carga y escalabilidad. El proceso de probar
la carga y escalabilidad tiene dos aspectos:
- Probar el tiempo de respuesta cuando se aumenta el tamaño
de la base de datos.
- Probar el tiempo de respuesta cuando se aumenta el número
de usuarios concurrentes.
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.
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:
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.
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:
Observe los requerimientos de memoria.
- 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:
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.
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:
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.
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.
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”
|