Nombre: | Tutorial #4 - Pruebas de Rendimiento: Análisis de capacidad |
Duración: | 60 minutos |
Profesor responsable | Harold Castro, Mario Villamizar |
Pre-Requisitos: | Shell GNU/Linux, Git, Postman |
Al finalizar el tutorial el estudiante estará en capacidad de:
En particular se utilizarán los siguientes recursos:
Las pruebas de rendimiento permiten estudiar el desempeño de un sistema o de una aplicación cuando el mismo se enfrenta a escenarios de carga o escenarios de estrés, similares a los que pueden suceder en producción.
Estas pruebas permiten conocer, la cantidad de usuarios simultáneos que soporta una aplicación, obtener datos para el dimensionamiento de la infraestructura requerida, así como brindar información para mejorar los tiempos de respuesta del sistema.
Estas pruebas son la herramienta para minimizar los riesgos en producción, como por ejemplo tiempos de respuesta elevados, consumo inadecuado de recursos, o interrupciones en el servicio.
Existen distintos tipos de pruebas de rendimiento, cada una con objetivos diferentes:
Una prueba de rendimiento que vamos a desarrollar en este curso se basan en la simulación de la demanda en un servicio o una aplicación para medir el desempeño de ese servicio o aplicación. Se usan las pruebas de rendimiento para medir, monitorear y observar las métricas de desempeño y otras características de un sistema bajo prueba.
Se usan las pruebas de rendimiento para medir, monitorear o observar las métricas de desempeño y otras características de un sistema bajo prueba. Esas características pueden incluir disponibilidad, confiabilidad, escalabilidad, etc. Cada atributo tiende a tener su propio conjunto de métricas o comportamiento.
Las pruebas de rendimiento evalúan la capacidad de un servicio para responder a las entradas de un usuario en un momento determinado del tiempo y bajo unas condiciones específicas. Una prueba de rendimiento, es un experimento que permite medir y analizar unos parámetros de interés de un servicio o aplicación.
Las pruebas de rendimiento deben cumplir 4 principios:
Las pruebas de carga se realizan para evaluar el comportamiento del sistema, bajo una cantidad esperada de usuarios concurrentes, que realizan un número específico de transacciones durante un tiempo predefinido.
Las pruebas de estrés se realizan para determinar el comportamiento del sistema cuando se enfrenta a cargas extremas. Se realizan aumentando el número de usuarios concurrentes y el número de transacciones que ejecutan, sobrepasando la carga esperada. Permiten conocer cómo es el rendimiento del sistema, en caso de que la carga real supere la carga esperada, determinando cuales son los componentes y/o recursos que fallan primero y limitan el desempeño del sistema.
Al diseñar una prueba de carga se debe considerar que los niveles creciente de carga de solicitudes concurrentes (usuarios virtuales) sean realistas, sin embargo, estas pruebas se pueden escalar hacia las denominadas pruebas de estrés. Las pruebas de estrés son cargas de trabajo que están más allá de los límites de las cargas de trabajo esperadas. Las pruebas de estrés se utilizan para evaluar la capacidad de un servicio o aplicación con unos recursos de infraestructura asignados.
Una carga representativa implica que se deben simular diferentes entradas de usuario (no solo una), es probable que se requiera una infraestructura dedicada solo para generar cargas efectivas.
Las métricas se definen a partir de los objetivos de la prueba de rendimiento y se utilizan para evaluar los resultados de la misma.
Las pruebas de rendimiento evalúan: capacidad de procesamiento, tiempos de respuesta, y utilización. Estas tres métricas son indispensables para diseñar pruebas de carga efectivas.
Ilustración 1. Ejemplo, comportamiento de las métricas en un escenario de carga.
Las pruebas de rendimiento deberán responder a interrogantes como: ¿la aplicación responde rápidamente? ¿cuánta carga de usuario puede manejar la aplicación con la infraestructura que actualmente la soporta?
En la gráfica anterior, puede analizar el comportamiento de las tres métricas mencionadas en función de la carga creada (simulación de usuarios). Los incrementos en la carga impactan la capacidad de procesamiento, el tiempo de respuesta y utilización. Inicialmente, la relación tiende a ser lineal con baja carga; a medida que la carga aumenta hacen que el comportamiento de las métricas sea no-lineal.
Este ejemplo ilustra la saturación de una CPU cuando el número de procesos supera la capacidad de la CPU. El tiempo de respuesta aumentará a razón del número de procesos en cola, y como consecuencia, la capacidad de procesamiento disminuye y afecta la experiencia de un usuario.
Las pruebas de rendimiento no deben ejecutarse sin comprender qué métricas y qué mediciones son necesarias, no ejecute sus pruebas sí:
Tenga en cuenta que las métricas recolectadas varían en función de:
Cuando defina las métricas recuerde incluir:
Las métricas y sus mediciones deben representarse en un reporte de métricas agregadas, este reporte es la visión del rendimiento de un servicio o una aplicación ante un escenario de pruebas definido. El reporte permite identificar tendencias de desempeño e informarlas de manera comprensible.
La aplicación que se utilizará en este tutorial corresponde al API RESTful utilizada en el tutorial anterior. Recuerde que esta API expone un par de servicios para cifrar y descifrar información con el algoritmo RC4.
Recuerde que el repositorio está disponible :
$ git clone https://github.com/jpadillaa/taller-api-flask-2.git
Utilice una máquina donde tenga instalada la aplicación, sino es así, aprovisione la aplicación en producción en un ambiente Gunicorn + Nginx + Ubuntu 20.04 LTS.
La planificación de la prueba tiene como objetivo identificar el ambiente, los datos de prueba, las herramientas y los recursos, además establece el alcance de la prueba de carga o estrés.
Los actores en el diseño del plan de pruebas pueden ser desarrolladores, personal de infraestructura, personal relacionado con la operación del negocio y usuarios finales. Estos actores definen los objetivos de la prueba y los criterios para determinar si estos objetivos se alcanzaron.
Es una buena práctica distinguir entre los objetivos técnicos (aspectos operativos) y los objetivos basados en el usuario final (estos se basan en la experiencia de usuario). Los aspectos operativos se relacionan con la capacidad de escalado del sistema y las condiciones de rendimiento.
Utilice preguntas orientadoras relacionadas con los objetivos de la prueba, ejemplo:
El resultado de este plan debe reflejarse en un documento llamado plan de pruebas, que se actualiza en cada iteración de los ciclos de desarrollo. El documento del plan de pruebas debe proporcionar la siguiente información:
Objetivo(s). Describe los objetivos, estrategias y métodos para la prueba. Están diseñados de tal forma que la respuesta pueda ser medible en lo relacionado con el desempeño de la aplicación o de un sistema.
Objetivos específicos. Se enumeran los objetivos específicos asociados a cada una de las métricas que se desean evaluar en el plan de pruebas.
Descripción general. Una breve descripción que proporciona contexto para la evaluación y medición de cada una de las métricas seleccionadas. En general debe incluir una descripción de alto nivel de las funcionalidades que se van a probar bajo una carga simulada y el por qué se seleccionó dicha carga.
Tipos de pruebas a realizar. Se listan las pruebas de rendimiento que se van a ejecutar con un su respectivo objetivo. Como lo son:
Criterios de aceptación. Deben establecerse los criterios de aceptación para todas las medidas pertinentes y relacionarlo con los objetivos generales de la prueba, los SLA's y los valores de la línea base.
Datos de prueba. Dados necesarios para ejecutar la prueba. Ejemplo: Cuentas de usuario, datos de entrada (asociados a un componente específico) y bases de datos de prueba.
Iteraciones. Recordemos que una prueba es un experimento, y para garantizar que los resultados de un experimento son confiables debemos iterar (repetir) el escenario de pruebas varias veces antes de aceptar los resultados del experimento y obtener las conclusiones. Mientras más iteraciones del escenario de pruebas son realizadas, más confianza se puede tener en los resultados. Los escenarios de pruebas se repiten normalmente de 5 a 10 veces con el propósito de garantizar confiabilidad de los datos recolectados y poder identificar los datos atípicos. Para un análisis rápido que brinde una visión muy general del sistema se sugiere un mínimo de 3 iteraciones.
Configuración del sistema. Información técnica asociada con:
Herramientas para la prueba. Software (es importante especificar la versión para entender las limitaciones del mismo) que se utiliza para crear los escenarios de prueba, la ejecución, el seguimiento y la generación de reportes. Ejemplo: AB, JMeter, entre otras.
Métricas. Identificar las mediciones y métricas que son relevantes para alcanzar los objetivos de la prueba. Ejemplo:
Riesgos. Destacar que no se va a medir en el plan de pruebas o qué limitaciones se tienen para considerarlas como parte del análisis.
La ejecución del plan de pruebas implica utilizar las herramientas seleccionadas previamente para generar carga contra la aplicación o los componentes del sistema a probar de acuerdo al perfil de carga. Un perfil de carga define la capacidad que un componente que se está probando puede recibir en un ambiente de producción. El perfil de carga debe considerar la cantidad de carga que recibe el componente y el periodo de tiempo en el que se ejecuta esta carga. Es importante considerar para este perfil el diseño de rampas de carga ascendente (ejemplo añadir 50 usuarios al componente por minuto), al igual que es importante definir la rampa descendente.
En los perfiles de carga también se consideran los escalones (cambios de carga instantánea) y distribuciones predefinidas (carga que simula el comportamiento típico de los usuarios).
Es importante resaltar que las herramientas que generan cargas (Ejemplo: AB o JMeter) soportan la ejecución del plan de pruebas. Pero estas normalmente no tienen integrada herramientas para monitorear la infraestructura de soporte de la aplicación (se requieren plugins o aplicaciones externas. Si la monitorización se configura adecuadamente se debe iniciar al mismo tiempo que se ejecuta la prueba. La monitorización es requerida durante la ejecución del plan de pruebas porque permite medir el impacto de la prueba sobre los recursos del sistema, ejemplo: AB o JMeter simulan una carga de usuarios concurrentes sobre un componente de una aplicación, estas herramientas permitirán medir los tiempos de respuesta y las transacciones por unidad tiempo, pero no permitirán monitorear el consumo de CPU y Memoria que se generó sobre la infraestructura de soporte.
Algunas herramientas de monitoreo simples y gratuitas que puede utilizar son:
Desde la máquina virtual 2, se ejecutaron las pruebas de carga al API REST para cifrar y descifrar mensajes, que fue provisionada al inicio de este tutorial. Primera recomendación, las pruebas de carga se deben realizar desde el mismo segmento de red del servidor, esto tiene dos objetivos prácticos: uno, evitar bloqueos de seguridad impuestos por los mecanismos de seguridad perimetral y dos, evitar que la latencia de la red y otros aspectos de la misma generen algún tipo de distorsión o afectación a los resultados de las pruebas de carga.
En esta sección del tutorial se desarrollan unas pruebas de carga básicas a la API REST y la infraestructura subyacente, utilizando el comando "ab" Apache Benchmark. Instale la utilidad con el comando:
$ sudo apt install apache2-utils
Para ejecutar las pruebas de carga sobre cualquiera de los servicios del API REST, basta con utilizar el comando presentado a continuación:
$ ab -n 1000 -c 100 -p data-cifrar.json -T application/json -rk
http://ip_servidor/cipher
$ ab -n 1000 -c 100 -p data-descifrar.json -T application/json -rk
http://ip_servidor/decipher
Cada parámetro corresponde a:
Con base a esto y retomando los comandos ejecutados previamente: el escenario de pruebas está lanzando 1000 solicitudes al servicio REST, de esas 1000 solicitudes 100 se realizan en simultáneo, es decir se ejecutan 10 pruebas en grupos de 100 solicitudes. Como los solicitudes al servicio son de tipo POST, es necesario respetar el formato de envío del cuerpo de la solicitud, los archivos data-cifrar.json y data-descrifrar.json corresponden a un documento de tipo JSON con datos válidos y equivalentes a una solicitud realizada con una herramienta como POSTMAN.
Ejemplo:
data-cifrar.json
{ "message": "La computación en la nube (del inglés cloud computing), conocida también como servicios en la nube, informática en la nube, nube de cómputo o simplemente «la nube», es un paradigma que permite ofrecer servicios de computación a través de una red, que usualmente es internet.", "key": "Wikipedia" }
data-descifrar.json
{ "message": "36E105B2CB98582C2AE99F7D7900250E4768186C4AD225C1EAD27F5905AB761786A7A9E9351FDABA1595C905A7C5D25C1C879791F33A9E34F7E147C3FDBF8A6B2675E54C43FFEC7ABB7C4D58F942C042D0892D5AEE90FDA62827076EDEE2D3629DE507A596425984F911A076E2845C858FE5A976B0EEF3A624920A586975DD67B17B7B5DBA3E7BE97619284CF4DA5CDABAA1BE4AF4353F66AC332548154C9C050BE3C98B5682DECA8DA35125D03458874DA7A48DA684519B162EB39537CFBEADAD53A538B81CB74B43530A50CB56A5A96E7380B4AC0AE8C130C5CC9699C465AAA70E0AC9A9B7A7EEBB4553AF3090AE297362F92DED68756BE1198F15FD43F21456A80C915D2575F75BF7CE9D32A6003423D5F8", "key": "Wikipedia" }
Usted obtendrá un resultado similar al siguiente:
This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.0.13 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.14.2 Server Hostname: 192.168.0.13 Server Port: 80 Document Path: /cipher Document Length: 566 bytes Concurrency Level: 100 Time taken for tests: 1.770 seconds Complete requests: 1000 Failed requests: 0 Keep-Alive requests: 1000 Total transferred: 721000 bytes Total body sent: 489000 HTML transferred: 566000 bytes Requests per second: 564.95 [#/sec] (mean) Time per request: 177.007 [ms] (mean) Time per request: 1.770 [ms] (mean, across all concurrent requests) Transfer rate: 397.78 [Kbytes/sec] received 269.78 kb/s sent 667.57 kb/s total Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.2 0 10 Processing: 12 168 30.6 174 209 Waiting: 2 168 30.6 174 209 Total: 12 168 28.6 174 209 Percentage of the requests served within a certain time (ms) 50% 174 66% 176 75% 178 80% 181 90% 187 95% 189 98% 190 99% 197 100% 209 (longest request)
Entre los datos relevantes del resultado de la prueba podemos resaltar:
Si graficamos los resultados de las pruebas (con alguna herramienta externa de soporte. Ejemplo: Gnuplot). Observamos:
El tiempo de respuesta promedio del servicio es 177 ms por solicitud, entre 0 y 100 solicitudes el tiempo de respuesta aumenta de forma lineal.
Vamos a asumir que este es un servicio de soporte para otras aplicaciones de negocio, este servicio provee funciones de Cifrado y Descifrado de información a las comunicaciones de otras aplicaciones. Por esta razón, este servicio puede introducir latencia que afecte el desempeño de otros procesos de negocio.
Se considera que el tiempo de respuesta es un atributo de calidad importante para este servicio, y se define que un valor promedio de 250 ms es aceptable. Un tiempo de respuesta superior a este afectaría la calidad de servicio y la experiencia de los procesos de negocio que requieren consumen este servicio.
Se debe repetir la prueba la prueba de carga incrementando la concurrencia de las solicitudes, con el objetivo de encontrar el valor máximo de solicitudes que es capaz de soportar esta aplicación y su infraestructura de soporte en simultáneo.
Incremente el número de solicitudes de 50 en 50, iniciando con el valor de referencia utilizando en la prueba anterior, es decir, una concurrencia de 100, 150, 200, 250, etc.
Ejemplo:
$ ab -n 1000 -c 250 -p data-cifrar.json -T application/json -rk
http://ip_servidor/cipher
Server Software: nginx/1.14.2 Server Hostname: 192.168.0.13 Server Port: 80 Document Path: /cipher Document Length: 566 bytes Concurrency Level: 250 Time taken for tests: 0.822 seconds Complete requests: 1000 Failed requests: 577 (Connect: 0, Receive: 0, Length: 577, Exceptions: 0) Non-2xx responses: 577 Keep-Alive requests: 1000 Total transferred: 495393 bytes Total body sent: 489000 HTML transferred: 339239 bytes Requests per second: 1216.39 [#/sec] (mean) Time per request: 205.526 [ms] (mean) Time per request: 0.822 [ms] (mean, across all concurrent requests) Transfer rate: 588.47 [Kbytes/sec] received 580.87 kb/s sent 1169.34 kb/s total
Como puede observar en el resultado anterior, para 250 solicitudes en simultáneo el tiempo de respuesta promedio es de 205 ms.
$ ab -n 1000 -c 300 -p data-cifrar.json -T application/json -rk
http://ip_servidor/cipher
Server Software: nginx/1.14.2 Server Hostname: 192.168.0.13 Server Port: 80 Document Path: /cipher Document Length: 566 bytes Concurrency Level: 300 Time taken for tests: 0.804 seconds Complete requests: 1000 Failed requests: 588 (Connect: 0, Receive: 0, Length: 588, Exceptions: 0) Non-2xx responses: 588 Keep-Alive requests: 1000 Total transferred: 491092 bytes Total body sent: 489000 HTML transferred: 334916 bytes Requests per second: 1243.16 [#/sec] (mean) Time per request: 241.321 [ms] (mean) Time per request: 0.804 [ms] (mean, across all concurrent requests) Transfer rate: 596.20 [Kbytes/sec] received 593.66 kb/s sent
Como puede observar en el resultado anterior, para 300 solicitudes en simultáneo el tiempo de respuesta promedio es de 241 ms. Lo que nos coloca sobre el límite del requerimiento o sobre el límite del criterio de aceptación. Para este caso al lanzar la prueba con 350 solicitudes concurrentes el tiempo de respuesta se incrementó a 289 ms, lo que para el requerimiento de negocio no es un valor aceptable.
Como conclusión de este escenario de pruebas este recurso es capaz de atender 300 concurrentes manteniendo el tiempo de respuesta sobre el criterio de aceptación. Si se requiere soportar más solicitudes concurrentes es necesario validar como escalar los recursos de la aplicación y su infraestructura de soporte.
Después de ejecutar el plan de pruebas es necesario recopilar los datos de interés, analizarlos y generar un reporte con los resultados y las conclusiones respectivas. Los datos recolectados se deben analizar y comparar con respecto a los objetivos definidos en el plan de pruebas. Los análisis y los resultados obtenidos deben facilitar identificar las recomendaciones y las acciones a ejecutar en el siguiente ciclo de desarrollo de la aplicación y respecto a la evolución de la infraestructura.
En este informe se analizan los siguientes datos:
Para comparar los datos recuerde:
Para generar una grafico a partir de los resultados de una prueba de carga realizada con AB, ejecute los siguientes pasos.
Instale Gnuplot:
$ sudo apt-get install gnuplot
Ejecute la prueba de carga pero modifique el comando para que guarde los datos recolectados en la prueba en un archivo de salida. Para esto agregue el flag -g y el nombre del archivo de salida. Ejemplo
$ ab -n 1000 -c 300 -p data-cifrar.json -T application/json -rk -g output.csv
http://ip_servidor/cipher
-g (gnuplot-file): Escribe todos los valores medidos como un ‘gnuplot'.
En la misma ruta donde se encuentra el archivo generado "output.csv", cree un nuevo archivo con extensión .p; ejemplo: "plot.p".
$ nano plot.p
Agregue la siguiente información:
set terminal png size 600 set output "reporte.png" set title "1000 peticiones, 300 peticiones concurrentes" set size ratio 0.6 set grid y set xlabel "Nro Peticiones" set ylabel "Tiempo de respuesta (ms)" plot "output.csv" using 9 smooth sbezier with lines title "http://ip_servidor/cipher"
Finalmente, para generar la gráfica "reporte.png" ejecute el comando:
$ gnuplot plot.p
Algunas conclusiones de este taller son:
[1] https://www.blazemeter.com/blog/why-load-testing-is-important
[2] https://www.blazemeter.com/blog/performance-testing-vs-load-testing-vs-stress-testing
[3] https://www.blazemeter.com/blog/load-testing-best-practices
[4] https://www.blazemeter.com/blog/5-things-we-hope-dont-happen-black-friday
[6] https://www.blazemeter.com/blog/open-source-load-testing-tools-which-one-should-you-use
[7] https://www.blazemeter.com/blog/how-get-started-jmeter-installation-test-plans
[8] https://www.blazemeter.com/blog/how-to-use-the-composite-graph-plugin-in-jmeter
[9] https://www.blazemeter.com/blog/apm-tools-comparison-which-one-should-you-choose
[10] https://www.blazemeter.com/how-to-convert-your-postman-api-tests-to-jmeter-for-scaling
[11] Apache HTTP benchmarking tool - https://httpd.apache.org/docs/2.4/programs/ab.html