Cómo Escalar una Aplicación Web de Cientos a Miles de Usuarios Concurrentes
Escalar una aplicación no es solo “poner un servidor más grande”. Es rediseñar la arquitectura para que pueda sobrevivir cuando pasan de 10 usuarios conectados a 5,000 usuarios conectados al mismo tiempo.
En este artículo te voy a contar, paso a paso, cómo una aplicación real escrita en Django tuvo que transformarse para manejar esa carga, qué servicios de la nube se usaron (principalmente AWS) y qué decisiones técnicas marcaron la diferencia.
1. El problema inicial
La aplicación empezó como algo típico: un backend monolítico hecho en Django que servía páginas, ejecutaba la lógica del negocio, manejaba el panel de administración y hablaba con la base de datos (PostgreSQL). Ese tipo de stack también podría haber sido Laravel (PHP), NestJS (Node.js) o incluso Phoenix (Elixir); la idea arquitectónica es parecida.
El problema apareció cuando entraban 4,000 o 5,000 usuarios concurrentes. “Concurrentes” significa personas usando la app al mismo tiempo, no visitas totales del día. A ese nivel de tráfico, el servidor simplemente se caía.
2. Primer intento: escalar verticalmente
La reacción obvia fue “compremos una máquina más potente”.
Esto es escalado vertical: subir el plan del VPS para tener más CPU y más RAM.
Ejemplo típico:
- VPS de ~$20/mes → ~4 vCPU y ~2 GB RAM
- VPS de ~$40/mes → ~8 vCPU y ~4 GB RAM
- VPS de ~$300/mes → ~32 vCPU y ~32 GB RAM (aprox.)
Eso efectivamente aguanta más usuarios… hasta cierto punto. En este caso, incluso pagando cientos de dólares al mes por una instancia grande, la app seguía colapsando bajo la carga.
Conclusión: ya no bastaba con “hacer el servidor más grande”. Había que rediseñar.
3. Separar el frontend del backend
El siguiente cambio estratégico fue dividir responsabilidades.
Antes
- Django devolvía HTML, hacía la lógica y hablaba con la base de datos.
- Todo vivía junto.
Después
Django se convierte en una API.
Se elimina la parte de servir páginas para el usuario final. El backend ahora solo devuelve datos (JSON), procesa lógica y expone endpoints. Ya no entrega archivos estáticos ni HTML del frontend salvo, si quieres, para un panel interno tipo admin.El frontend se vuelve un proyecto aparte.
- Primero usando React + Vite.
- Luego migrado a Next.js por decisión del equipo (server-side rendering / routing más robusto).
- Este frontend consume la API de Django.
Despliegue separado del frontend.
El frontend se despliega en un servicio independiente. Nombras opciones comunes como Vercel, Netlify, Railway o Heroku. En este caso concreto, se desplegó en AWS Amplify porque toda la infraestructura ya estaba en AWS. Amplify es parecido conceptualmente a esas plataformas: toma tu frontend y lo sirve con recursos que ya corren sobre AWS (hablas incluso de configuraciones con múltiples vCPU y decenas de GB de RAM para servir el front).
¿Por qué esto ayuda a escalar?
Porque quitas peso del backend. Django ya no tiene que renderizar vistas pesadas ni servir estáticos al usuario final. El backend se enfoca en responder rápido a la API, y el frontend escala por separado.
4. Hacer más rápido el backend con caché (Redis / Valkey)
Una de las causas del cuello de botella eran endpoints que devolvían siempre casi la misma información una y otra vez.
Ejemplo: una función que arma una respuesta a partir de PostgreSQL, pero esa respuesta casi no cambia (tal vez solo cambia una vez al mes).
Solución: caché en memoria con Redis.
Flujo:
- Primera llamada:
- Django consulta PostgreSQL.
- Guarda la respuesta en Redis.
- Siguientes llamadas:
- En lugar de volver a pedirle a PostgreSQL (que lee de disco), devuelve directamente lo que está en Redis (que lee de memoria RAM).
Resultado:
- Una consulta que tomaba ~200 ms desde la base de datos puede bajar a ~2 ms desde Redis. Eso acelera brutalmente las respuestas, reduce carga en la base de datos y mejora la experiencia del usuario.
En AWS esto se implementó usando ElastiCache.
Dato interesante: además de Redis, hoy puedes usar Valkey, que es un fork abierto creado cuando Redis cambió de licencia. Valkey busca ser compatible con Redis, potencialmente con mejor costo/rendimiento.
5. Escalado horizontal (múltiples instancias)
Después de optimizar, vino el paso grande: escalar horizontalmente.
En lugar de intentar que UNA sola máquina gigante procese todas las peticiones, se crean muchas copias del backend (muchas instancias de Django), todas corriendo en paralelo.
Para repartir las solicitudes entre esas copias se usa un proxy / balanceador de carga:
- Técnicamente podrías usar NGINX o Traefik.
- En AWS se usó Application Load Balancer (ALB). ALB recibe cada petición del frontend y decide qué instancia de backend debe atenderla. Eso distribuye la carga y evita que una sola instancia se queme.
6. Contenerización y despliegue en AWS
Para poder tener “muchas copias del backend” de forma consistente, cada copia de Django se dockeriza.
El flujo queda así:
- Construyes una imagen Docker con tu backend Django (código + dependencias + configuración).
- Subes esa imagen a AWS Elastic Container Registry (ECR). Este es tu registro privado de imágenes.
- Usas AWS Elastic Container Service (ECS) para lanzar contenedores basados en esa imagen. ECS es el orquestador que sabe “cuántas tareas/instancias deben estar corriendo”.
- ECS necesita dónde correr esas tareas. Aquí hay dos opciones:
- EC2: básicamente máquinas virtuales (los “VPS” de AWS).
- Fargate: AWS administra por ti la infraestructura subyacente y escala automáticamente. En el caso del proyecto, se prefirió Fargate porque maneja el clúster de contenedores sin que tú tengas que estar creando y manteniendo manualmente las VMs.
Gracias a eso, escalar deja de ser “abre otra máquina tú mismo” y pasa a ser “ECS + Fargate levantan más tareas de contenedor cuando sube la carga”.
7. Servicios de apoyo alrededor del backend
El backend no solo responde peticiones HTTP. También interactúa con otros servicios que consumen recursos:
Almacenamiento de archivos
Uso de S3 (AWS Simple Storage Service) para guardar archivos subidos por usuarios, contenido estático, etc.Tareas en background
Uso de Celery dentro de Python para ejecutar tareas pesadas o programadas en background (por ejemplo, procesamiento que toma mucho tiempo).
Celery utiliza Redis no solo como caché sino como cola de trabajo/broker.Base de datos administrada
Uso de AWS RDS (Relational Database Service) para desplegar PostgreSQL en modo administrado.RDS Proxy
Uso de un proxy de base de datos (RDS Proxy) para optimizar y acelerar conexiones concurrentes hacia PostgreSQL cuando tienes muchas instancias del backend hablando con la misma base de datos.
8. Infraestructura como código y automatización
Hay varias formas de crear toda esta infraestructura en AWS:
Manual (clic en consola)
Ir servicio por servicio en la consola web y crear todo “a mano”. Es viable para entornos pequeños o pruebas rápidas.CLI y archivos de configuración
Usar las herramientas de línea de comando de AWS para describir lo que quieres y que se cree con comandos. Es más repetible y versionable en Git.Infraestructura como código (IaC)
Describir toda la arquitectura en archivos declarativos para poder recrearla igual en cualquier momento.- CloudFormation (propio de AWS, usando YAML/JSON).
- Terraform.
- Pulumi.
Esto no solo ahorra tiempo: también evita errores humanos y documenta tu infraestructura.
9. Autoescalado y políticas de escalado
Con ECS + Fargate puedes definir reglas del tipo:
- “Si la carga llega a X, levanta más instancias.”
- “Si baja la carga, apaga instancias para no pagar de más.”
Ejemplo conceptual mencionado:
- 1,000 usuarios concurrentes → ~20 copias del backend.
- 2,000 usuarios concurrentes → ~40 copias.
- 3,000 usuarios concurrentes → ~60 copias.
La idea es que el sistema escale dinámicamente según la demanda real.
Importante: esto depende del código.
Un backend mal optimizado puede consumir tantos recursos por usuario que te obliga a pagar muchísimo más. Has visto casos donde un mal diseño hizo que alguien gastara cientos o miles de dólares en pocos días por culpa de tareas muy pesadas corriendo en AWS.
10. Límites de la nube (cuotas)
Algo que casi nadie menciona: AWS no te deja crear 100 servidores de golpe en una cuenta nueva.
Cuando tu proyecto empieza a necesitar, por ejemplo, 20 instancias para sobrevivir a picos de tráfico, puede que tu cuenta por defecto solo tenga cuota para 12.
Tienes que pedir explícitamente a AWS que aumente tus límites (“aumentar cuota”) para poder escalar más allá.
En el caso descrito:
- Para ~5,000 usuarios concurrentes se llegó a necesitar entre 150 y 200 instancias de backend en los momentos de mayor carga.
- Eso implicó costos en el rango de $4,000–$5,000 USD al mes solo en infraestructura.
11. Pruebas de carga (Load Testing)
¿Cómo sabes si tu arquitectura realmente aguanta antes de que llegue el pico real de tráfico?
Ahí entran las pruebas de carga (load tests o stress tests).
Además de los tests normales de código (unit tests, tests de endpoint con Jest, Mocha, etc.), haces pruebas que simulan miles de usuarios golpeando tu frontend o tu API al mismo tiempo para medir:
- ¿Cuántas peticiones pasan exitosamente?
- ¿Cuándo empiezan a fallar las respuestas?
- ¿Cuánto tarda cada endpoint bajo presión?
Herramienta recomendada en el caso real: k6, un proyecto open source creado por Grafana.
k6 puede simular, por ejemplo, 1,000 / 3,000 usuarios concurrentes y luego te da un informe del rendimiento. Con esos resultados puedes estimar cuántas instancias necesitas para cierto pico (“si con 1,000 usuarios uso 20 instancias, con 2,000 probablemente usaré ~40”).
12. Resumen final / Lecciones
Empieza simple.
Para muchos proyectos nuevos basta con algo tipo Vercel, Railway, Render, etc. No necesitas toda esta arquitectura desde el día 1.Cuando llega el problema de escala, separa responsabilidades.
- Backend como API.
- Frontend independiente que consume esa API.
Usa caché agresiva.
Redis (o Valkey) para respuestas repetitivas reduce carga en la base de datos y baja la latencia.Escala horizontalmente, no solo verticalmente.
Varias réplicas del backend detrás de un balanceador (ALB en AWS, NGINX/Traefik en general).Conteneriza y orquesta.
Docker + ECR + ECS + Fargate = despliegue reproducible y autoescalable.Prueba tu infraestructura antes del pico.
Usa k6 u otra herramienta de load testing para simular tráfico real y estimar costos.Controla costos y cuotas.
AWS te limita al inicio, y cuando escalas en serio puedes estar gastando miles de dólares al mes.
Conclusión
Escalar no es magia, pero tampoco es solo “compra un servidor más grande”.
Es un diseño completo: separar frontend y backend, usar caché, balancear carga, contenerizar, orquestar, automatizar la infraestructura y validar todo con pruebas de carga antes de ir a producción.
Si estás en la fase en la que tu app ya “se cae con 200 usuarios concurrentes”, este es el camino que te espera.
Y si todavía estás en modo MVP con pocos usuarios… ahora ya sabes por qué las grandes empresas hablan tanto de AWS, escalado horizontal y microinfraestructura distribuida.