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)¶
- Se escribieron 10 marcadores
rpo-referenciacada 10 s (ids 1–10). - Se lanzó el backup (captura
20260922T200413Z); marcadorbackup-pre-dumpid 11. - Se escribieron 6 marcadores más después del backup (ids 12–17).
- 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_feature → v2 (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
:s4aú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¶
- Si el servidor muere a las 14:15 UTC, ¿cuánto dato perderías como máximo y por qué?
- ¿Por qué el drill restaura en una base
finaz_te_drill_*y no en la viva? - Explica por qué
down -vno es un rollback válido aquí.