StackPractices
advanced Por Mathias Paulenko

Plantilla de Runbook de Migración de Datos con Rollback

Usa esta plantilla de runbook para migrar datos de forma segura. Incluye pre-chequeos, pasos de ejecución, rollback y validación post-migración.

Visión General

Las migraciones de datos están entre las operaciones más riesgosas de la ingeniería de software. A diferencia de un despliegue fallido, una migración fallida no se deshace con kubectl rollout undo. Las escrituras que llegaron durante el intento ya quedaron mezcladas en el dataset. Una migración mal ejecutada puede corromper datos de producción, violar requisitos de retención o cumplimiento, y estirar una interrupción mucho más allá de la ventana planificada.

Esta plantilla de runbook estructura la migración en cinco fases verificables (preparación, prueba en seco, ejecución, validación y rollback) con una puerta de decisión go/no-go explícita entre el ensayo y producción. Cópiala en tu repo, rellena los huecos y ensáyala antes del evento real. Para cambios solo de esquema, combínala con la guía de evolución de esquema; para la checklist posterior al corte, usa la plantilla de checklist post-deployment.

Cuándo Usarlo

Usa este runbook cuando:

  • Mueves datos entre versiones o motores de base de datos (MySQL 5.7 a 8.0, PostgreSQL auto-gestionado a Aurora)
  • Divides la base de datos de un monolito en almacenes por servicio
  • Consolidas varias fuentes de datos en un data warehouse o un nuevo primario
  • Ejecutas cambios de esquema tan grandes que exigen reescribir filas existentes
  • Migras entre proveedores de cloud (AWS RDS a GCP Cloud SQL)

No necesitas este runbook para:

  • Migraciones de esquema rutinarias en tablas pequeñas y de bajo tráfico: tu herramienta de migraciones del ORM o Flyway/Liquibase ya cubre ese camino
  • Actualizaciones de servicios gestionados donde el proveedor ejecuta la validación y el rollback por ti (por ejemplo, un despliegue blue/green de RDS)
  • Backfills de solo lectura que simplemente se re-ejecutan si fallan; el valor del runbook está en coordinar escrituras, verificación y rollback bajo presión

Para una guía de estrategias de migración sin downtime antes de llegar a la fase de runbook, consulta Migración de Datos: Estrategias Zero-Downtime que Funcionan.

Antes de Empezar

Antes de comenzar:

  • Backup completo de los sistemas fuente y destino, completado y verificado
  • Script de migración probado con un dataset de volumen similar a producción
  • Ventana de inactividad aprobada por los stakeholders (si aplica)
  • Plan de rollback documentado y probado
  • Monitoreo y alertas configurados tanto para la fuente como para el destino

Plantilla

# Runbook de Migración de Datos: `<Nombre de la Migración>`

## 1. Checklist Pre-Migración

### Sistema Fuente
```bash
pg_dump -h source.db.internal -U admin mydb | gzip > /backups/pre-migration.sql.gz
gunzip -t /backups/pre-migration.sql.gz

## Registrar métricas baseline
psql -h source.db.internal -c "SELECT pg_size_pretty(pg_database_size('mydb'));"
psql -h source.db.internal -c "SELECT COUNT(*) FROM orders;"
psql -h source.db.internal -c "SELECT MAX(updated_at) FROM orders;"
```

| Métrica | Valor | Notas |
|---------|-------|-------|
| Tamaño de base de datos | ______ | |
| Conteos de filas por tabla | ______ | |
| Último timestamp de actualización | ______ | |
| Conexiones activas | ______ | |
| Lag de replicación | ______ | |

### Sistema Destino
- [ ] Esquema de destino creado y coincide con la estructura fuente
- [ ] Índices de destino construidos y validados
- [ ] Capacidad de almacenamiento de destino > 2x el tamaño esperado de los datos
- [ ] Conectividad de red verificada entre fuente y destino
- [ ] Línea base de rendimiento del destino establecida

