Files
dtic-DIIAA/docs/ambito/dtic-ADN/A01.P012_Pipeline-Drones-Intercomunicacion.md
T
Ricardo Monla 892019fa22 [A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
Refactorización del pipeline de backups DASUTEN:

1. Flujo simplificado (un solo destino):
   - Backup directo a X:\ (share srv-ns8)
   - Eliminado paso intermedio E:\BK_SQL\sysdasuten\
   - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/)

2. Módulo común DasuExecutor (lib/dasu_executor.rb):
   - Centraliza ejecución PowerShell en dasu-sql4
   - Centraliza verificación de archivos en fenix
   - Elimina código repetido en 6 drones (-52% líneas)

3. Drones refactorizados:
   - dasuten_exportar_srvv-fenix.rb (FULL → X:)
   - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:)
   - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado)
   - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor)
   - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor)
   - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor)

4. Integración BKPs mantenida:
   - T8-T15: Tareas individuales
   - C7: dasuten_full (pipeline semanal)
   - C8: dasuten_diferencial (pipeline diario)

5. Documentación actualizada:
   - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común
   - Lecciones aprendidas agregadas

6. Nuevos archivos:
   - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/)
   - A01.P011/P012: Documentación de drones y pipeline
   - proc_linux.rb, ops.rb: Utilitarios del ecosistema

7. Scripts legacy (para referencia):
   - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb
   - dasuten_upload_srv-ns8-drive.rb
2026-04-14 20:06:06 -03:00

641 lines
23 KiB
Markdown

