Saltar a contenido

Backup y rollback

Qué vas a aprender

  • Qué son RPO y RTO y cómo se midieron de verdad en el servidor.
  • Qué captura el backup cada 6 h, cómo se verifica y cómo se restaura (drill en scratch).
  • Cómo se hace un rollback release-to-release por ID/digest de imagen, con ruteo persistente y shadow de control.

Dos preguntas que todo operador debe saber contestar

Sigla Pregunta Unidad
RPO (Recovery Point Objective) ¿Cuántos datos puedo perder como máximo? tiempo hacia atrás
RTO (Recovery Time Objective) ¿Cuánto tardo en volver a estar en pie? tiempo hacia delante

Analogía

El RPO es cuánto trabajo pierdes si se va la luz desde la última vez que guardaste; el RTO, cuánto tardas en volver a tener el documento abierto.

El backup (gate S6)

  • Directorio: /home/iort-user/finaz-te-backups/ (700), fuera de los volúmenes Docker.
  • Frecuencia: cada 6 h (02:20, 08:20, 14:20 y 20:20 UTC), cron de iort-user.
  • Retención: 20 capturas (5 días). Solo borra directorios 20YYmmddTHHMMSSZ.
  • Tamaño medido: ~0,6 MB por captura (584.275 bytes el 2026-09-22).
  • Scripts versionados en deployment/v2/ops/backup/ (idénticos a los vivos en ~/finaz-te-backups/bin/).
20 2,8,14,20 * * * FINAZ_TE_BACKUP_KEEP=20 /home/iort-user/finaz-te-backups/bin/finaz_te_backup.sh >> /home/iort-user/finaz-te-backups/backup.log 2>&1

Qué captura finaz_te_backup.sh:

# Artefacto Cómo
0 Marcador backup_marker INSERT origen='backup-pre-dump' (rol migrador)
1 pg_finaz_te.dump pg_dump -Fc de la base finaz_trading_engine
2 ch_{tabla}.native SELECT … FORMAT Native (rol finaz_te_read)
3 finaz-te-artifacts.tar.gz tar del volumen de artefactos
4 finaz-te-jobs.tar.gz tar del spool de jobs
5 manifest.json sha256 + bytes + marcador RPO (nunca secretos)
flowchart LR
    M["marcador RPO<br/>(PG)"] --> D["pg_dump -Fc"]
    D --> C["export CH Native"]
    C --> T["tar de volúmenes"]
    T --> MF["manifest.json<br/>(sha256)"]
    MF --> R["retención 20"]
    MF -.->|"rsync pull ~15 min después"| OFF["copia off-host<br/>(portátil del operador)"]

Restaurar sin tocar lo vivo: el drill

finaz_te_restore_drill.sh restaura la captura más reciente en namespaces scratch (nunca sobre los vivos): verifica los sha256 del manifiesto (divergencia = FALLO), restaura PG en una base finaz_te_drill_<HHMMSS>, lee el marcador, mide tiempos y borra la base scratch al terminar.

Cómo se midió el RPO (2026-09-22)

  1. Se escribieron 10 marcadores rpo-referencia cada 10 s (ids 1–10).
  2. Se lanzó el backup (captura 20260922T200413Z); marcador backup-pre-dump id 11.
  3. Se escribieron 6 marcadores más después del backup (ids 12–17).
  4. El drill restauró la captura: aparecen los ids 1–11; los 12–17 no (como debe ser).
Magnitud Valor medido
RPO observado 3,395 s (≈3,4 s)
RPO máximo teórico 6 h (intervalo del cron)
RTO_PG 2.092 ms
RTO total del drill 6.415 ms

Gate S6: PASS (hito 4, 2026-09-22)

El lado servidor está cumplido (RPO y RTO medidos con reloj, hashes, cron cada 6 h, retención 20) y la copia off-host está operativa: el portátil del operador tira de las capturas por rsync (sin --delete) y verifica cada artefacto con sha256 contra su manifest.json. El 2026-09-22 se registraron dos pulls verificados: el primero con 18 artefactos OK y el segundo con 30 artefactos OK y 0 fallos (5 capturas copiadas), evidencia en qa_reports/v2/runs/2026-09-22/s6/offhost-pull.json. Límite declarado: la copia depende de que el portátil esté encendido.

