Cómo hacer deploys sin downtime: lo que aprendimos en producción
El deploy a producción debería ser aburrido. Un evento rutinario, sin drama, sin “cerramos por mantenimiento”, sin usuarios que pierden lo que estaban haciendo.
Llegar a ese punto requiere pensar la arquitectura correctamente desde el inicio. Aquí están las estrategias que usamos y cuándo aplica cada una.
El problema con los deploys “ingenuos”
Un deploy típico sin ninguna estrategia especial:
- Detener el servidor actual
- Reemplazar el código
- Iniciar el nuevo servidor
Durante los pasos 1 a 3, la aplicación no responde. Si el inicio tarda 30 segundos, tienes 30 segundos de downtime. Si algo falla en el inicio, el downtime se extiende hasta que alguien lo detecta y actúa.
Para una aplicación interna usada en horario de oficina, esto puede ser aceptable. Para un e-commerce, una plataforma SaaS o cualquier sistema con usuarios activos las 24 horas, no lo es.
Blue-Green deployment: la estrategia más confiable
La idea: mantener dos entornos de producción idénticos (azul y verde). En cualquier momento, uno está activo y el otro está en espera.
Cómo funciona:
- El entorno azul está sirviendo el tráfico actual
- Desplegamos la nueva versión en el entorno verde (que no recibe tráfico)
- Verificamos que el verde funciona correctamente
- Redirigimos el tráfico al verde (cambio instantáneo en el load balancer)
- El azul queda en espera — si hay un problema, revertir es volver a apuntar al azul
Ventaja principal: el rollback es inmediato. Si la nueva versión tiene un bug crítico, volver a la versión anterior toma segundos, no minutos.
Desventaja: necesitas el doble de infraestructura corriendo simultáneamente durante el deploy.
Rolling deployment: para la mayoría de casos
Si tienes múltiples instancias de tu aplicación (horizontal scaling), el rolling deployment actualiza una instancia a la vez mientras las otras siguen sirviendo tráfico.
Cómo funciona:
- Tienes 4 instancias corriendo la versión A
- Detenes la instancia 1, la actualizas a versión B, la vuelves a iniciar
- El load balancer solo envía tráfico a las instancias saludables
- Repites para las instancias 2, 3 y 4
Ventaja: no necesitas infraestructura extra. Simple de implementar con orquestadores modernos (Kubernetes, Railway, ECS).
Consideración importante: durante el rolling update, coexisten la versión A y la versión B. Si hiciste un cambio de base de datos incompatible hacia atrás, esto puede generar errores. Las migraciones tienen que ser backwards-compatible.
Migraciones de base de datos: el problema más subestimado
La parte más delicada de los deploys sin downtime es la base de datos. Cambiar una columna de nombre, eliminar una tabla, cambiar un tipo de dato — estas operaciones pueden romper la versión anterior si se ejecutan antes de que todos los servidores estén en la nueva versión.
La regla: las migraciones de base de datos tienen que ser backwards-compatible con la versión anterior del código por al menos un ciclo de deploy.
Patrón para renombrar una columna sin downtime:
- Deploy 1: agrega la columna nueva, el código escribe en ambas columnas
- Deploy 2: el código lee la columna nueva, sigue escribiendo en ambas
- Deploy 3: el código solo usa la columna nueva, elimina la escritura duplicada
- Migración final: elimina la columna vieja
Sí, son más pasos. Pero evitan el downtime y, más importante, evitan pérdida de datos.
Health checks: el prerequisito de todo lo anterior
Ninguna estrategia de deploy sin downtime funciona sin health checks correctos. El load balancer necesita saber cuándo una instancia está lista para recibir tráfico.
Un health check básico responde en /health con un status 200 cuando la aplicación está lista — conexión a la base de datos verificada, caché caliente, dependencias disponibles.
Si el health check falla, el load balancer no envía tráfico a esa instancia. Nunca.
// Ejemplo básico de health check endpoint
app.get('/health', async (req, res) => {
try {
await db.execute('SELECT 1'); // verificar conexión DB
res.json({ status: 'ok', timestamp: new Date().toISOString() });
} catch (error) {
res.status(503).json({ status: 'error', message: 'Database unavailable' });
}
});
Lo que usamos para proyectos en Railway
Railway hace que esto sea considerablemente más simple. Con su sistema de deploys, la nueva versión no recibe tráfico hasta que el health check pasa. Si el deploy falla, el tráfico sigue en la versión anterior automáticamente.
Para la mayoría de proyectos que construimos, esto es suficiente. Para proyectos que requieren zero-downtime absoluto o rollback instantáneo, implementamos blue-green con un proxy de Nginx o Caddy delante.
Si tu aplicación tiene deploys que generan downtime y quieres resolverlo, generalmente es una combinación de infraestructura correcta y migraciones de base de datos bien planificadas. Escríbenos si quieres revisarlo.
Compartir artículo