### Aplicación
- [ ] Feature flags configurados para escritura dual o lectura-post-escritura
- [ ] Código de aplicación desplegado que soporta ambos sistemas, viejo y nuevo
- [ ] Dashboards de monitoreo actualizados con métricas del sistema destino

## 2. Selección de Estrategia de Migración

| Estrategia | Tiempo de Inactividad | Complejidad | Caso de Uso |
|------------|----------------------|-------------|-------------|
| Big Bang | Minutos a horas | Baja | Datasets pequeños (< 100GB), esquema simple |
| Incremental / Batch | Casi cero | Media | Datasets grandes, puede tolerar consistencia eventual |
| Escritura Dual | Cero | Alta | Sistemas en vivo que requieren 100% disponibilidad |
| CDC (Change Data Capture) | Casi cero | Alta | Replicación continua, tiempo de inactividad mínimo |

### Registro de Decisión
**Estrategia seleccionada:** ______

**Justificación:** ______

## 3. Ejecución de la Prueba en Seco

```bash
## Ejecutar la migración sobre una copia de los datos de producción
## NO conectar a sistemas de producción

cp /backups/pre-migration.sql.gz /tmp/dry-run.sql.gz
gunzip /tmp/dry-run.sql.gz

## Ejecutar el script de migración
psql -h target-staging.db.internal -f /tmp/dry-run.sql

## Validar la prueba en seco
./scripts/validate-migration.sh \
  --source source-staging.db.internal \
  --target target-staging.db.internal
```

| Resultado de la Prueba en Seco | Estado |
|--------------------------------|--------|
| Duración | ______ |
| Filas migradas | ______ |
| Errores encontrados | ______ |
| Validación aprobada | [ ] |

**Puerta de Decisión:** solo proceder a producción si la prueba en seco completó sin errores y la validación pasó.

## 4. Ejecución de la Migración en Producción

### Paso 4a: Backup Final
```bash
## Crear un backup point-in-time inmediatamente antes de la migración
aws rds create-db-snapshot \
  --db-instance-identifier source-db \
  --db-snapshot-identifier pre-migration-$(date +%Y%m%d-%H%M%S)
```

### Paso 4b: Detener Escrituras (si se usa Big Bang)
```bash
## Configurar la aplicación a solo lectura
curl -X POST http://app.internal/admin/maintenance-mode

## Verificar que no hay escrituras activas
psql -h source.db.internal -c "SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active';"
```

### Paso 4c: Ejecutar la Migración
```bash
## Registrar la hora de inicio de la migración
MIGRATION_START=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "Migración iniciada: $MIGRATION_START"

## Ejecutar la migración
psql -h target.db.internal -f migration-script.sql 2>&1 | tee migration.log

## Registrar la hora de fin de la migración
MIGRATION_END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "Migración finalizada: $MIGRATION_END"
```

### Paso 4d: Reanudar Escrituras (si aplica)
```bash
## Verificar que el destino es saludable antes de cambiar las escrituras
curl -X POST http://app.internal/admin/target-health-check

## Cambiar la aplicación al destino
curl -X POST http://app.internal/admin/switch-datastore \
  -H "Content-Type: application/json" \
  -d '{"target": "new-database"}'

## Reanudar operaciones normales
curl -X POST http://app.internal/admin/normal-mode
```

## 5. Validación Post-Migración

### Verificación de Conteo de Filas
```sql
-- Comparar conteos de filas de todas las tablas principales
SELECT 'source_orders' as table_name, COUNT(*) as row_count FROM source.orders
UNION ALL
SELECT 'target_orders', COUNT(*) FROM target.orders
UNION ALL
SELECT 'source_users', COUNT(*) FROM source.users
UNION ALL
SELECT 'target_users', COUNT(*) FROM target.users;
```

### Verificaciones de Integridad de Datos
```sql
-- Comparación de checksums para tablas críticas
SELECT 'source', SUM(CHECKSUM(id, amount, created_at)) FROM source.payments
UNION ALL
SELECT 'target', SUM(CHECKSUM(id, amount, created_at)) FROM target.payments;

-- Verificar que no hay valores NULL en columnas requeridas
SELECT COUNT(*) FROM target.orders WHERE customer_id IS NULL;
SELECT COUNT(*) FROM target.orders WHERE created_at IS NULL;
```

