Migración de esquema sin downtime
Por qué renombrar una columna rompe producción, y el patrón de cuatro pasos que convierte una migración en una tarea de martes por la tarde.
Existe un principio que explica prácticamente todo incidente de migración de base de datos, y cabe en una frase:
Durante el deploy, las dos versiones del código corren al mismo tiempo contra la misma base.
Siempre. Rolling update, blue-green, canary: en algún momento existe código viejo y código nuevo vivos simultáneamente, y los dos hablan con el mismo esquema.
Por lo tanto, toda alteración de esquema tiene que ser compatible con la versión anterior del código. Sin excepción.
Qué prohíbe eso
Renombrar una columna. Rompe la versión antigua en el instante en que corre.
Eliminar una columna que todavía se usa. El mismo problema.
Añadir una columna NOT NULL sin valor por defecto. La versión antigua no sabe rellenarla, y su INSERT falla.
Cambiar el tipo de una columna de forma incompatible. La versión antigua escribe un valor que ya no cabe.
Añadir una restricción que el dato existente viola. Falla en el momento o impide escritura legítima.
El patrón expand-contract
También llamado parallel change. Cuatro pasos, y cada uno es un deploy independiente y reversible.
- 1ExpandirColumna nueva, aceptando nulo. Ningún código la usa todavía.
- 2Escribir en las dosEl código nuevo graba en la vieja y en la nueva.
- 3Migrar el históricoLotes pequeños, con pausa, rellenan el pasado.
- 4ContraerLee solo de la nueva. La vieja se va en un deploy siguiente.
Paso 1: Expandir
Añade la estructura nueva sin tocar la vieja. Columna nueva, aceptando nulo, sin restricción. Nada se rompe, porque ningún código la usa todavía.
Paso 2: Escribir en las dos
La versión nueva del código pasa a grabar en la columna vieja y en la nueva. Deploy. Ahora todo dato nuevo existe en los dos sitios, y la versión antigua sigue funcionando leyendo la vieja.
Paso 3: Migrar el histórico
Un proceso por lotes rellena la columna nueva para los registros antiguos. En lotes pequeños, con pausa entre ellos, para no bloquear la tabla ni inundar el log de replicación.
Paso 4: Contraer
Cuando todo esté relleno y ninguna versión antigua siga en el aire, el código pasa a leer solo de la nueva. En un deploy siguiente, no en el mismo, eliminas la columna vieja.
Cuatro deploys en vez de uno. Parece burocrático. Es lo que convierte una migración en una tarea de martes por la tarde, en vez de una ventana de madrugada con el equipo entero despierto.
Y cada paso es reversible por sí solo, que es la propiedad que más importa cuando algo sale mal a las once de la noche.
Qué sale mal con volumen
Una migración que corre en milisegundos en una tabla vacía puede bloquear la tabla veinte minutos en producción, con diez millones de filas.
Los casos clásicos, en Postgres:
CREATE INDEX bloquea la escritura. Usa CREATE INDEX CONCURRENTLY. Es más lento, no bloquea, y
puede fallar dejando un índice inválido que tienes que eliminar manualmente. Conoce ese comportamiento
antes de la noche del deploy.
ALTER TABLE que reescribe la tabla. Cambiar el tipo de una columna normalmente lo reescribe
todo, con lock exclusivo. En versiones modernas, añadir una columna con valor por defecto ya no
reescribe, pero comprueba tu versión.
Añadir una clave foránea valida la tabla entera bajo lock. La salida es añadirla como NOT VALID
y después ejecutar VALIDATE CONSTRAINT, que usa un lock más débil.
Un UPDATE masivo genera un volumen enorme de WAL, retrasa réplicas y puede llenar el disco.
Siempre por lotes, con pausa, monitorizando el lag.
El lock que espera. En Postgres, un ALTER que necesita lock exclusivo entra en la cola, y
mientras espera, bloquea todas las queries que llegan después. Una transacción larga reteniendo un
lock ligero puede, indirectamente, bloquear la tabla entera. Usa un lock_timeout corto e inténtalo
de nuevo, en vez de dejar el comando colgado.
Prueba la migración con volumen
Esto es lo que casi nadie hace y lo que más evita sorpresas.
Ejecuta la migración en una copia con volumen parecido al de producción, aunque sea una vez, antes del deploy grande. Cronométrala. Si tarda veinte minutos, acabas de descubrir que necesitas otro enfoque, y lo descubriste un martes, no de madrugada.
Y prueba también la migración hacia atrás. Muchas herramientas generan el script de reversión automáticamente y nadie lo ejecuta nunca. El día en que lo necesites, no funciona.
Migración de datos contra migración de esquema
Vale la pena separar las dos.
El cambio de esquema es rápido o lento dependiendo del comando, y es reversible en la mayoría de los casos.
El cambio de datos (recalcular un campo, normalizar valores, reprocesar histórico) siempre es lento y frecuentemente irreversible. Trátalo como un proceso por lotes con estado: registra lo que ya se procesó, permite retomar, y hazlo en lotes idempotentes.
El caso del renombrado de tabla
El mismo principio, un nivel más arriba. Quieres renombrar usuarios como cuentas.
Expandir: crea la tabla nueva y un trigger que replica las escrituras de una a la otra. O una vista con el nombre antiguo apuntando a la nueva, si tu base permite escritura en vistas.
Migrar: copia el histórico por lotes.
Contraer: mueve las lecturas, después las escrituras, después elimina la antigua.
La misma coreografía, más piezas. Y la pregunta que vale la pena hacerse antes: ¿ese renombrado entrega valor suficiente para justificar cuatro deploys? A veces la respuesta honesta es no.
Resumiendo
Tres reglas que evitan la mayoría de los incidentes:
- Nunca renombres. Añade, migra, elimina.
- Toda migración es compatible con la versión anterior del código.
- Prueba con volumen antes.
Y una cuarta, que es más cultural: la migración no es un apéndice de la tarea. Es parte del diseño, y merece el mismo cuidado que el código.
Lee esto después
- IA AplicadaPaso 10Qué es de verdad un LLM: token, embedding y atenciónSin misticismo y sin matemáticas: qué hace el modelo, por qué alucina, y cómo eso cambia tus decisiones de arquitectura.Leer artículo
- IngenieríaPaso 9Observabilidad: métrica, log y trace, y qué responde cada unoLos tres pilares no son intercambiables. Cada uno responde una pregunta distinta, y usar el equivocado es el motivo de investigaciones que duran horas.Leer artículo
- IngenieríaPaso 7Los cinco patrones de resiliencia que evitan la caída en cascadaCómo una dependencia secundaria lenta derriba el sistema entero en noventa segundos, y los cinco patrones que lo impiden.Leer artículo