Parte 1: Lo que Alta Disponibilidad Realmente Significaba para NovaCommerce
Una plataforma de examen ficticia con números reales: un servidor frontend, dos backends fijos, un nodo de base de datos y dos standby apagados. La Parte 1 define alta disponibilidad y muestra cómo el cuello de botella se movía constantemente.
El 25 de septiembre, durante los exámenes, los estudiantes comenzaron a ver HTTP 429, "demasiadas solicitudes". La causa no era una falta de servidores. Era una regla que contaba lo equivocado: un rate limiter contaba por dirección IP, y una escuela pone cientos de estudiantes detrás de una sola dirección. Para el limitador, todo un edificio se parecía a una sola persona muy impaciente.
Once días después, a las 02:28 del 6 de octubre, una prueba de carga en el backend devolvió 2.854 errores de aplicación. Todos decían lo mismo: un timeout de conexión a Valkey. Una capa diferente, una causa diferente, un arreglo diferente. Después vino el CPU plano, que es el único problema que más servidores realmente resuelven.
Esa secuencia es la verdadera historia de este proyecto: el cuello de botella se movía constantemente. Agregar servidores no es arquitectura. Es uno de los movimientos posibles, y el último.
Entre fines de septiembre y el 7 de octubre de 2026, llevamos una plataforma Laravel y Next.js en DigitalOcean de un servidor frontend y dos backends fijos a una configuración lista para un pico de examen programado. Esta primera parte cubre el punto de partida: el problema empresarial, lo que heredamos, por qué no era suficiente, qué significaba "alta disponibilidad" para nosotros, y qué nos enseñaron las primeras pruebas.
Antes de comenzar: NovaCommerce es un nombre ficticio, utilizado para este caso de estudio. Omití cualquier cosa que identificara a la empresa, sus servidores o sus estudiantes.
Esta es la parte 1 del caso de estudio de cinco partes "From One Server to Exam-Day Ready". NovaCommerce es un nombre ficticio; la arquitectura, los números y los errores son reales.
Una plataforma cuyo tráfico llega según un horario
NovaCommerce es una plataforma en línea que vende cursos y ejecuta exámenes en línea cronometrados para estudiantes. Las personas se registran, pagan, toman exámenes en vivo, ven sus resultados y utilizan ayuda de estudio con IA.
La mayoría del tráfico web es una colina: sube por la mañana y baja por la noche. El tráfico de examen es un acantilado. Piensa en las puertas de una sala de conciertos. Nada sucede durante horas, luego las puertas se abren y todos llegan juntos. Cuando comienza un examen programado, miles de estudiantes golpean la plataforma en el mismo minuto.
La buena noticia es que el acantilado tiene un horario. Sabemos cuándo llega. Describo esa ventaja en términos generales en la guía sobre autoscaling para picos de tráfico. Esta serie es la otra mitad: una plataforma real, con números reales.
El negocio nos dio cuatro requisitos, en lenguaje simple.
Los picos de examen son programados y agudos. Miles de estudiantes llegan en el mismo minuto.
Los envíos nunca deben perderse. Una página lenta es molesta. Un envío de examen perdido es el trabajo de un estudiante desaparecido.
Los deploys deben ser invisibles. El sitio tiene que estar en funcionamiento mientras enviamos una versión.
El costo debe mantenerse sensato. La capacidad que nadie necesita cuenta como un defecto también.
Cada decisión técnica en esta serie se remonta a una de esas cuatro líneas.
El punto de partida: lo que heredamos
Aquí está la configuración tal como la encontramos antes del 5 de octubre de 2026, en una región de DigitalOcean y una red privada.
Dos puntos únicos de falla (el frontend y la base de datos), una lista fija de dos backends, y dos servidores standby que estaban apagados pero aún facturados.
El frontend: un servidor, ocho CPUs, un único cajero
El frontend era un único droplet con 8 vCPU y 16 GB de memoria, ejecutando un contenedor Next.js. El dominio apuntaba directamente a él, y TLS provenía de certbot en el droplet. No había load balancer. Si ese servidor moría, el sitio estaba caído.
También desperdiciaba dinero. Un proceso Node.js se renderiza en aproximadamente un núcleo, por lo que la mayoría de los ocho vCPU estaban inactivos: una tienda con ocho cajas y un único cajero.
El backend: dos servidores que un load balancer no podía hacer crecer
Un load balancer de DigitalOcean estaba enfrente de dos droplets backend fijos, cada uno con 4 vCPU y 8 GB. La aplicación es Laravel (PHP-FPM) detrás de nginx en Docker, como dos contenedores por nodo: web para nginx y app para php-fpm. Las colas (Horizon), el scheduler y Reverb, el servidor WebSocket para actualizaciones en vivo, corrían en un worker separado.
El load balancer apuntaba a cada backend por su ID fijo, no por etiqueta. Esa es la diferencia entre una lista de invitados con nombres en ella y una regla que dice "cualquiera que use un distintivo de personal". Con nombres, un nuevo servidor nunca puede entrar por su cuenta. Alguien tiene que editar la lista.
Había buenas noticias aquí, y importaban más que cualquier otra cosa. Los nodos web del backend ya eran stateless. Las sesiones, el cache y las colas vivían en Valkey gestionado, y los logs iban a stderr. Un servidor que puedes eliminar sin perder nada es un servidor que puedes multiplicar. Sin esta propiedad el resto de la serie no hubiera podido suceder.
La base de datos y los standbys apagados
La base de datos era un nodo Managed MySQL "Advanced" con 8 vCPU y 32 GB: un único nodo, sin standby.
Luego había dos droplets "standby", mantenidos como respaldo. Estaban apagados. Un droplet apagado aún cuesta dinero, y traerlo cuesta minutos. Es una llanta de repuesto en un garaje cerrado del otro lado de la ciudad: existe, pagas por ella, y no ayuda mientras estés varado en la carretera. Eso no es alta disponibilidad.
Dos cosas más pequeñas estaban en la esquina. Los certificados TLS del load balancer eran cargas manuales con fecha de vencimiento, por lo que la renovación era una tarea recurrente que alguien tenía que recordar. Y el límite de droplets de la cuenta era 25, lo que importaría una vez que dos pools pudieran crecer y reemplazar nodos (parte 2).
Por qué esto no era suficiente
Parte de la configuración
Cómo se construyó
Qué sucede bajo estrés o falla
Frontend
Un droplet de 8 vCPU / 16 GB, un contenedor Next.js, sin load balancer
Sitio caído si muere; la mayoría de núcleos inactivos, porque un proceso Node usa aproximadamente un núcleo
Backend
Dos droplets fijos de 4 vCPU / 8 GB detrás de un load balancer, apuntados por ID
Sin crecimiento automático; un nuevo servidor nunca puede unirse por sí solo
Base de datos
Un nodo Managed MySQL, 8 vCPU / 32 GB, sin standby
Sin segundo nodo listo para tomar el control si el nodo falla
Standbys
Dos droplets, apagados
Facturados mientras están apagados; minutos para encender
Mira lo que falta de esa tabla: un bug. Nada estaba roto. La configuración hacía lo que se construyó para hacer. El problema era un desajuste entre un acantilado programado y una configuración que no podía perder un servidor, no podía agregar uno por sí solo, y mantenía su capacidad de reserva apagada. No puedes parchear un desajuste. Tienes que cambiar la forma del sistema, y eso es lo que significa arquitectura.
Lo que "alta disponibilidad" realmente significaba aquí
"Alta disponibilidad" es una de esas frases que todos asienten y nadie define. Antes de cambiar nada, escribimos lo que significaba para esta plataforma. Se redujo a cuatro líneas.
Ningún servidor único cuya pérdida derriba el sitio. La base de datos sobrevive la pérdida de un nodo. Los deploys y la reducción de escala son invisibles para los estudiantes. La capacidad está lista antes de que comience un examen, no cinco minutos después.
Me gusta una definición que puedas probar con una pregunta. ¿Podemos desenchufar cualquier servidor y seguir sirviendo? ¿Puede fallar un nodo de base de datos sin que se detenga el examen? ¿Podemos enviar un release sin que un estudiante se dé cuenta? ¿Está la capacidad ya allí cuando el primer estudiante hace clic en "iniciar"?
Dos reglas empresariales están en la parte superior: los envíos nunca se pierden, y el costo se mantiene sensato. La figura mapea cada requisito a la capa que debe entregarlo y a la parte de la serie donde se trata.
Cada requisito tiene un propietario: una capa que debe entregarlo, y una parte de esta serie que muestra cómo.
La última línea de la definición, "no cinco minutos después", es la que dolió. En nuestras pruebas, autoscaling agregó el primer nodo extra aproximadamente siete minutos después de que CPU alcanzó 99%. Siete minutos es mucho tiempo cuando un examen comienza en uno. La capacidad tiene que estar allí antes del acantilado, no después de él.
Las primeras pruebas: el cuello de botella se mueve constantemente
Con la definición escrita, hicimos lo que el método exige: medir primero. Entre fines de septiembre y el 6 de octubre vimos producción y ejecutamos pruebas de carga, y encontramos tres problemas seguidos. Se ven sin relación, y ese es el punto.
Tres cuellos de botella seguidos, cada uno con un tipo diferente de arreglo. Solo el tercero se resuelve agregando servidores.
1. El limitador: un problema de código (25 de septiembre)
Medido en producción: los estudiantes recibían HTTP 429 durante los exámenes. El global API rate limiter estaba codificado por IP, porque el guard de autenticación por defecto estaba vacío para estudiantes basados en token, por lo que el limitador no podía saber quién era un estudiante. Una escuela o un operador de telefonía móvil pone cientos de estudiantes detrás de una dirección NAT, y todo el edificio compartía un bucket.
Estado: Implementado. Codificamos el API limiter por cuenta de estudiante o instructor, y por IP solo para invitados. Más servidores no hubieran ayudado; la regla decía no, no el hardware. Un primo cercano de este bug volvió el 7 de octubre en los throttles de nivel de ruta, y esa historia está en la parte 4.
2. Conexiones Valkey: un problema de dimensionamiento de la capa de datos (6 de octubre)
A las 02:28 el 6 de octubre, una prueba de carga del backend devolvió 2.854 errores de aplicación 500. Cada uno fue RedisException: Operation timed out mientras se conectaba, con un timeout de conexión de 5 segundos. Valkey (4 GB, primary más standby) no podía aceptar conexiones lo suficientemente rápido bajo carga.
Cómo la aplicación lo usaba agregaba presión. Cada solicitud abrió una conexión TLS fresca a Valkey (aproximadamente 5.4 ms de CPU por solicitud) y otra a MySQL (aproximadamente 3.3 ms). Imagina una tienda donde cada cliente es escaneado en la puerta, cada vez. Bajo una multitud, la puerta se convierte en la cola.
Estado: Implementado, luego Probado. Redimensionamos Valkey in-place a 8 GB con dos nodos (primary y standby), datos preservados y host sin cambios. Luego reexaminamos con una rampa gradual de 25 a 500 solicitudes por segundo, comenzando en dos nodos backend mientras el pool escalaba: 0 errores de aplicación y 0 backend 5xx. Había 143 errores en el load balancer, pero solo mientras los nodos estaban saturados de CPU. Esa es la señal esperada de "fuera de capacidad", no un bug. La capa de datos obtiene la parte 3, y Valkey regresa en la parte 4. Para la teoría general, consulta la capa de datos bajo carga.
3. CPU del backend: un problema de capacidad
Con los dos primeros arreglados, lo que quedó fue aritmética simple. Un nodo backend de 4 vCPU / 8 GB sirve aproximadamente 65 a 70 solicitudes por segundo de la mezcla real de API a CPU completa, en aproximadamente 59 ms de CPU por solicitud. Cada decisión de capacidad posterior se construye sobre ese número.
Este es el único de los tres cuellos de botella que más servidores realmente resuelven, e incluso aquí la sincronización es la trampa: el primer nodo extra llegó aproximadamente siete minutos después de que CPU alcanzó 99%. Cómo hicimos que la capacidad llegara temprano es el tema de la parte 2.
Así que el orden fue un limitador (código), luego conexiones Valkey (dimensionamiento de capa de datos), luego CPU del backend (capacidad). Si hubiéramos comenzado agregando servidores, el limitador aún habría dicho 429 y Valkey aún habría agotado el tiempo.
Lo que la configuración antigua costaba
Antes de hablar sobre la nueva forma, ayuda saber qué costaba la antigua. Estos son precios de lista mensual de DigitalOcean para la capa web antigua.
Elemento
Costo
Lo que compró
Capa web antigua: frontend, dos backends, worker, un load balancer, dos standbys
aproximadamente $416 al mes
Un sitio en funcionamiento con varios puntos únicos de falla
Dos droplets standby apagados
$112 al mes
Sin disponibilidad: facturados mientras están apagados, minutos para encender
Un nodo backend extra, para comparación
aproximadamente $0.08 por hora
Capacidad que realmente sirve solicitudes
Los $112, un poco más de un cuarto de la capa web, es el número que se me quedó. Compró dos servidores que no podían servir una sola solicitud. Pre-escalar un nodo backend extra cuesta aproximadamente $0.08 por hora, así que elevar la capacidad para una noche cuesta centavos. Pagar todo el mes por capacidad que está apagada es la forma más cara de sentirse seguro.
Una nota honesta: los $416 cubren solo la capa web, no las bases de datos, Valkey o almacenamiento, así que no se puede comparar con una factura completa. En la parte 5 comparo lo semejante con lo semejante.
El método, y el plan para la serie
El patrón que repetimos a lo largo del proyecto es el que acabas de ver: medir, encontrar el cuello de botella, entender la causa, cambiar la arquitectura, probar de nuevo, y medir de nuevo. Agregar servidores no es arquitectura. La arquitectura es elegir qué capa cambia, y por qué.
Aquí es cómo encajan las cinco partes.
Parte
Capa
Lo que verás
1 (esta parte)
Punto de partida
El problema empresarial, la configuración antigua, la definición de alta disponibilidad, los primeros cuellos de botella y el costo antiguo
La noche real del 7 de octubre, pruebas de carga y soak, y la arquitectura final
También consideramos ideas que no construimos, y etiqueté cada una. Un load balancer para ambas capas fue Considerado, luego Rechazado. Un "standby caliente listo en segundos" fue Considerado, pero los pools de autoscaling no tienen pool caliente, así que usamos capacidad de repuesto ya sirviendo más pre-scaling. Kubernetes fue Considerado para después y no está implementado.
No todo está terminado, y lo diré. El servidor worker único es un punto único de falla conocido; arreglarlo es Planeado, no hecho (parte 4). Una prueba formal de soak de varias horas también es Planeada (parte 5).
Lo que queríamos lograr
El objetivo como una lista de verificación, con la parte donde se construye o prueba cada línea.
Capas frontend y backend detrás de load balancers, con al menos dos nodos cada una (parte 2).
Nuevos servidores que se unan y salgan por su cuenta, sin nodos editados a mano (parte 2).
Capacidad elevada antes de un examen, con autoscaling como la red de seguridad (partes 2 y 5).
Una base de datos con standby que cuesta menos por mes que un nodo grande (parte 3).
Nodos web sin estado local, para que los envíos sobrevivan a cualquier nodo muriendo (parte 4).
Deploys y scale-in que los estudiantes no pueden ver (partes 2 y 4).
Un modelo de capacidad construido a partir de datos reales de examen (parte 5).
Una factura que podamos explicar línea por línea (parte 5).
Lo que aprendimos
Escribe "alta disponibilidad" en términos de fallas y eventos antes de comprar nada. Una definición que puedas probar con una pregunta es mejor que un eslogan.
Un standby apagado es un costo, no disponibilidad. El nuestro era $112 al mes sin protección real.
El cuello de botella se mueve. Arreglar el primero revela el siguiente, así que mide de nuevo después de cada cambio.
Diferentes cuellos de botella necesitan diferentes arreglos: código (el limitador), dimensionamiento de capa de datos (Valkey) y capacidad (CPU del backend). Solo el último se resuelve con más servidores.
Los nodos stateless son el precio de admisión. Sin ellos, los pasos posteriores no habrían sido seguros.
Siguiente, en la parte 2, construimos la capa de aplicación: dos load balancers, dos pools de autoscaling, pre-scaling, y un método de rollout aprendido de un pequeño blip 503.