Lineamientos para probar los servicios de backend con balanceadores de cargas de aplicaciones

Cuando se integra un servicio de backend con un balanceador de cargas de aplicaciones, es importante medir el rendimiento de un servicio de backend por sí solo, ante la ausencia de un balanceador de cargas. Las pruebas de carga en condiciones controladas te ayudan a evaluar las compensaciones de planificación de capacidad entre diferentes dimensiones de rendimiento, como la capacidad de procesamiento y la latencia. Debido a que la planificación de capacidad detallada podría subestimar la demanda real, te recomendamos que uses pruebas de carga para determinar de forma proactiva cómo la disponibilidad de un servicio se ve afectada cuando el sistema está sobrecargado.

Objetivos de prueba de carga

Una prueba de carga típica mide el comportamiento visible a nivel externo del servicio de backend en diferentes dimensiones de carga. Algunas de las dimensiones más relevantes de esta prueba son las siguientes:

  • Capacidad de procesamiento de solicitudes: La cantidad de solicitudes entregadas por segundo.
  • Simultaneidad de las solicitudes: La cantidad de solicitudes procesadas de forma simultánea.
  • Capacidad de procesamiento de la conexión: la cantidad de conexiones que inician los clientes por segundo. La mayoría de los servicios que usan la seguridad de la capa de transporte (TLS) tienen cierta sobrecarga de transporte de red y negociación de TLS asociada a cada conexión que es independiente del procesamiento de solicitudes.
  • Simultaneidad de conexión: la cantidad de conexiones de cliente procesadas en simultáneo.

  • Latencia de solicitud: El tiempo total transcurrido entre el comienzo de la solicitud y el final de la respuesta.

  • Tasa de errores: La frecuencia con la que las solicitudes generan errores, como los errores HTTP 5xx y las conexiones prematuras.

Para evaluar el estado del servidor con carga, un procedimiento de prueba de carga también puede recopilar las siguientes métricas de servicio internas:

  • Uso de recursos del sistema: Los recursos del sistema, como CPU, RAM y controladores de archivos (sockets), suelen expresarse en porcentaje.

    La importancia de estas métricas difiere según la forma en que se implementa el servicio. Las aplicaciones experimentan un rendimiento menor, suprimen la carga o fallan cuando agotan sus recursos. Por lo tanto, es esencial determinar la disponibilidad de los recursos cuando un host tiene una carga pesada.

  • Uso de otros recursos delimitados: Recursos que no son del sistema y que podrían agotarse con carga, como en la capa de la aplicación.

    Algunos ejemplos de estos recursos incluyen los siguientes:

    • Un grupo delimitado de subprocesos o procesos de trabajadores.
    • Para un servidor de aplicaciones que usa subprocesos, es común limitar la cantidad de subprocesos de trabajador que operan de forma simultánea. Los límites de tamaño del conjunto de subprocesos son útiles para evitar el agotamiento de la memoria y la CPU, pero la configuración predeterminada suele ser muy conservadora. Los límites demasiado bajos pueden evitar el uso adecuado de los recursos del sistema.
    • Algunos servidores usan grupos de procesos, en lugar de grupos de subprocesos. Por ejemplo, un servidor Apache cuando se configura con el modelo de procesamiento múltiple de Prefork, asigna un proceso a cada conexión de cliente. Por lo tanto, el límite de tamaño del grupo determina el límite superior en la simultaneidad de la conexión.
    • Un servicio implementado como frontend en otro servicio que tiene un grupo de conexiones de backend de tamaño limitado.

Planificación de la capacidad frente a pruebas de sobrecarga

Las herramientas de prueba de carga te ayudan a medir diferentes dimensiones de escalamiento de forma individual. Para la planificación de capacidad, determina el umbral de carga para el rendimiento aceptable en varias dimensiones. Por ejemplo, en lugar de medir el límite absoluto de una solicitud de servicio en todo momento, considera medir lo siguiente:

  • La tasa de solicitudes en la que se puede entregar el servicio con una latencia del percentil 99 que sea menor que una cantidad específica de milisegundos. El SLO del servicio especifica el número.
  • El porcentaje de solicitudes máximo que no hace que el uso de recursos del sistema supere los niveles óptimos. Ten en cuenta que el uso óptimo varía según la aplicación y podría ser mucho menor que el 100%. Por ejemplo, con un uso máximo de memoria del 80%, la aplicación tal vez pueda manejar mejor los picos de carga menores que si el uso máximo fuera del 99%.

