One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

Autoscaling para picos de tráfico: qué escala de verdad y qué no debería

Un pico programado alcanza su máximo en segundos y un servidor nuevo tarda minutos. Descubre qué escala bajo una ráfaga, por qué el preescalado supera a las reglas reactivas, cómo dimensionar SSR y PHP-FPM y cómo hacer pruebas de carga.

Author

Anichur Rahaman

hace 3 semanas11 min read2 views
Autoscaling para picos de tráfico: qué escala de verdad y qué no debería

Veinte solicitudes por segundo, sin un solo pico: ese es el tráfico de una tienda en línea a las 11:59 p. m. de la noche en que arranca una venta relámpago, mientras la persona responsable de la plataforma mira el panel. La regla de autoscaling está lista: agregar un servidor si el CPU se mantiene por encima de 70 % durante cinco minutos. (Es una escena ilustrativa, no una tienda real.)

A medianoche abre la venta y unos mil compradores entran en el mismo minuto. La carga salta a 440 solicitudes por segundo. A las 12:01 el panel sigue en verde, porque el promedio de cinco minutos apenas se movió. A las 12:03 el pago se queda sin respuesta. El servidor nuevo se une a las 12:06, cuando los compradores ya se fueron con la competencia.

El autoscaling reacciona a la carga, y un pico programado llega antes que la reacción. Este artículo explica qué escala de verdad bajo una ráfaga, qué no se le debe pedir que escale y cómo comprobarlo antes del evento, no durante.

Los números que siguen salen de mis propias pruebas en una sola configuración con Laravel y Next.js. Muestran el método, no un punto de referencia universal, así que repite la medición en tu sistema.

Esta es la parte 1 de la serie de cinco partes «Ingeniería para alto volumen». Cubre el camino de la solicitud, desde el borde hasta la aplicación. Las colas, la capa de datos, la observabilidad y los despliegues seguros vienen en las partes 2 a 5.

Un pico no es un día ocupado

Dos formas de tráfico piden dos respuestas distintas. Un aumento gradual le da tiempo a cualquier lazo de control para notar y responder. Un escalón no. El salto de 20 a 440 de la escena inicial es un escalón, y una regla que promedia el CPU durante cinco minutos no lo ve a tiempo.

La buena noticia es que un pico programado se puede prever. Conoces la hora de inicio, sabes más o menos cuánta gente viene y muchas veces puedes reproducir la mezcla de solicitudes del evento anterior. Esa anticipación es tu mejor herramienta, y la mayor parte de este artículo trata de cómo aprovecharla.

El camino de la solicitud, del borde a la app

Antes de decidir qué escalar, dibuja el camino que sigue una solicitud. En las configuraciones que opero se ve así: CDN y WAF en el borde, un balanceador de carga administrado, dos nodos web de backend, uno o más nodos de frontend con renderizado en servidor (SSR), un nodo de workers para las tareas en segundo plano y, detrás de todo, la capa de datos.

Arquitectura de referencia: CDN y WAF, balanceador de carga, nodos web de backend y nodos de frontend SSR, nodo de workers, y después base de datos, Redis y almacenamiento de objetos
El camino de la solicitud, del borde a los datos. Cada capa escala de forma distinta y a distinta velocidad.

Cada caja tiene su propio modo de fallar y su propia palanca para escalar. Tratarlas como un solo «servidor» es la forma en que un equipo termina agregando RAM a la capa que nunca fue el problema.

El borde y el balanceador de carga

Primero pon un CDN al frente. Los archivos estáticos, las imágenes y cualquier página igual para todos los visitantes no deberían llegar a tus servidores durante un pico. Las reglas de WAF y de bots en esa misma capa también evitan que los scrapers consuman la capacidad que reservaste para los compradores.

Detrás del CDN, usa un balanceador de carga administrado con verificaciones de salud y vaciado de conexiones (connection draining). Las verificaciones sacan solas a un nodo enfermo. El vaciado deja que el nodo termine las solicitudes en curso antes de irse, y por eso los despliegues y las reducciones de nodos pasan inadvertidos para los usuarios.