### Smoke Tests de Aplicación
```bash
## Flujos críticos de usuario
./scripts/smoke-test.sh --environment=production

## Comparación de línea base de rendimiento
./scripts/performance-test.sh --target=new-db --baseline=old-db
```

| Verificación | Fuente | Destino | Coincide | Tiempo |
|--------------|--------|---------|----------|--------|
| Conteo total de filas | ______ | ______ | [ ] | ______ |
| Conteos a nivel de tabla | ______ | ______ | [ ] | ______ |
| Checksum de pagos | ______ | ______ | [ ] | ______ |
| Verificaciones de constraint NULL | N/A | ______ | [ ] | ______ |
| Smoke tests aprobados | N/A | ______ | [ ] | ______ |
| Rendimiento dentro del 10% | ______ | ______ | [ ] | ______ |

## 6. Procedimiento de Rollback

### Condiciones de Disparo
Hacer rollback si OCURRE CUALQUIERA de los siguientes:
- Tasa de error > 1% después de la migración
- Falla la verificación de integridad de datos
- Degradación de rendimiento > 50%
- Función orientada al cliente rota

### Pasos de Rollback
```bash
## 1. Detener escrituras al destino inmediatamente
curl -X POST http://app.internal/admin/maintenance-mode

## 2. Cambiar la aplicación de vuelta a la fuente
curl -X POST http://app.internal/admin/switch-datastore \
  -d '{"target": "source-database"}'

## 3. Reanudar operaciones en la fuente
curl -X POST http://app.internal/admin/normal-mode

## 4. NO BORRAR los datos del destino hasta resolver la causa raíz
## 5. Documentar todos los hallazgos para el postmortem
```

| Paso de Rollback | Estado | Tiempo |
|------------------|--------|--------|
| Modo de mantenimiento activado | [ ] | ______ |
| Fuente restaurada como primaria | [ ] | ______ |
| Aplicación cambiada | [ ] | ______ |
| Smoke tests aprobados en la fuente | [ ] | ______ |
| Datos del destino preservados | [ ] | ______ |

## 7. Acciones Post-Migración

- [ ] Monitorear el sistema destino durante un mínimo de 24 horas
- [ ] Comparar tasas de error entre antes y después de la migración
- [ ] Validar el backup del sistema destino
- [ ] Actualizar el runbook con la duración real y los problemas encontrados
- [ ] Programar la limpieza de los datos fuente (tras la retención de 30 días)
- [ ] Documentar lecciones aprendidas
- [ ] Cerrar el canal de incidente cuando esté estable

Explicación

El runbook impone una separación entre tres preocupaciones que los equipos suelen mezclar bajo presión: preparación (backups, líneas base, un ensayo), ejecución (la migración en sí) y validación (demostrar que los datos se movieron correctamente). La línea más importante de la plantilla es la puerta de decisión después de la prueba en seco: si el ensayo no terminó limpio con datos a escala de producción, la ejecución en producción no arranca.

Flujo del runbook de migración de datos: la preparación y la prueba en seco alimentan una puerta de decisión go/no-go antes de la ejecución en producción; una validación fallida dispara el rollback a la fuente preservada

Tres detalles que conviene entender antes de adaptarla:

  • La prueba en seco debe usar datos a escala de producción. Un subconjunto de 5 GB en staging no revelará la contención de locks, el lag de replicación ni la presión de memoria que aparecerán con 500 GB. Si no puedes clonar producción, al menos reproduce una porción representativa.
  • Los conteos de filas por sí solos no son validación. Detectan tablas perdidas, no corrupción silenciosa: un varchar truncado, un desplazamiento de zona horaria, un mojibake de charset. Por eso la plantilla combina conteos con checksums en una tabla crítica y verificaciones de NULL en columnas requeridas.
  • El plan de rollback asume que la fuente sigue funcionando. Aquí rollback significa “volver a conectar la app a la fuente”, no “restaurar un backup”. Restaurar un dump de 500 GB en medio de un incidente lleva horas; cambiar un connection string lleva segundos. Mantén la fuente intacta hasta que el nuevo sistema haya sobrevivido tráfico real.