Si bien es importante usar los resultados de la prueba de carga para tomar decisiones de planificación de la capacidad, es igual de importante comprender cómo se comporta un servicio cuando la carga excede la capacidad. Estos son algunos comportamientos del servidor que a menudo se evalúan mediante pruebas de sobrecarga:

  • Reducción de carga: cuando un servicio recibe solicitudes o conexiones entrantes excesivas, podría responder mediante la reducción de todas las solicitudes o el rechazo de algunas solicitudes para mantener un rendimiento aceptable en las restantes. Recomendamos el último enfoque para evitar los tiempos de espera de los clientes antes de recibir una respuesta y para reducir el riesgo de agotamiento de memoria mediante la disminución de la simultaneidad de las solicitudes en el servidor.

  • Resiliencia contra el agotamiento de recursos: Un servicio suele evitar una falla en el agotamiento de recursos porque es difícil que las solicitudes pendientes avancen si el servicio falla. Si un servicio de backend tiene muchas instancias, la solidez de las instancias individuales es vital para la disponibilidad general del servicio. Mientras una instancia se reinicia debido a una falla, otras instancias pueden experimentar más carga, lo que podría causar una falla en cascada.

Lineamientos generales de pruebas

Cuando definas tus casos de prueba, ten en cuenta los siguientes lineamientos.

Crea pruebas a pequeña escala

Crea pruebas a pequeña escala para medir los límites de rendimiento del servidor. Con una capacidad de servidor excesiva, existe el riesgo de que una prueba no revele los límites de rendimiento del servicio, pero pueda descubrir cuellos de botella en otros sistemas, como los hosts del cliente o la capa de red.

Para obtener mejores resultados, considera un caso de prueba que usa una sola instancia de máquina virtual (VM) o un pod de Google Kubernetes Engine (GKE) a fin de probar el servicio de forma independiente. Para lograr la carga completa en el servidor, si es necesario, puedes usar varias VM, pero recuerda que pueden complicar la recopilación de datos de rendimiento.

Elige patrones de carga de bucle abierto

La mayoría de los generadores de cargas usan el patrón de bucle cerrado para limitar la cantidad de solicitudes simultáneas y retrasar las solicitudes nuevas hasta que se completen las anteriores. No recomendamos este enfoque porque es posible que los clientes de producción del servicio no muestren ese comportamiento de regulación.

Por el contrario, el patrón de bucle abierto permite a los generadores de cargas simular la carga de producción mediante el envío de solicitudes a una velocidad constante, sin importar la velocidad a la que llegan las respuestas del servidor.

Recomendamos los siguientes generadores de cargas para las pruebas de carga del servicio de backend:

Nighthawk

Nighthawk es una herramienta de código abierto desarrollada en coordinación con el proyecto Envoy. Puedes usarla para generar cargas de clientes, visualizar comparativas y medir el rendimiento del servidor para la mayoría de las situaciones de prueba de carga de los servicios HTTPS.

Prueba HTTP/1

Para probar HTTP/1, usa el siguiente comando:

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http1 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --connections CONNECTIONS

Reemplaza lo siguiente:

  • URI: El URI que se comparará
  • DURATION: Es el tiempo total de ejecución de prueba en segundos.
  • REQ_BODY_SIZE: el tamaño de la carga útil de POST en cada solicitud
  • CONCURRENCY: La cantidad total de bucles de eventos simultáneos

    Este número debe coincidir con el recuento de núcleos de la VM del cliente

  • RPS: La tasa objetivo de solicitudes por segundo, por bucle de evento

  • CONNECTIONS: Es la cantidad de conexiones simultáneas, por bucle de evento.

Consulta el siguiente ejemplo:

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http1 --request-body-size 5000 \
    --concurrency 16 --rps 500 --connections 200