# A01.P012 — Pipeline de Drones para Backup y Restauración de DASUTEN
> **Estado**: 🟢 Pipeline implementado (2026-04-14)
> **Pertenece a**: [A01 — Ecosistema ADN](./A01_dtic-ADN.md)
> **Relacionado**: [A01.P009 — Evolución Dron ADN](./A01.P009_Evolucion-Dron-ADN.md)
> **Caso de Uso**: [A04.P005 — DASUTEN sin DC](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md)
> **Fecha**: 2026-04-14
> **Responsable**: Lic. Ricardo MONLA
> **Versión**: 2.0 — Con verificación de consistencia (DBCC CHECKDB) de BD
---
## 📈 Progreso
```
Fase 0: ██████████ 100% Diseño de Arquitectura de Pipeline ✅
Fase 1: ██████████ 100% Drones Atómicos Fragmentados ✅
Fase 1.5: ██████████ 100% Orquestador con Intercomunicación JSON ✅
Fase 1.7: ██████████ 100% Implementación Real — Pipeline DASUTEN ✅ (2026-04-14)
Fase 1.8: ██████████ 100% Verificación de Consistencia (DBCC CHECKDB) ✅
Fase 1.9: ██████████ 100% Pipeline Diferencial (Refresco diario) ✅
Fase 2: ░░░░░░░░░░ 0% Persistencia en Bitácora — Outputs en DB
Fase 3: ░░░░░░░░░░ 0% Reintentos Automáticos — Retry con backoff
Fase 4: ░░░░░░░░░░ 0% Condicionales — Skip si output indica "ya hecho"
```
---
## 📋 Resumen Ejecutivo
Este plan documenta el **Pipeline de Drones para Backup y Restauración de DASUTEN** — una implementación real donde múltiples drones atómicos se encadenan para formar el flujo de refresco de base de datos (Fase 8b), comunicándose entre sí mediante **archivos JSON** que actúan como medio de intercambio de contexto.
**Caso de uso:** Refresco de la base de datos `sysdasuten` desde `srvv-fenix` (producción) hasta `dasu-sql4` (nuevo servidor), pasando por Google Drive como intermediario.
**Problema resuelto:** Transferir 1.3 GB de backup entre servidores sin dependencia de Tailscale inestable, usando Google Drive como puente y verificando integridad con DBCC CHECKDB.
**Solución:** Un **orquestador** que:
1. Lanza 6 drones atómicos secuencialmente
2. Lee el output JSON de cada dron completado
3. Inyecta el contexto en el siguiente dron
4. Decide continuar o abortar según el resultado
### Convención de Nombres (desde 2026-04-14)
```
dasuten_<verbo>_<origen-destino>.rb
Ejemplos:
- dasuten_exportar_srvv-fenix.rb (acción en nodo específico)
- dasuten_transferir_srvv-fenix-srv-ns8.rb (transferencia entre nodos)
- dasuten_upload_srv-ns8-drive.rb (upload desde nodo a Drive)
```
---
## 🎯 Objetivo
Implementar un **Pipeline de Drones para el refresco de BD de DASUTEN** que permita:
1. **Fragmentar el flujo de backup** en 6 drones atómicos independientes (1 dron = 1 paso)
2. **Intercomunicar drones** mediante archivos JSON estandarizados (`/tmp/dron_*_output.json`)
3. **Orquestar el refresco completo** con un solo comando desde `srv-ns8`
4. **Registrar trazabilidad** de cada paso en la Bitácora Web
5. **Permitir re-ejecución** de pasos individuales sin re-correr todo el pipeline
6. **Verificar integridad** de la BD restaurada con `DBCC CHECKDB`
---
## 🏗️ Arquitectura del Pipeline
### Diagrama de Flujo (6 Drones Atómicos)
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ORQUESTADOR (orquestador_pipeline.rb) │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Contexto Compartido (Hash Ruby) │ │
│ │ - backup_ruta: ruta del archivo .bak │ │
│ │ - drive_ruta: URL de Google Drive │ │
│ │ - estado_integridad: resultado de DBCC CHECKDB │ │
│ │ - duracion_total: suma de duraciones de drones │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
│ 1. Lanza dron
┌─────────────────┐
│ DRON: │ Nodo: srvv-fenix
│ exportar │ Tarea: BACKUP DATABASE WITH COMPRESSION
│ │ Output: /tmp/dron_export_output.json
└─────────────────┘
│ 2. Orquestador lee JSON, extrae backup_ruta
┌─────────────────┐
│ DRON: │ Nodo: srv-ns8
│ transferir │ Tarea: smbclient //fenix/BK_SQL → /var/tmp/
│ │ Output: /tmp/dron_transfer_output.json
└─────────────────┘
│ 3. Orquestador lee JSON, extrae backup_local
┌─────────────────┐
│ DRON: │ Nodo: srv-ns8
│ upload │ Tarea: rclone copy → Google Drive
│ │ Output: /tmp/dron_upload_output.json
└─────────────────┘
│ 4. Orquestador lee JSON, extrae drive_ruta
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ download │ Tarea: Invoke-WebRequest ← Google Drive
│ │ Output: /tmp/dron_download_output.json
└─────────────────┘
│ 5. Orquestador lee JSON, extrae backup_local
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ restaurar │ Tarea: RESTORE DATABASE (sin CHECKDB)
│ │ Output: /tmp/dron_restore_output.json
└─────────────────┘
│ 6. Orquestador lee JSON, decide verificar
┌─────────────────┐
│ DRON: │ Nodo: dasu-sql4
│ verificar │ Tarea: DBCC CHECKDB + validaciones
│ │ Output: /tmp/dron_verify_output.json
└─────────────────┘
✅ PIPELINE COMPLETADO (6 drones)
```
---
## 🧩 Drones Atómicos del Pipeline (6 drones)
Cada dron es **independiente**, **autocontenido** y **auto-bitácorado**.
### Dron 1: `dasuten_exportar_srvv-fenix.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srvv-fenix` |
| **Tarea** | Generar backup comprimido en SQL Server |
| **Comando** | `BACKUP DATABASE sysdasuten TO DISK = ... WITH COMPRESSION` |
| **Output File** | `/tmp/dron_export_output.json` |
| **Output Clave** | `backup_ruta` |
**Output JSON:**
```json
{
"paso": "export_complete",
"backup_ruta": "E:\\BK_SQL\\sysdasuten_compressed_ADN.bak",
"nodo": "srvv-fenix",
"duracion": 45.2,
"timestamp": "2026-04-14T10:00:00-03:00",
"siguiente_paso": "transferir"
}
```
---
### Dron 2: `dasuten_transferir_srvv-fenix-srv-ns8.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srv-ns8` |
| **Tarea** | Descargar backup desde srvv-fenix vía SMB |
| **Comando** | `smbclient //10.0.10.200/BK_SQL -c "get sysdasuten_compressed_ADN.bak"` |
| **Output File** | `/tmp/dron_transfer_output.json` |
| **Output Clave** | `backup_local` |
**Output JSON:**
```json
{
"paso": "transfer_complete",
"backup_local": "/var/tmp/sysdasuten_compressed_ADN.bak",
"nodo": "srv-ns8",
"duracion": 120.5,
"timestamp": "2026-04-14T10:02:00-03:00",
"siguiente_paso": "upload"
}
```
---
### Dron 3: `dasuten_upload_srv-ns8-drive.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `srv-ns8` |
| **Tarea** | Subir backup a Google Drive vía rclone |
| **Comando** | `rclone copy /var/tmp/... rmonla-GDrive:drive_bkps-dasu/...` |
| **Output File** | `/tmp/dron_upload_output.json` |
| **Output Clave** | `drive_ruta` |
**Output JSON:**
```json
{
"paso": "upload_complete",
"drive_ruta": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
"drive_url": "https://drive.google.com/drive/folders/1Tgrn2QYyWf0v0eyY1bSGCLlhiqhtIA2n",
"nodo": "srv-ns8",
"duracion": 180.5,
"timestamp": "2026-04-14T10:05:00-03:00",
"siguiente_paso": "download"
}
```
---
### Dron 4: `dasuten_download_drive-dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Descargar backup desde Google Drive vía HTTP |
| **Comando** | `Invoke-WebRequest -Uri "https://drive.usercontent.google.com/download?id=..."` |
| **Output File** | `/tmp/dron_download_output.json` |
| **Output Clave** | `backup_local` |
**Output JSON:**
```json
{
"paso": "download_complete",
"backup_local": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
"drive_origen": "rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_100500.bak",
"nodo": "dasu-sql4",
"duracion": 150.3,
"timestamp": "2026-04-14T10:08:00-03:00",
"siguiente_paso": "restaurar_backup"
}
```
---
### Dron 5: `dasuten_restaurar_dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Restaurar backup (solo RESTORE, sin CHECKDB) |
| **Comando** | `RESTORE DATABASE ... WITH REPLACE, MOVE ...` |
| **Output File** | `/tmp/dron_restore_output.json` |
| **Output Clave** | `estado_integridad=PENDING_CHECKDB` |
**Output JSON:**
```json
{
"paso": "restore_complete",
"backup_ruta": "F:\\BACKUP\\sysdasuten_compressed_ADN.bak",
"nodo": "dasu-sql4",
"duracion_restore": 0.23,
"estado_integridad": "PENDING_CHECKDB",
"timestamp": "2026-04-14T10:10:00-03:00",
"siguiente_paso": "verificar_integridad"
}
```
---
### Dron 6: `dasuten_verificar-integridad_dasu-sql4.rb`
| Campo | Valor |
|-------|-------|
| **Nodo** | `dasu-sql4` |
| **Tarea** | Verificar integridad con DBCC CHECKDB |
| **Comando** | `DBCC CHECKDB('sysdasuten') WITH NO_INFOMSGS, ALL_ERRORMSGS` |
| **Output File** | `/tmp/dron_verify_output.json` |
| **Output Clave** | `estado_integridad`, `pipeline_completo` |
**Output JSON:**
```json
{
"paso": "verify_complete",
"nodo": "dasu-sql4",
"duracion_checkdb": 248.56,
"estado_integridad": "OK",
"paginas_verificadas": 1186793,
"timestamp": "2026-04-14T10:14:00-03:00",
"pipeline_completo": true
}
```
---
## 🎼 Orquestador: `orquestador_pipeline.rb`
El orquestador es el **cerebro** del pipeline. No ejecuta tareas directamente, sino que:
1. **Lanza drones** secuencialmente vía `dron lanzar`
2. **Lee outputs JSON** de cada dron completado
3. **Inyecta contexto** en el siguiente dron (vía ENV o argumentos)
4. **Decide continuar** o abortar según exito/fracaso
5. **Registra estado** en la Bitácora Web
### Código del Orquestador (simplificado)
```ruby
#!/usr/bin/env ruby
# adn/tools/cli/drones/orquestador_pipeline.rb
ESTADOS = {
exportar: {
script: 'dasuten_exportar.rb',
nodo: 'srvv-fenix',
nota: '📤 Exportar backup DASUTEN',
output_file: '/tmp/dron_export_output.json',
siguiente: :upload
},
upload: {
script: 'dasuten_upload_drive.rb',
nodo: 'srv-ns8',
nota: '☁️ Upload a Google Drive',
output_file: '/tmp/dron_upload_output.json',
siguiente: :download
},
# ... más pasos
}.freeze
def lanzar_dron(script, nodo, nota, timeout)
cmd = "#{ADN_RUN} dron lanzar --nota #{nota} --nodo #{nodo} --timeout #{timeout} -- ruby #{script}"
exito = system(cmd)
if exito
# Leer output del dron
output_file = ESTADOS[nodo.to_sym][:output_file]
resultado = JSON.parse(File.read(output_file)) if File.exist?(output_file)
return true, resultado
else
return false, {}
end
end
# Ejecutar pipeline
paso_actual = :exportar
contexto = {}
loop do
break unless ESTADOS[paso_actual]
config = ESTADOS[paso_actual]
exito, resultado = lanzar_dron(config[:script], config[:nodo], config[:nota], timeout)
unless exito
puts "❌ Pipeline fallido en paso: #{paso_actual}"
exit 1
end
contexto.merge!(resultado)
if config[:siguiente]
paso_actual = config[:siguiente]
else
puts "✅ Pipeline completado!"
break
end
end
```
---
## 🚀 Uso del Pipeline
### Ejecutar Pipeline Completo
```bash
# Pipeline completo (todos los pasos encadenados)
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN 8b" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb
```
### Ejecutar Drones Individualmente
```bash
# Paso 1: Exportar backup
./adn/tools/run dron lanzar --nota "Exportar backup" --nodo srvv-fenix -- \
ruby adn/tools/cli/drones/dasuten_exportar.rb
# Paso 2: Upload a Drive
./adn/tools/run dron lanzar --nota "Upload Drive" --nodo srv-ns8 -- \
ruby adn/tools/cli/drones/dasuten_upload_drive.rb
# Paso 3: Download desde Drive
./adn/tools/run dron lanzar --nota "Download Drive" --nodo dasu-sql4 -- \
ruby adn/tools/cli/drones/dasuten_download_drive.rb
# Paso 4: Restaurar backup
./adn/tools/run dron lanzar --nota "Restaurar backup" --nodo dasu-sql4 -- \
ruby adn/tools/cli/drones/dasuten_restaurar.rb
```
### Ejecutar desde un Paso Específico
```bash
# Comenzar desde upload (saltear export)
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb --paso upload
```
### Dry-Run (Simulación)
```bash
# Ver qué haría sin ejecutar
./adn/tools/run dron lanzar --nota "Pipeline DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline.rb --dry-run
```
---
## 📊 Ventajas de esta Arquitectura
| Ventaja | Descripción |
|---------|-------------|
| **🧩 Atomicidad** | Cada dron hace UNA cosa. Fácil de testear, debuggear, reemplazar. |
| **🔗 Intercomunicación** | JSON como contrato entre drones. Lenguaje-agnóstico. |
| **🔄 Re-ejecución** | Si falla el paso 3, re-ejecutar solo paso 3 (no todo el pipeline). |
| **👁️ Observabilidad** | Cada dron se registra individualmente en Bitácora Web. |
| **🛡️ Tolerancia a Fallos** | Si un dron falla, el pipeline se detiene (fail-fast). |
| **📈 Escalabilidad** | Agregar nuevos pasos = agregar nuevo dron + registrar en ESTADOS. |
---
## 📁 Estructura de Archivos
```
adn/tools/cli/drones/
├── orquestador_pipeline.rb # Orquestador principal
├── dasuten_exportar.rb # Dron 1: Exportar backup
├── dasuten_upload_drive.rb # Dron 2: Upload a Drive
├── dasuten_download_drive.rb # Dron 3: Download desde Drive
└── dasuten_restaurar.rb # Dron 4: Restaurar backup
/tmp/
├── dron_export_output.json # Output del dron 1
├── dron_upload_output.json # Output del dron 2
├── dron_download_output.json # Output del dron 3
└── dron_restore_output.json # Output del dron 4
```
---
## 🔮 Futuras Mejoras (Fases Pendientes)
### Fase 2: Persistencia en Bitácora
Actualmente los outputs JSON son efímeros (`/tmp/`). La **Fase 2** persistirá los outputs en la Bitácora Web:
```ruby
# En dron_db.rb
CREATE TABLE drone_outputs (
id SERIAL PRIMARY KEY,
dron_id VARCHAR(50) REFERENCES drones(dron_id),
output JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- El orquestador puede leer outputs de la DB en lugar de archivos
output = DronDB.obtener_output(dron_id)
```
### Fase 3: Reintentos Automáticos
```ruby
# Orquestador con retry
def lanzar_dron_con_retry(script, nodo, nota, timeout, max_retries: 3)
max_retries.times do |intento|
exito, resultado = lanzar_dron(script, nodo, nota, timeout)
return true, resultado if exito
puts "⚠ Dron falló (intento #{intento + 1}/#{max_retries})"
sleep(2 ** intento) # Backoff exponencial
end
return false, {}
end
```
### Fase 4: Condicionales de Skip
```ruby
# Skip si ya está hecho
def deberia_ejecutar?(paso, contexto)
case paso
when :upload
# Skip si ya hay backup reciente en Drive
!backup_reciente_en_drive?(contexto[:backup_ruta])
when :restaurar
# Skip si BD ya está actualizada
!bd_actualizada?(contexto[:backup_local])
else
true
end
end
```
---
## 📝 Lecciones Aprendidas
### ✅ Lo que Funcionó
| Lección | Descripción |
|---------|-------------|
| **JSON como contrato** | Simple, legible, lenguaje-agnóstico. Funciona mejor que DB compartida. |
| **Outputs en /tmp** | Fácil de limpiar, no requiere permisos especiales. |
| **Orquestador stateless** | El estado está en los JSONs, no en el orquestador. |
| **Drones auto-bitácora** | Cada dron se registra individualmente en la Bitácora Web. |
| **Google Drive como intermediario** | Elimina dependencia de Tailscale inestable. Download HTTP directo desde dasu-sql4. |
| **Base64 encoding para PowerShell** | Evita problemas de escaping UTF-8 al transportar scripts sobre SSH. |
| **sshpass + ProxyCommand** | Patrón efectivo para acceder a VMs detrás de relay (dasu-sql4 detrás de srv-dasu). |
### 📊 Resultados del Pipeline DASUTEN (2026-04-14)
| Métrica | Valor |
|---------|-------|
| **Archivo transferido** | 1.3 GB (`sysdasuten_compressed_ADN.bak`) |
| **Tiempo total de pipeline** | ~35 minutos |
| **Upload a Drive** | 29 minutos (1736s) — limitado por ancho de banda |
| **Download HTTP** | ~5 minutos — con confirmación de virus manejada |
| **Restore SQL** | 0.23 segundos — MOVE instantáneo |
| **DBCC CHECKDB** | 248.56 segundos — **Integridad: OK** |
| **Throughput efectivo** | ~650 KB/s (limitado por subida a Drive) |
### ⚠️ Lo que Requiere Atención
| Lección | Descripción |
|---------|-------------|
| **Limpieza de archivos** | Los JSONs en `/tmp/` deben limpiarse post-pipeline. |
| **Timeouts por dron** | Cada dron debe tener timeout individual (no todos duran lo mismo). |
| **Manejo de errores** | El orquestador debe capturar stderr de cada dron para debugging. |
### 🔧 Desafíos Superados
| Desafío | Solución |
|---------|----------|
| **Virus confirmation page** | Parseo de HTML con regex PowerShell para extraer `id`, `uuid`, `confirm` |
| **dasu-sql4 sleep mode** | Acceso vía relay SSH desde srv-dasu con `sshpass -e` + ProxyCommand |
| **PowerShell escaping** | Base64 encoding del script completo + heredoc con `'PSCMD'` (sin interpolación) |
| **sqlcmd no disponible en PATH** | Usar ruta completa: `C:\Program Files\...\sqlcmd.exe` |
---
## 🔗 Referencias
- **Ámbito**: [A01_dtic-ADN.md](./A01_dtic-ADN.md)
- **Evolución Dron**: [A01.P009_Evolucion-Dron-ADN.md](./A01.P009_Evolucion-Dron-ADN.md)
- **Drones Ops**: [A01.P011_Drones-Operaciones-Infra.md](./A01.P011_Drones-Operaciones-Infra.md)
- **Caso de Uso**: [A04.P005_DASUTEN-sin-DC.md](../dtic-DASUTEN/A04.P005_DASUTEN-sin-DC.md) — Pipeline implementado para Fase 8b
---
## 📊 Estado del Pipeline DASUTEN (6 drones atómicos)
| Paso | Estado | Dron | Nodo | Output | Duración |
|------|--------|------|------|--------|----------|
| **8b.1** | ✅ Completado | `dasuten_exportar_srvv-fenix.rb` | srvv-fenix | `backup_ruta` | 44.8s |
| **8b.2** | ✅ Completado | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | `backup_local` | ~2 min |
| **8b.3** | ✅ Completado | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | `drive_ruta` | 1736s (29 min) |
| **8b.4** | ✅ Completado | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | `backup_local` | ~5 min |
| **8b.5** | ✅ Completado | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=PENDING` | 0.23s |
| **8b.6** | ✅ Completado | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `estado_integridad=OK` | 248.56s |
**Pipeline completado exitosamente el 2026-04-14 a las 17:21.**
### Resultados Finales
- **Backup comprimido:** 1.3 GB (`sysdasuten_compressed_ADN.bak`)
- **Google Drive URL:** `rmonla-GDrive:drive_bkps-dasu/sysdasuten_compressed_ADN_20260414_*.bak`
- **Download HTTP directo:** Con confirmación de virus manejada (formulario HTML parsing)
- **Restore:** 0.23 segundos (MOVE a F:\DATA y F:\LOG)
- **DBCC CHECKDB:** 248.56 segundos — **Integridad: OK** (1,186,793 páginas verificadas, sin errores)
- **Duración total del pipeline:** ~35 minutos
---
## 🔄 Pipeline Diferencial (Refresco Diario)
Para refrescos diarios de la base de datos, se recomienda usar **backup diferencial** que solo transfiere los cambios desde el último backup completo.
### Ventajas del Pipeline Diferencial
| Métrica | Completo | Diferencial |
|---------|----------|-------------|
| **Tamaño backup** | 1.3 GB | ~50-100 MB* |
| **Tiempo export** | 45 seg | ~5-10 seg |
| **Tiempo upload** | 29 min | ~2-3 min |
| **Tiempo download** | ~5 min | ~30 seg |
| **Tiempo total** | ~35 min | ~5-10 min |
\* Depende de la cantidad de cambios diarios
### Drones del Pipeline Diferencial
| Paso | Dron | Nodo | Tarea |
|------|------|------|-------|
| **1** | `dasuten_exportar_diferencial_srvv-fenix.rb` | srvv-fenix | `BACKUP DATABASE ... WITH DIFFERENTIAL` |
| **2** | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | SMB → `/var/tmp/` |
| **3** | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | rclone → Google Drive |
| **4** | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | HTTP ← Google Drive |
| **5** | `dasuten_restaurar_diferencial_dasu-sql4.rb` | dasu-sql4 | `RESTORE ... WITH DIFFERENTIAL` + `RECOVERY` |
| **6** | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | `DBCC CHECKDB` |
### Orquestador Diferencial
```bash
# Ejecutar pipeline diferencial completo
./adn/tools/run dron lanzar --nota "Refresco Diario DASUTEN" -- \
ruby adn/tools/cli/drones/orquestador_pipeline_diferencial.rb
```
### Estrategia Recomendada
| Día | Tipo de Backup | Duración Estimada |
|-----|----------------|-------------------|
| **Lunes** | Completo | ~35 min |
| **Martes** | Diferencial | ~5-10 min |
| **Miércoles** | Diferencial | ~5-10 min |
| **Jueves** | Diferencial | ~5-10 min |
| **Viernes** | Diferencial | ~5-10 min |
| **Sábado** | Diferencial | ~5-10 min |
| **Domingo** | — (sin cambios) | — |
### Consideraciones Importantes
1. **El backup diferencial se basa en el último backup completo**, no en el diferencial anterior
2. **Cada diferencial es acumulativo** — el martes tiene cambios desde lunes, el miércoles tiene cambios desde lunes (no desde martes)
3. **Restaurar diferencial requiere:**
- Último backup completo (ya restaurado en dasu-sql4)
- Backup diferencial más reciente
- `WITH NORECOVERY` para aplicar diferencial, luego `WITH RECOVERY` para poner BD online
---
*Documento creado: 2026-04-14*
*Última actualización: 2026-04-14*
*Versión: 2.1 — Con pipeline diferencial*