# En el portátil (off-host): script deployment/v2/ops/backup/finaz_te_backup_pull.sh,
# cron "50 4,10,16,22 * * *" hora de Madrid (30 min tras cada captura). Núcleo:
rsync -az finaz-new-user:finaz-te-backups/ "$HOME/manupy/finaz-te-backups"/   # destino real según offhost-pull.json
# y después sha256 de cada artefacto contra el manifest.json de su captura

Rollback release-to-release (gate G9)

Un rollback vuelve a la versión buena anterior. Aquí se hace por ID/digest de imagen (nunca latest) y recreando solo el servicio afectado.

sequenceDiagram
    participant O as Operador
    participant API as api
    participant R as Ruteo (volumen artefactos)
    O->>API: a. anotar release buena (:s2, digest, healthy, verify_release OK)
    O->>O: b. construir candidata (:s4, ID distinto)
    O->>API: c. up -d --no-deps api con :s4 → e2e 10/10 + run nuevo
    O->>R: d. fijar flujo en modo v2, runtime causal (actor, motivo)
    O->>API: e. up -d --no-deps api con :s2 → healthy, e2e 10/10, run de c sigue
    O->>R: f. revertir flujo a modo legacy, banco (reversion=true)
    O->>O: g. shadow golden: match=true, 0 desviaciones

Drill vivo del 2026-09-22 (deployment/v2/ops/rollback_drill.py, evidencia en qa_reports/v2/runs/2026-09-22/g9/): los 7 pasos a–g en PASS. Con este drill, más la carga y el caos en vivo, el gate G9 está en PASS (docs/v2/GATES.md, hito 4).

Paso Resultado
a. Release buena finaz-te-api:s2, sha256:5fc6b6df…, healthy, verify_release: OK
c. Candidata finaz-te-api:s4 healthy, e2e_batch 10/10, run nuevo 202 → consultable
d. Ruteo flujo fixture.flow.01_A_featurev2 (auditado)
e. Rollback vuelta a :s2 en 7,61 s hasta healthy; healthz/ready 200; e2e 10/10
f. Revertir ruteo flujo → legacy, reversion: true en el historial
g. Shadow match: true, 0 desviaciones (oráculo AG-001.H1)
# Ejecución automática (sale 0 solo si a–g son PASS)
python3 deployment/v2/ops/rollback_drill.py --salida qa_reports/v2/runs/<fecha>/g9

# El núcleo manual del rollback:
DC="docker compose -p finaz-trading-engine -f deployment/v2/compose.yaml"
FINAZ_API_IMAGE=finaz-te-api:s2 $DC up -d --no-deps api
sh deployment/v2/verify_release.sh

Prohibido en un rollback

docker compose down (con o sin -v), tocar worker-stream o sus diarios, operar sobre el proyecto finaz, prune, o dejar escritores duales (en shadow el runtime causal no publica).

Alcance

El drill cubre el servicio api. El rollback de workers, el de esquema PG (expand/contract) y la restauración de backups tienen sus propios procedimientos. El ruteo persistente es de momento el registro auditado del flag canary: la API aún no lo consume en caliente.

Deuda de release tras el hito 4 (docs/v2/GATES.md, 2026-09-22)

Las imágenes :s4 (finaz-te-api, finaz-te-core, finaz-te-neural) se construyeron el 2026-09-22 y se usaron en los drills (candidata del rollback, drill G3, job G7), pero no están en release-manifest.s2.json ni las cubre verify_release.sh. Los servicios vivos siguen en :s2/:s3. Mientras no se registren, :s4 no es una release a la que se pueda volver por procedimiento. Por eso S5 sigue INCOMPLETO: el daemon worker-stream corre :s3 sin fencing y con emit no golden, y promoverlo exige :s4 + FINAZ_STREAM_RUN_ID + grupo nuevo + verify_release.

Resumen

  • Backup cada 6 h, retención 20, hashes en manifest.json, restauración siempre en scratch.
  • RPO observado 3,395 s (máx. teórico 6 h), RTO_PG 2,1 s; off-host verificado (último pull 30/30 artefactos): S6 PASS.
  • Estado global tras el hito 4: todo PASS salvo S5 INCOMPLETO; las imágenes :s4 aún no están en el release manifest.
  • Rollback por digest recreando solo api; drill vivo con 7/7 pasos PASS: G9 PASS.

Para practicar

  1. Si el servidor muere a las 14:15 UTC, ¿cuánto dato perderías como máximo y por qué?
  2. ¿Por qué el drill restaura en una base finaz_te_drill_* y no en la viva?
  3. Explica por qué down -v no es un rollback válido aquí.