Migraciones y evolución segura del esquema
Un esquema físico cambia con el producto. Una migración segura debe conservar los datos, permitir que distintas versiones de la aplicación convivan durante el despliegue y ofrecer una estrategia de recuperación.
1. Qué debe contener una migración #
Una migración debería ser:
- Versionada: forma parte del mismo control de versiones que la aplicación.
- Repetible en cada entorno: desarrollo, integración, preproducción y producción siguen el mismo proceso.
- Observable: registra duración, errores y número de filas afectadas.
- Revisable: el SQL y su impacto se pueden inspeccionar antes de ejecutarlo.
- Recuperable: existe un rollback seguro o una estrategia documentada para avanzar con una corrección.
No edites una migración que ya fue aplicada en un entorno compartido. Crea una migración nueva que corrija o complete la anterior.
2. Cambios compatibles e incompatibles #
Suelen ser compatibles:
- Añadir una tabla nueva.
- Añadir una columna que permita
NULL. - Añadir una columna con un valor predeterminado seguro, tras evaluar el comportamiento del motor.
- Crear un índice con la modalidad en línea o concurrente disponible en el SGBD.
Suelen ser incompatibles si se realizan de una vez:
- Renombrar o eliminar una columna usada por la aplicación.
- Cambiar a un tipo más estrecho.
- Añadir
NOT NULLcuando existen filas sin valor. - Reescribir una tabla grande dentro de una transacción prolongada.
La compatibilidad depende de la versión del motor, el volumen de datos y el patrón de tráfico. Verifica siempre el plan concreto.
3. Patrón expandir, migrar y contraer #
Este patrón separa un cambio incompatible en varias versiones desplegables.
Fase 1: expandir #
Añade la nueva estructura sin eliminar la anterior:
1ALTER TABLE clientes ADD COLUMN nombre_completo VARCHAR(200);
La aplicación puede escribir temporalmente tanto en la columna antigua como en la nueva.
Fase 2: migrar los datos #
Completa la columna nueva en lotes:
1UPDATE clientes 2SET nombre_completo = nombre 3WHERE nombre_completo IS NULL;
En una tabla grande, no ejecutes necesariamente una única actualización. Procesa rangos de claves, limita el tamaño de cada transacción y registra el progreso.
Fase 3: cambiar las lecturas #
Despliega una versión que lea exclusivamente la estructura nueva. Comprueba que ya no existen escrituras ni lecturas de la columna anterior.
Fase 4: contraer #
Solo después de completar la transición se elimina lo antiguo:
1ALTER TABLE clientes DROP COLUMN nombre;
La eliminación debe ir en una migración posterior. Así una versión anterior de la aplicación no queda rota durante un despliegue gradual.
4. Añadir una columna obligatoria #
En una tabla con datos, conviene separar el cambio:
- Añadir la columna permitiendo
NULL. - Desplegar la aplicación que la rellena en nuevas escrituras.
- Hacer un backfill de las filas antiguas.
- Verificar que no queda ningún
NULL. - Añadir la restricción
NOT NULL.
1SELECT COUNT(*) AS pendientes 2FROM pedidos 3WHERE moneda IS NULL;
No añadas un valor predeterminado arbitrario solo para superar la migración. Debe ser correcto para todas las filas existentes.
5. Índices en tablas con tráfico #
Crear un índice puede consumir CPU, E/S y espacio, además de bloquear operaciones según el motor. Utiliza las capacidades específicas cuando estén disponibles:
Estas opciones dependen de la edición, versión, tipo de índice y operación. Deben comprobarse en el entorno de destino.
6. Rollback y roll-forward #
No todos los cambios se revierten de forma segura. Después de eliminar una columna, un rollback del esquema no recupera sus datos.
- Rollback: revierte la migración cuando no implica pérdida o ambigüedad.
- Roll-forward: despliega una migración adicional que corrige el problema.
- Restauración: recupera una copia de seguridad o usa recuperación a un punto en el tiempo cuando hubo pérdida de datos.
Antes de una migración destructiva, verifica la copia, el procedimiento de restauración y el tiempo estimado de recuperación.
7. Transacciones y sentencias DDL #
El comportamiento transaccional de DDL varía:
- PostgreSQL permite revertir muchas operaciones DDL dentro de una transacción, con excepciones.
- MySQL provoca confirmaciones implícitas en numerosas operaciones DDL.
- Oracle también realiza confirmaciones implícitas alrededor de DDL.
- SQL Server admite muchas operaciones DDL transaccionales, pero existen límites operativos.
- SQLite soporta DDL transaccional, aunque algunos cambios requieren recrear tablas.
No asumas que envolver cualquier migración en BEGIN garantiza un rollback completo.
8. Verificación antes y después #
Antes de desplegar:
- Estima filas, tamaño, duración y espacio adicional.
- Revisa bloqueos y compatibilidad entre versiones de la aplicación.
- Prueba con una copia representativa y datos realistas.
- Define métricas, alertas y criterio de cancelación.
- Comprueba copias y recuperación.
Después de desplegar:
- Verifica restricciones, índices y recuentos esperados.
- Busca errores de aplicación y consultas lentas.
- Confirma que el backfill terminó.
- Conserva evidencias de la versión aplicada.
- Retira la estructura antigua únicamente cuando ya no tenga consumidores.
Resumen del tema
Conceptos clave #
- Las migraciones deben estar versionadas, ser observables y tener una estrategia de recuperación.
- El patrón expandir–migrar–contraer mantiene compatibilidad durante despliegues graduales.
- Los backfills grandes deben realizarse en lotes y con transacciones controladas.
- Crear índices y modificar tablas puede bloquear o reescribir datos según el motor.
- Un rollback de esquema no recupera automáticamente los datos eliminados.
Qué debes recordar #
La migración más segura es la que separa estructura, movimiento de datos y retirada de compatibilidad en pasos pequeños y verificables.