Archivos complementarios: el repo companion de data-migration-runbook-template tiene el runbook como archivo independiente más un script validate-migration.sh que compara los conteos de filas entre fuente y destino.

Cómo Elegir la Estrategia de Migración

La tabla de estrategias dentro de la plantilla es una ayuda de decisión, no decoración. Elige según tres restricciones.

¿Cuánto downtime toleras? Si la respuesta es “ninguno”, el Big Bang queda descartado y la elección es entre escritura dual y replicación basada en CDC. La escritura dual traslada la carga al código de la aplicación (cada ruta de escritura debe alcanzar ambos almacenes y sobrevivir a fallos parciales), mientras que herramientas CDC como Debezium o AWS DMS leen el log de transacciones de la fuente y dejan la app intacta.

¿Qué tan grandes son los datos? Por debajo de ~100 GB con una ventana de mantenimiento, un Big Bang de dump-and-restore tiene las menos piezas móviles. Pasados unos cientos de GB, el tiempo de restauración suele exceder cualquier ventana razonable, así que necesitas replicación incremental más un corte corto.

¿Qué consistencia exige el corte? La consistencia eventual durante la puesta al día vale para analítica; no para procesamiento de pedidos. Si las lecturas tras escritura deben ser exactas, planifica una congelación breve de escrituras en el corte: segundos, no toda la duración de la migración.

Elijas lo que elijas, la forma del runbook no cambia: ensayar, decidir, ejecutar, validar, estar listo para volver atrás.

Variantes

ContextoEnfoqueNotas
Actualización de versión de base de datospg_upgrade / pg_dumpallProbar con OS y versiones de PostgreSQL idénticas
Migración entre proveedores de cloudAWS DMS / GCP Database Migration ServiceValidación integrada, pero monitorear el lag de replicación
Extracción de microserviciosPatrón de escritura dualComplejo, pero sin downtime; requiere cambios en la aplicación
ETL a data warehouseCargas batch con AirflowProgramar durante ventanas de bajo tráfico
NoSQL a SQLScripts de transformación personalizadosEl diseño del esquema es la parte más difícil; probar las queries exhaustivamente

Qué Funciona

  1. Siempre ejecuta una prueba en seco con datos a escala de producción en un entorno aislado
  2. Nunca modifiques la fuente durante la migración; el acceso de solo lectura previene corrupción accidental
  3. Valida incrementalmente: verifica conteos de filas por tabla, no solo totales
  4. Preserva ambos sistemas hasta que la validación esté completa y estable
  5. Documenta la duración real vs. estimada: mejora la planificación futura

Errores Comunes

  1. No probar con el volumen de datos de producción: los datasets pequeños ocultan problemas de rendimiento
  2. Modificar datos de la fuente durante la migración: crea inconsistencias que no pueden reconciliarse
  3. Saltarse el ensayo del rollback: descubres que el rollback no funciona cuando más lo necesitas
  4. Eliminar los datos de la fuente demasiado pronto: la validación puede revelar problemas horas después de la migración
  5. No monitorear el comportamiento de la aplicación: el éxito de la migración de base de datos != éxito de la aplicación

