Migraciones de base de datos con ayuda de un LLM: detectar operaciones peligrosas y probar ida y vuelta

Cómo usar un LLM para escribir migraciones de esquema sin poner en riesgo los datos de producción: pedir migración de subida y de bajada, detectar en código operaciones destructivas o que bloquean tablas, y probar la ida y vuelta con datos de muestra antes de desplegar.

Bases de datos con flechas de migración

«Renombra la columna nombre de la tabla clientes a razon_social» parece una petición inocente. Un modelo puede responder con ALTER TABLE clientes RENAME COLUMN nombre TO razon_social, que es correcto y rompe al instante todas las versiones de la aplicación que siguen leyendo nombre durante el despliegue. O puede responder con un DROP COLUMN y un ADD COLUMN, que además borra los datos. El SQL de una migración suele ser corto; sus consecuencias en una base de datos con millones de filas y una aplicación en marcha no lo son. Un LLM ahorra tiempo al escribirlas, siempre que el código revise lo que el modelo no puede ver.

Qué pedir al modelo

Dale el esquema actual, el cambio deseado, el motor y versión de la base de datos, y el tamaño aproximado de las tablas afectadas. Pide siempre dos partes: la migración de subida y la de bajada, que deshace la anterior. Pide también que señale si el cambio requiere varias fases para no romper la aplicación durante el despliegue. Un renombrado seguro, por ejemplo, se hace en pasos: añadir la columna nueva, escribir en ambas, copiar los datos, cambiar las lecturas, y solo después eliminar la antigua.

Revisar en código las operaciones peligrosas

import re

REGLAS = [
    (r"\bDROP\s+(TABLE|COLUMN)\b", "borra datos: confirma que hay copia y que ya no se usa"),
    (r"\bTRUNCATE\b", "borra todas las filas"),
    (r"\bDELETE\s+FROM\s+\w+\s*;", "DELETE sin WHERE"),
    (r"\bUPDATE\s+\w+\s+SET\b(?![^;]*\bWHERE\b)", "UPDATE sin WHERE"),
    (r"\bRENAME\s+COLUMN\b", "rompe el código que use el nombre anterior durante el despliegue"),
    (r"\bALTER\s+COLUMN\s+\w+\s+(TYPE|SET\s+DATA\s+TYPE)\b", "cambio de tipo: puede reescribir la tabla y bloquearla"),
    (r"\bADD\s+COLUMN\b[^;]*\bNOT\s+NULL\b(?![^;]*\bDEFAULT\b)", "NOT NULL sin DEFAULT: falla con filas existentes"),
    (r"\bCREATE\s+(UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)", "índice sin CONCURRENTLY: bloquea escrituras en PostgreSQL"),
]

def revisar_migracion(sql):
    avisos = []
    for patron, motivo in REGLAS:
  for m in re.finditer(patron, sql, re.IGNORECASE):
      linea = sql[:m.start()].count("\n") + 1
      avisos.append((linea, m.group(0).strip(), motivo))
    return avisos

Las reglas son específicas de cada motor; las de este ejemplo están pensadas para PostgreSQL. Un aviso no prohíbe la operación: obliga a que una persona la apruebe sabiendo lo que hace. Herramientas especializadas de análisis de migraciones cubren muchos más casos y conviene usarlas si existen para tu motor.

Probar la ida y vuelta

La prueba mínima es aplicar la subida, la bajada y otra vez la subida sobre una copia del esquema con datos de muestra, y comprobar que el esquema y los datos vuelven a su estado:

import sqlite3

def esquema(con):
    return sorted(r[0] for r in con.execute(
  "SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'"))

def probar_ida_y_vuelta(esquema_inicial, datos, subida, bajada):
    con = sqlite3.connect(":memory:")
    con.executescript(esquema_inicial + datos)
    antes_esquema = esquema(con)
    filas_antes = {t: con.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
             for (t,) in con.execute("SELECT name FROM sqlite_master WHERE type='table'")}
    con.executescript(subida)
    con.executescript(bajada)
    problemas = []
    if esquema(con) != antes_esquema:
  problemas.append("la bajada no restaura el esquema")
    for t, n in filas_antes.items():
  if con.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] != n:
      problemas.append(f"la tabla {t} ha cambiado de número de filas")
    try:
  con.executescript(subida)        # debe poder aplicarse otra vez
    except sqlite3.Error as e:
  problemas.append(f"la subida no se puede volver a aplicar: {e}")
    return problemas

El ejemplo usa SQLite en memoria para que se pueda ejecutar sin servidor; en la práctica, la prueba se hace con el mismo motor y versión que producción, idealmente en integración continua y con una copia anonimizada de los datos reales o una muestra representativa. Mide también cuánto tarda la migración con un volumen de datos parecido al real: una migración que tarda 2 segundos con mil filas puede tardar una hora con cien millones.

Algunas bajadas no pueden restaurar datos: si la subida borra una columna, la bajada recrea la columna vacía. La prueba lo detecta. En esos casos, documenta que la migración no es reversible y asegura una copia de seguridad antes de aplicarla.

Errores frecuentes y limitaciones

Dar al modelo solo el cambio, sin el esquema ni el tamaño. Sin contexto, propone la forma más directa, que suele ser la que bloquea o rompe la compatibilidad.

Probar solo la subida. La bajada se escribe con prisa y se descubre que falla el día que hace falta revertir.

Migraciones de datos dentro de la migración de esquema. Actualizar millones de filas en la misma transacción que un cambio de esquema bloquea la tabla. Separa las actualizaciones de datos en procesos por lotes.

¿Puede el modelo generar la migración comparando dos versiones del modelo de datos?

Las herramientas de migración de los propios frameworks (Django, Alembic, Prisma y otras) ya generan el diff de esquema de forma determinista. El modelo aporta más en lo que esas herramientas no hacen: dividir en fases seguras, escribir migraciones de datos y explicar los riesgos.

Flujo recomendado

Esquema, motor y tamaños como contexto, migración de subida y bajada con plan por fases si hace falta, revisión automática de operaciones peligrosas con aprobación humana de cada aviso, prueba de subida, bajada y subida con datos de muestra en el mismo motor que producción, medición del tiempo con volumen realista y copia de seguridad antes de aplicar.

// Quién lo escribe

Creador de El Oráculo IA. Desde México crea y monetiza sitios web, programa en PHP y Python y usa herramientas de IA a diario en su trabajo. Aquí explica la inteligencia artificial con datos, fuentes y sus límites. Gamer y rodeado de gatos.