DEV Community

De "se cayó la VM" a "estamos de vuelta"

Diseñando backups y recuperación para un framework de pruebas SQL en Azure

1. Introducción: pensar en el desastre antes de que ocurra

Ningún equipo planea perder datos, pero todos deberían planear la recuperación. Dos métricas ordenan la conversación:

  • RPO: la pérdida máxima de datos tolerable.
  • RTO: el tiempo máximo aceptable para recuperar el servicio.

Estos valores se acuerdan con quien usa el sistema. Para nuestro prototipo los tratamos como objetivos de diseño por definir y validar, no como cifras medidas.

El caso de estudio es un Framework de Pruebas SQL en FastAPI que ejecuta casos contra Oracle y conserva el historial. Usamos SQLite para metadatos e historial y Oracle Free como base objetivo. No usamos MS SQL Server, y queda fuera de este artículo.

2. Arquitectura del despliegue

La aplicación vive en una máquina virtual de Azure orquestada con Docker Compose. Hay tres servicios: Caddy como proxy HTTPS, la API (FastAPI más frontend) y Oracle Free. Solo Caddy publica los puertos 80 y 443. La API y Oracle se comunican en la red interna de contenedores.

Los datos se guardan en volúmenes persistentes, uno para SQLite y otro (oracle_data) para Oracle. El diseño sigue el principio de que los contenedores son efímeros y los volúmenes son el estado. Las migraciones de esquema se versionan con Alembic, lo que permite reconstruir la estructura de forma reproducible. Para los datos hay que restaurarlos desde una copia.

La separación de responsabilidades también ayuda a la seguridad. Los perfiles de conexión no persisten contraseñas de Oracle, así que el backup de SQLite no incluye esas credenciales. Aun así contiene SQL y resultados esperados, por lo que debe cifrarse y tener retención definida.

3. Estrategia por activo

SQLite, el activo crítico

Es la única fuente del historial y de los casos definidos. Aplicamos tres niveles:

  1. Backup lógico en caliente con la API de backup de SQLite (sqlite3.Connection.backup o VACUUM INTO), que genera una copia coherente sin detener la app. Copiar el archivo directamente con la base en uso es riesgoso, sobre todo con WAL.
  2. Backup en frío (detener, copiar, iniciar) para ventanas de mantenimiento o antes de operaciones delicadas.
  3. Copia fuera de la VM, en un almacenamiento separado (Azure Blob Storage, por ejemplo), con verificación PRAGMA integrity_check antes de subir.
bash
# Snapshot coherente desde dentro del contenedor (ruta ilustrativa)
docker exec -i framework_api_prod python -c "
import sqlite3
s=sqlite3.connect('/app/data/framework.db'); d=sqlite3.connect('/tmp/snap.db')
with d: s.backup(d)
"
Enter fullscreen mode Exit fullscreen mode

Oracle, el activo reconstruible en parte

En la demostración, el esquema de pruebas se crea con un script de inicialización. Para conservar datos cargados usamos un backup lógico con Data Pump (expdp) hacia DATA_PUMP_DIR, y copiamos el .dmp fuera del contenedor. Como alternativa en frío, se empaqueta el volumen oracle_data con el contenedor detenido. El esquema y las credenciales del ejemplo son placeholders, y en un entorno real se usa un parfile protegido.

La VM

Las copias o snapshots de disco de la VM protegen ante fallos de infraestructura, pero suelen ser consistentes ante caída, no coherentes por aplicación. Los usamos como capa adicional.

4. Lo que un backup no es: el rol del ROLLBACK

Una confusión frecuente en frameworks de prueba: el rollback no es un backup. Nuestro motor aplica rollback obligatorio a toda operación DML para no dejar datos residuales, y bloquea DDL, que en Oracle provoca confirmaciones implícitas. Eso protege la integridad transaccional de cada prueba. No protege ante pérdida de disco, borrado accidental ni corrupción. Son capas distintas, y solo los backups cubren la última.

5. CI/CD como acelerador de la recuperación

El despliegue con GitHub Actions convierte la infraestructura de aplicación en algo reproducible: imágenes, Compose y configuración están en el repositorio. Ante un desastre, el runbook es corto:

  1. Provisionar la VM (o reutilizar la existente).
  2. Ejecutar el pipeline para levantar Caddy, API y Oracle desde cero.
  3. Restaurar el volumen de SQLite con el último snapshot verificado.
  4. Verificar: endpoint de salud, inicio de sesión y ejecución de un caso de prueba.
  5. Restaurar el esquema/datos de Oracle con Data Pump si corresponde.

Integramos además un paso de backup previo al despliegue, porque las migraciones corren en cada actualización:

yaml
- name: Backup previo
  run: ssh deploy@$VM "/opt/framework/scripts/backup_sqlite.sh"
- name: Deploy
  run: ssh deploy@$VM "cd /opt/framework && docker compose pull && docker compose up -d"
Enter fullscreen mode Exit fullscreen mode

El flujo es ilustrativo y se adapta a cada entorno. Como el código no necesita restauración, el RTO queda dominado por el tiempo de restaurar el volumen de datos.

6. Límites y conclusión

Conviene decirlo con transparencia. Una VM única no ofrece alta disponibilidad, y un fallo puede interrumpir el servicio. Los RPO y RTO deben acordarse y comprobarse con ensayos de restauración. Oracle Free sirve para la demostración, pero no para producción por falta de soporte del fabricante.

Aprendimos que una buena estrategia separa lo que se redespliega (código, imágenes) de lo que se restaura (datos), y que la automatización solo ayuda si las restauraciones se ensayan.

🎥 Demostración en video:

💻 Código: https://github.com/Geraldzvallos/framework-pruebas-sql-publico

Top comments (0)