Vale la pena revisar dos detalles. Primero, el algoritmo de balanceo. Muchos balanceadores en la nube usan por defecto round robin, que supone que todas las solicitudes cuestan lo mismo. El pago y la búsqueda no cuestan lo mismo. Un modo de «menos solicitudes pendientes» o «menos conexiones» envía la siguiente solicitud al nodo con menos trabajo en curso. En AWS, por ejemplo, round robin es el valor por defecto del Application Load Balancer, y least outstanding requests es un ajuste por grupo de destino. Segundo, el balanceador también es un servicio que escala. La documentación de AWS indica que un Application Load Balancer puede duplicar su capacidad en unos cinco minutos, y ofrece capacidad reservada para eventos que más que dupliquen el tráfico en menos de cinco minutos (consulta la guía de reserva de capacidad). Otros proveedores tienen límites parecidos, así que pregunta al tuyo.

En mis pruebas de carga el balanceador nunca fue el cuello de botella: el CPU se quedó en 8 % o menos, con cero errores a la tasa más alta. Es lo habitual. Busca primero más adentro del camino.

Nodos web sin estado: el precio de entrada

Solo puedes poner dos nodos web detrás de un balanceador si cualquiera de ellos puede atender cualquier solicitud. Esa propiedad se llama «sin estado» (stateless): es barata si se hace bien desde el principio y dolorosa si se corrige después. Usa esta lista antes de agregar un segundo nodo.

  1. Sesiones en Redis o Valkey, no en archivos del disco del nodo.
  2. Caché de la aplicación en Redis o Valkey, compartida por todos los nodos.
  3. Archivos subidos y generados en almacenamiento de objetos (compatible con S3), nunca en el disco local.
  4. Un solo scheduler. Las tareas tipo cron deben correr en exactamente un nodo; si no, cada tarea se ejecuta dos veces.
  5. Compilaciones idénticas. Todos los nodos corren la misma imagen, con la configuración tomada del entorno.
  6. Límites de subida alineados en cada capa. Cada proxy tiene su propio límite de cuerpo (client_max_body_size en nginx, post_max_size y upload_max_filesize en PHP). En una configuración, el valor por defecto de 1 MB del proxy del host rechazó subidas en silencio hasta que todas las capas coincidieron.

Una plataforma autoalojada como StoreConsole guarda sesiones y caché en Redis por esta razón, para que los nodos sean intercambiables.

La capa SSR: un proceso de Node equivale a un núcleo

Esta capa esconde la trampa más filosa. Un servidor Next.js renderiza las páginas en un proceso de Node.js, y Node ejecuta tu JavaScript en un solo hilo. Sin importar lo grande que sea la máquina, un proceso usa más o menos un núcleo para renderizar.

Reproduje con k6 la mezcla real de solicitudes de un pico anterior contra un solo proceso de frontend. Se saturó en unas 220 a 250 solicitudes por segundo. A partir de ahí, la latencia p95 pasó de unos 260 ms a unos 3 segundos, y el p99 llegó a 6.4 segundos. Durante todo ese tiempo el host tenía más de dos núcleos libres. El minuto pico del evento real necesitaba unas 440 solicitudes por segundo, así que un solo proceso habría fallado justo en el momento que importaba.

La solución fue sencilla: correr varios contenedores de frontend idénticos desde la misma imagen (tres, en mi caso) detrás de nginx con least_conn, que envía cada solicitud al servidor con menos conexiones activas, y repetir la prueba a la tasa objetivo. Hay un detalle propio de Next.js: por defecto cada instancia guarda su propia caché local, así que con varias instancias conviene configurar el framework con un almacén de caché compartido, como describe su documentación.

La aritmética

La planeación de capacidad aquí es una multiplicación simple. Toma el límite medido por proceso, multiplícalo por el número de procesos y compáralo con el pico esperado, con margen.