Resolución de Problemas

  • El lag de replicación CDC nunca se pone al día: la fuente genera cambios más rápido de lo que el destino los aplica. Revisa las métricas de tasa de aplicación de la herramienta y las IOPS del destino antes del corte; si el lag crece de forma sostenida, pausa escrituras no esenciales o escala la instancia de replicación.
  • Checksum o conteo de filas que no coincide: primero descarta un objetivo en movimiento: las tablas que siguen recibiendo escrituras nunca coinciden. Vuelve a verificar tras la congelación de escrituras; si la brecha persiste, compara las claves primarias para localizar el rango que falta.
  • Locks de migración en tablas calientes: el DDL o las actualizaciones masivas sobre una tabla en vivo se encolan detrás de transacciones largas. Inspecciona pg_stat_activity buscando bloqueadores antes de empezar, y termina o espera las sesiones idle-in-transaction.
  • El disco del destino se llena a mitad de la migración: los índices, el WAL y el espacio temporal pueden llevar al destino a necesitar 2-3x el tamaño de los datos de la fuente. El chequeo de capacidad 2x de la plantilla existe por esto; no lo saltes.
  • La app conecta pero se comporta mal tras el corte: diferencias de search_path, collation o zona horaria entre fuente y destino. Compara las salidas de SHOW de los parámetros de los que depende tu app durante la prueba en seco.

Notas de Producción

  • Ensaya el rollback en staging al menos una vez; los equipos que se saltan el ensayo descubren permisos faltantes o connection strings obsoletas durante el incidente.
  • Mantén la fuente intacta y en solo lectura hasta que el destino haya servido tráfico real durante toda la ventana de retención del runbook.
  • Vigila las métricas a nivel de aplicación, no solo las de base de datos: una migración puede parecer limpia mientras la tasa de error del checkout sube en silencio.
  • Registra la duración real de cada fase en el runbook; esos números se convierten en la línea base para la estimación de la próxima migración.

Puntos Clave

  • La puerta de decisión tras la prueba en seco es la línea más valiosa del runbook: nunca inicies una migración en producción con un script no probado.
  • El rollback significa volver a conectar la app a una fuente preservada, no restaurar un backup en medio de un incidente.
  • Valida por tabla, no en agregado: un conteo total de filas que coincide puede ocultar la corrupción de una tabla concreta.
  • Trata el runbook rellenado como un activo. Las duraciones reales vs. planificadas son lo que hace fiable la siguiente estimación.

Errores de Adopción

  • Guardar el runbook en un wiki que nadie abre durante los incidentes: déjalo junto al código o en el repo de on-call.
  • Copiar la plantilla sin eliminar las secciones que no aplican: una sección de rollback irrelevante erosiona la confianza en el documento en pleno incidente.
  • Dejar los ______ sin rellenar: un runbook sin hostnames, umbrales y responsables reales es decoración.
  • No actualizar nunca el documento tras la migración: los tiempos reales y los gotchas son la parte más valiosa para la próxima vez.
  • Ejecutarlo sin un responsable con nombre: cuando todos son responsables del runbook, nadie lo mantiene.

Preguntas frecuentes

¿Cómo estimo la ventana de migración?

Ejecuta la prueba en seco con datos a escala de producción y extrapola: duración esperada ≈ duración de la prueba en seco × (tamaño de producción / tamaño de la prueba), más margen para el corte, la validación y la revisión go/no-go. Después suma el tiempo de rollback; si volver atrás lleva 20 minutos, la ventana debe cubrir ejecución, validación y un posible rollback. Si el total excede la ventana aprobada, pasa a una estrategia incremental o CDC en lugar de recortar el margen.

¿Cómo manejo migraciones muy grandes (TB+)?

Usa un enfoque incremental: migra los datos históricos en lotes durante periodos de bajo tráfico y luego usa CDC para el delta final. Herramientas como AWS DMS, Debezium o scripts batch personalizados funcionan bien. Planifica en días o semanas, no en horas.

¿Qué pasa si los esquemas fuente y destino difieren?

Documenta el cambio en el script de migración y valida cada campo modificado. Problemas comunes: conversiones de zona horaria, codificaciones de caracteres, valores enum y columnas nullable. Prueba los casos de borde en la prueba en seco.

¿Cuánto tiempo debo conservar los datos de la fuente después de la migración?

Un mínimo de 30 días para la mayoría de los sistemas. Para datos regulados por cumplimiento, sigue tu política de retención (con frecuencia 90 días o más). Consérvalos hasta que tengas confianza en que la migración es estable y todos los consumidores downstream han verificado sus integraciones.