Gestión de Migraciones en MongoDB: Un Enfoque Robusto para Versionamiento y Rollbacks
Resumen
Este documento describe una estrategia robusta y simple para gestionar migraciones en MongoDB utilizando Go. Incluye la implementación de migraciones hacia adelante (up) y rollbacks (down), y detalla el flujo necesario para revertir cambios en caso de errores o decisiones críticas del equipo. La solución combina claridad en el diseño y herramientas prácticas que se integran fácilmente en pipelines de CI/CD, asegurando una gestión confiable de la estructura de las colecciones. Se adopta un principio clave: durante cualquier migración o rollback, la escritura en la base de datos se detiene, permitiendo que solo las migraciones gestionen los cambios estructurales.
Introducción
En sistemas dinámicos, las migraciones de esquemas son necesarias para mantener la funcionalidad y la adaptabilidad del sistema. Sin embargo, cualquier error en este proceso puede impactar negativamente los servicios en producción. Por ello, es fundamental implementar un flujo claro y robusto que incluya tanto la aplicación de migraciones como su reversión en caso de fallos o decisiones humanas críticas.
Este documento presenta una solución basada en Go para manejar migraciones y rollbacks en MongoDB, describiendo cómo integrarla en el pipeline de CI/CD y cómo ejecutar un rollback de manera efectiva, ya sea por fallos técnicos o decisiones estratégicas.
Principio Clave: Control de Escritura
Durante cualquier migración o rollback:
La escritura en la base de datos se detiene:
- Esto asegura que no se realicen cambios adicionales mientras la estructura está siendo modificada.
Solo las migraciones tienen permiso de escritura:
- Las migraciones aplican cambios estructurales necesarios.
El código actualizado se despliega una vez finalizada la migración:
- Esto sincroniza la lógica de negocio con los cambios estructurales realizados.
Los servicios dependientes se reinician:
- APIs, cronjobs y otros componentes reanudan operaciones con la estructura consistente.
Flujo de Migraciones y Rollbacks
1. Flujo para Migraciones (Up)
El proceso para aplicar cambios estructurales sigue estos pasos:
Detener Escrituras:
- Suspender temporalmente todos los servicios que realizan operaciones de escritura en la base de datos.
Aplicar Migraciones:
- Ejecutar el comando
go run cmd/migrate.go -up
para aplicar las migraciones definidas.
- Ejecutar el comando
Validar la Estructura:
- Verificar que las colecciones tengan los cambios esperados (nuevos campos, índices, etc.).
Desplegar el Código:
- Sincronizar la nueva versión del código con los cambios estructurales.
Reanudar los Servicios:
- Reiniciar APIs, cronjobs y otros servicios dependientes para reanudar la operación.
Este flujo asegura que la estructura de la base de datos esté alineada con el código desplegado.
2. Flujo para Rollbacks (Down)
Rollback por Falla Técnica
Si ocurre un error crítico tras desplegar una nueva versión, el flujo de rollback sigue estos pasos:
Detener Servicios:
- Suspender todas las operaciones de escritura y lectura que dependan de la nueva estructura.
Ejecutar Rollback de Migraciones:
Ejecutar
go run cmd/migrate.go -down
para revertir las migraciones aplicadas. Esto incluye:Eliminar campos adicionales.
Restaurar índices eliminados o modificados.
Revertir Código:
Realizar un
git pull
hacia la versión anterior o específica del código base que sea compatible con la estructura anterior de la base de datos.git checkout <commit-id>
Reiniciar Servicios:
- Levantar los servicios nuevamente con el estado previo del código y la base de datos.
Validar Consistencia:
- Confirmar que la aplicación y la base de datos estén en un estado funcional tras el rollback.
Rollback por Decisión Humana
En ocasiones, aunque el despliegue sea técnicamente exitoso, el equipo puede decidir revertir a una versión anterior debido a problemas no anticipados. Este flujo incluye:
Suspender Servicios:
- Informar al equipo y detener la escritura en la base de datos.
Ejecutar el Rollback:
Aplicar
migrate.go -down
para revertir migraciones.Revertir el código a la versión anterior ejecutando:
git checkout <commit-id>
Documentar la Decisión:
- Registrar los motivos y las medidas correctivas.
Reanudar Servicios:
- Levantar nuevamente APIs y procesos, asegurando consistencia.
Implementación en Go
1. Migraciones en Go
Las migraciones están definidas en archivos separados que incluyen tanto la lógica de avance (up
) como de reversión (down
).
Ejemplo de Migración: 20241201_add_phone_field.go
package main
import (
"context"
"go.mongodb.org/mongo-driver/mongo"
)
func upAddPhoneField(db *mongo.Database) error {
_, err := db.Collection("customers").UpdateMany(
context.Background(),
map[string]interface{}{},
map[string]interface{}{
"$set": map[string]interface{}{
"phone": nil,
},
},
)
return err
}
func downAddPhoneField(db *mongo.Database) error {
_, err := db.Collection("customers").UpdateMany(
context.Background(),
map[string]interface{}{},
map[string]interface{}{
"$unset": map[string]interface{}{
"phone": "",
},
},
)
return err
}
Integración con CI/CD
Para integrar estas migraciones en un pipeline CI/CD, puedes añadir pasos en el proceso de despliegue:
Migraciones hacia adelante:
Detener escrituras.
Aplicar
go run cmd/migrate.go -up
antes del despliegue del código.
Rollback automático o humano:
Suspender servicios.
Revertir con
migrate.go -down
y volver a una versión previa del código.
Ejemplo de pipeline con GitHub Actions:
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20
- name: Install dependencies
run: go mod tidy
- name: Run migrations
run: |
# Detener servicios de escritura antes
go run cmd/migrate.go -up
- name: Deploy application
run: go run cmd/main.go
- name: Rollback on decision
if: always()
run: |
go run cmd/migrate.go -down
git checkout <previous-commit>
Conclusión
Este enfoque ofrece un sistema claro y eficiente para gestionar migraciones y rollbacks en MongoDB. La estrategia de detener escrituras y centralizar los cambios asegura la consistencia en cada despliegue o rollback, mientras que la integración en CI/CD automatiza el proceso, reduciendo riesgos y mejorando la confiabilidad del sistema.