ConceptoValor (una configuración)Nota
Minuto pico necesario~440 req/sAl reproducir el último evento real
Un proceso SSR, medido220-250 req/sMás allá, p95 subió de ~260 ms a ~3 s
Tres procesos SSR~660-750 req/sAlrededor de 1.5 veces el pico, como margen
CPU del balanceador8 % o menosNo era el cuello de botella

PHP-FPM: dimensiona el pool a propósito

El backend web tiene el problema contrario. PHP-FPM corre un pool de procesos worker, y cada uno atiende una solicitud a la vez. Si el pool es muy pequeño, las solicitudes hacen fila. Si es muy grande, al nodo se le acaba la memoria y empieza a usar swap o a matar procesos.

Una regla práctica: los workers ocupados que necesitas son, más o menos, las solicitudes por segundo multiplicadas por el tiempo promedio por solicitud. Un ejemplo ilustrativo: 200 solicitudes por segundo a 150 ms cada una requieren unos 30 workers ocupados, así que un pool de 60 deja margen para las solicitudes lentas. Después revisa la memoria: si un worker usa unos 100 MB, 60 workers necesitan unos 6 GB, así que el nodo debe tener más que eso.

Los valores que usé en una configuración fueron pm.max_children=60, start_servers=30, min_spare_servers=20, max_spare_servers=30 y max_requests=1000, con keepalive entre nginx y FPM (keepalive 16 y fastcgi_keep_conn on). Arrancar con 30 importa: el pool ya está caliente cuando llega la ráfaga, en lugar de crear procesos bajo presión.

Dos hábitos más rinden frutos. Activa OPcache en producción con validate_timestamps=0 y reinicia los workers en cada despliegue. Y considera fastcgi_next_upstream error timeout http_500 http_503 con fastcgi_next_upstream_tries 2, que convirtió fallas pasajeras de FPM en reintentos silenciosos. Esto solo es seguro para solicitudes idempotentes. Reintentar un POST de pago no es una solución, es un error.

Por qué el autoscaling reactivo llega tarde

Ahora la idea central. El autoscaling reactivo vigila una métrica, espera a que cruce un umbral, lanza una máquina y espera a que arranque, se una al balanceador y caliente sus cachés. En la práctica eso toma de uno a tres minutos en el mejor de los casos. Los valores por defecto pueden alargarlo: AWS EC2 Auto Scaling, por ejemplo, aplica un cooldown por defecto de 300 segundos, y el monitoreo básico publica las métricas de las instancias solo cada cinco minutos, a menos que pagues el monitoreo detallado de un minuto.

Un pico programado alcanza su máximo en segundos. La máquina nueva llega cuando la gente ya se fue o, peor, cuando ya vio los errores. Y una máquina virtual no puede crecer en RAM sin reiniciarse, así que «agrandarla en el sitio» tampoco ayuda.

Línea de tiempo que compara el autoscaling reactivo, que agrega capacidad minutos después del máximo del pico, con el preescalado, que tiene la capacidad lista antes del evento
El escalado reactivo persigue la carga. El preescalado la espera ya listo. Línea de tiempo ilustrativa.

Preescala para los eventos que conoces

Para un evento programado, sube el número mínimo de nodos antes de que empiece y bájalo al terminar. Cuesta menos de lo que parece: un nodo extra durante unas horas vale muy poco frente a una venta fallida. Y es más confiable que cualquier regla, porque no depende de que una métrica cruce una línea a tiempo.

Deja las reglas reactivas como red de seguridad para lo inesperado, no como el plan. Y escala lo que se escala rápido. Los procesos dentro de un contenedor arrancan en segundos y las máquinas tardan minutos, por eso escalo los workers de las colas por proceso y no por máquina (en la parte 2 de esta serie).