En el resultado de cada ejecución de prueba, se proporciona un histograma de latencias de respuesta. En el ejemplo de la documentación de Nighthawk, ten en cuenta que la latencia del percentil 99 es de alrededor de 135 microsegundos.

Initiation to completion
    samples: 9992
    mean:    0s 000ms 113us
    pstdev:  0s 000ms 061us

    Percentile  Count       Latency
    0           1           0s 000ms 077us
    0.5         4996        0s 000ms 115us
    0.75        7495        0s 000ms 118us
    0.8         7998        0s 000ms 118us
    0.9         8993        0s 000ms 121us
    0.95        9493        0s 000ms 124us
    0.990625    9899        0s 000ms 135us
    0.999023    9983        0s 000ms 588us
    1           9992        0s 004ms 090us

Prueba HTTP/2

Para probar HTTP/2, usa el siguiente comando:

nighthawk_client URI \
    --duration DURATION \
    --open-loop \
    --no-default-failure-predicates \
    --protocol http2 \
    --request-body-size REQ_BODY_SIZE \
    --concurrency CONCURRENCY \
    --rps RPS \
    --max-active-requests MAX_ACTIVE_REQUESTS \
    --max-concurrent-streams MAX_CONCURRENT_STREAMS

Reemplaza lo siguiente:

  • URI: El URI que se comparará
  • DURATION: Es el tiempo total de ejecución de prueba en segundos.
  • REQ_BODY_SIZE: el tamaño de la carga útil de POST en cada solicitud
  • CONCURRENCY: La cantidad total de bucles de eventos simultáneos

    Este número debe coincidir con el recuento de núcleos de la VM del cliente

  • RPS: Es la tasa de destino de las solicitudes por segundo para cada bucle de eventos.

  • MAX_ACTIVE_REQUESTS: Es la cantidad máxima de solicitudes activas simultáneas para cada bucle de eventos.

  • MAX_CONCURRENT_STREAMS: la cantidad máxima de transmisiones simultáneas permitidas en cada conexión HTTP/2

Consulta el siguiente ejemplo:

nighthawk_client http://10.20.30.40:80 \
    --duration 600 --open-loop --no-default-failure-predicates \
    --protocol http2 --request-body-size 5000 \
    --concurrency 16 --rps 500 \
    --max-active-requests 200 --max-concurrent-streams 1

ab (herramienta de comparativas de Apache)

ab es una alternativa menos flexible a Nighthawk, pero está disponible como un paquete en casi todas las distribuciones de Linux. ab solo se recomienda para pruebas rápidas y simples.

Para instalar ab, usa el siguiente comando:

  • En Debian y Ubuntu, ejecuta sudo apt-get install apache2-utils.
  • En las distribuciones basadas en Red Hat, ejecuta sudo yum install httpd-utils.

Después de instalar ab, usa el siguiente comando para ejecutarlo:

ab -c CONCURRENCY \
    -n NUM_REQUESTS \
    -t TIMELIMIT \
    -p POST_FILE URI

Reemplaza lo siguiente:

  • CONCURRENCY: cantidad de solicitudes simultáneas que se deben realizar
  • NUM_REQUESTS: Cantidad de solicitudes que se realizarán
  • TIMELIMIT: Es la cantidad máxima de segundos que se invierten en las solicitudes.
  • POST_FILE: archivo local que contiene la carga útil de HTTP POST
  • URI: El URI que se comparará

Consulta el siguiente ejemplo:

ab -c 200 -n 1000000 -t 600 -P body http://10.20.30.40:80

El comando del ejemplo anterior envía solicitudes con una simultaneidad de 200 (patrón de bucle cerrado) y se detiene después de 1,000,000 (un millón) de solicitudes o 600 segundos de tiempo transcurrido. El comando también incluye el contenido del archivo body como una carga útil de HTTP POST.

El comando ab produce histogramas de latencia de respuesta similares a los de Nighthawk, pero su resolución se limita a milisegundos, en lugar de microsegundos:

Percentage of the requests served within a certain time (ms)
    50%     7
    66%     7
    75%     7
    80%     7
    90%    92
    95%   121
    98%   123
    99%   127
    100%  156 (longest request)

¿Qué sigue?