Saltar al contenido
Volver al blog

Revisado el 29 de septiembre de 2026

Cambiar la base de datos sin parar el servicio: expandir y contraer

La misma tabla en tres momentos: primero con la columna vieja, después con la vieja y la nueva a la vez y al final solo con la nueva

Renombrar una columna es una línea de SQL y tira el producto durante el despliegue. El motivo es sencillo y se olvida siempre: durante unos minutos conviven el código viejo y el nuevo. El viejo pregunta por una columna que ya no existe. El patrón que lo evita se llama expandir y contraer y tiene tres pasos. El problema real es que el tercero casi nunca se ejecuta.

Por qué se rompe

Un despliegue no es instantáneo. Da igual cómo lo hagas: durante un rato hay instancias con la versión antigua sirviendo peticiones y otras con la nueva.

Si la migración se aplica antes, el código viejo consulta una columna que ya no está. Si se aplica después, el código nuevo consulta una que todavía no existe. No hay orden correcto, porque el problema es que el cambio no es compatible con las dos versiones a la vez.

De ahí sale la regla que gobierna todo lo demás:

Toda migración tiene que ser compatible con el código que ya está en producción.

Los tres pasos

1 · Expandir. Se añade lo nuevo sin tocar lo viejo. Columna nueva, nullable, con su índice si lo necesita. En este punto el código antiguo sigue funcionando porque no se ha quitado nada.

2 · Migrar. El código nuevo escribe en las dos y lee de la nueva. Se rellena la columna nueva con los datos históricos, por lotes. Cuando el relleno termina y todo el código en producción es el nuevo, la columna vieja ya no se lee: solo se escribe por seguridad.

3 · Contraer. Se deja de escribir en la vieja, se despliega y en una migración posterior se borra. Nunca en la misma: si algo va mal y hay que volver atrás, la columna vieja tiene que seguir ahí.

El paso 3 va en una migración posterior, nunca en la misma: si hay que volver atrás, la columna vieja tiene que seguir ahí.

Tres despliegues para renombrar una columna. Parece desproporcionado, pero es exactamente lo que cuesta no tener una ventana de parada.

El paso que nadie ejecuta

Aquí está el fallo real y la tecnología no tiene nada que ver.

Los pasos 1 y 2 son urgentes: sin ellos la funcionalidad nueva no sale. El paso 3 no lo pide nadie, no se ve y siempre hay algo más importante. Así que se queda.

A los dos años tienes columnas que nadie lee, columnas duplicadas donde una está a medio rellenar y disparadores de sincronización que ya no sincronizan nada pero siguen ejecutándose en cada escritura. El espacio perdido es lo de menos: nadie sabe cuál de las dos columnas es la buena y cada persona nueva que entra pregunta lo mismo.

Lo que funciona, por orden de eficacia:

  • Crear la tarea de contraer en el mismo momento que la de expandir, con fecha. No al terminar.
  • Anotar en la propia migración qué la sustituye y cuándo se puede borrar. Un comentario en el fichero, que es donde va a mirar quien lo herede.
  • Revisar las pendientes una vez al trimestre. Media hora que evita la arqueología.

Las operaciones que bloquean

La otra mitad del problema: hay cambios que, aunque sean compatibles, paran la tabla mientras se ejecutan. En una tabla grande eso es una caída, aunque el SQL parezca inocente.

OperaciónQué pasa
Añadir columna nullable sin valor por defectoInstantáneo en Postgres moderno
Añadir columna con valor por defectoInstantáneo desde Postgres 11 si el valor es constante; con uno volátil, como la hora actual, sigue reescribiendo la tabla entera
Crear un índiceBloquea las escrituras salvo que se cree de forma concurrente
Añadir NOT NULL a una columna existenteRecorre toda la tabla para comprobarlo
Añadir una clave foráneaComprueba todas las filas y bloquea las dos tablas
Cambiar el tipo de una columnaReescribe la tabla

El detalle está en la documentación de ALTER TABLE. Las dos primeras reglas prácticas que salen de esa tabla:

Los índices se crean de forma concurrente. Tarda más y no bloquea. El único inconveniente es que si falla deja el índice inválido y hay que borrarlo y repetir.

Las restricciones se añaden en dos tiempos. Primero se declara sin validar, para que aplique a las filas nuevas; después se valida, que recorre la tabla sin bloquear las escrituras. En una tabla de millones de filas, la diferencia entre hacerlo así y no hacerlo es una parada de varios minutos.

Lo que sigue siendo difícil

  • La transacción larga que bloquea a todos. Una migración esperando a que termine otra operación mantiene su bloqueo en cola y a partir de ahí se para todo lo que toque esa tabla. Conviene poner un tiempo máximo de espera para el bloqueo: mejor que la migración falle rápido a que congele el producto.
  • El backfill masivo. Rellenar diez millones de filas en una sola sentencia genera una transacción enorme y presiona la replicación. Va por lotes, con pausa entre ellos.
  • Los despliegues que no se pueden ordenar. Si dos servicios distintos leen la misma tabla, expandir y contraer necesita coordinarse entre los dos y ahí el patrón se complica de verdad.

Cuándo esto es sobreingeniería

Si tu producto puede parar cinco minutos de madrugada y nadie se entera, para el servicio y haz la migración de una vez. Es más rápido, más simple y menos propenso a fallos que tres despliegues coordinados. Mucha gente monta el patrón completo para un producto que podría permitirse una ventana de mantenimiento perfectamente.

El umbral es concreto: cuando hay usuarios en franjas horarias distintas o cuando una parada significa dinero perdido o una llamada del cliente. Antes de eso, la ventana de mantenimiento es, sin más, la decisión correcta.

Lo que sí conviene siempre, aunque puedas parar: no borrar nada en la misma migración que lo sustituye. Esa regla no cuesta nada. Con ella se vuelve atrás en un minuto; sin ella toca restaurar una copia de seguridad.


Nimboo construye producto entero y lo mantiene después, así que las migraciones de dentro de dos años las hace quien escribió el modelo de datos. Cómo se decide todo esto en la fase de diseño está en SaaS a medida y Vecinly es un producto en producción con clientes que no admiten parada.

← Volver al blog Hablemos del proyecto →