EnfoqueTiempo de reacciónIdeal para
Preescalado (subir el mínimo)Listo antes del eventoPicos programados
Autoscaling reactivo de VM1-3 minutos o másCrecimiento gradual, tráfico imprevisto
Escalado por procesosSegundosWorkers de colas dentro de un contenedor
Diagrama de flujo: si el inicio del pico es conocido, si la carga sube despacio, si el trabajo son tareas en cola, y qué palanca de escalado señala cada respuesta
Qué palanca jalar y cuándo: tres preguntas deciden entre preescalado, escalado reactivo, escalado por procesos y protección en el borde.

Haz pruebas de carga como si fuera el evento real

Ninguno de los números anteriores salió de una suposición. Salieron de pruebas que se parecen al evento. Una prueba que solo golpea la página de inicio demuestra muy poco. Sigue esta secuencia.

  1. Captura la mezcla real de solicitudes de los registros de acceso del último evento: qué URL, en qué proporciones, con qué pausas entre acciones del usuario.
  2. Reprodúcela con k6 (u otra herramienta similar) a la tasa que esperas y luego a 1.5 veces esa tasa.
  3. Define umbrales de aborto en el script, como p95 por encima de 3 segundos o cualquier 5xx o timeout, para que una prueba que falla se detenga sola en vez de dañar un sistema compartido.
  4. Agrega un script guardián que vigile el CPU de los nodos y detenga la corrida si se llega a los límites.
  5. Compara tus métricas con las del proveedor de nube. En mis pruebas coincidieron con unos pocos puntos porcentuales de diferencia, y eso me dijo que los paneles eran confiables.
  6. Cambia una sola cosa a la vez y repite la prueba a la tasa objetivo después de cada corrección.

No actualices el hardware antes de que una prueba demuestre dónde está el límite. En mi caso las máquinas estaban bien. El límite era el proceso de un solo hilo, y la solución costó únicamente configuración.

La misma medianoche, ya preparada

Corre otra vez la escena inicial, ahora con la preparación hecha. A las 11:00 p. m. el mínimo pasa de dos nodos web a tres, y la capa SSR corre tres procesos detrás de least_conn. La caché está caliente y el pool de FPM ya tiene 30 workers.

A medianoche llegan 440 solicitudes por segundo. El techo medido es de unas 660, así que el p95 se mantiene cerca de su nivel normal y la regla de autoscaling nunca se dispara, porque nada la necesita. A las 12:30 a. m. el mínimo vuelve a bajar. El evento costó unas horas de un nodo extra.

Las capas que están detrás de este camino fallan de otra manera. Los trabajos en segundo plano suelen romperse primero bajo una ráfaga, y la parte 2 trata sobre colas y workers a escala. La parte 3 pasa a la capa de datos bajo carga, la parte 4 a la observabilidad y la parte 5 a la seguridad y los despliegues sin caída. Para decidir qué tan grande debe ser el servidor de tu negocio conforme crece, revisa el artículo anterior sobre la ruta de crecimiento del servidor.

Ideas clave

  • Un pico programado es un escalón, no una rampa. El autoscaling reactivo tarda minutos y reacciona cuando el pico ya pasó.
  • Para eventos conocidos, preescala: sube el número mínimo de nodos antes del inicio y bájalo después.
  • Mantén los nodos web sin estado: sesiones y caché en Redis, archivos en almacenamiento de objetos, un solo scheduler.
  • Un proceso SSR de Node.js usa más o menos un núcleo. Corre varios procesos idénticos detrás de least_conn y prueba a la tasa objetivo.
  • Dimensiona el pool de PHP-FPM con las solicitudes por segundo, el tiempo por solicitud y la memoria por worker, y caliéntalo de antemano.
  • Haz pruebas de carga con la mezcla real de solicitudes, umbrales de aborto y un script guardián, y corrige el cuello de botella medido antes de comprar hardware.

Anichur Rahaman es arquitecto de software y creador de StoreConsole. Diseña sistemas de comercio y ERP para negocios en crecimiento, con enfoque en arquitectura orientada a eventos, integridad de datos y operación en servidores propios.

About the Author

Anichur Rahaman

Continue Reading