mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 23:23:16 +00:00
Fase 5 (migracion de datos): - api/scripts/migrate-sqlite-to-postgres.ts: ETL unico SQLite -> Postgres. Pre-flight (CURP/RFC duplicados case-insensitive, FKs huerfanas, tenant_id invalido, fechas mal formateadas) -> carga por base/esquema con credenciales _owner (setval de secuencias, OVERRIDING SYSTEM VALUE, ON CONFLICT DO NOTHING idempotente) -> verificacion de conteos y sumas de dinero con tolerancia. Probado end-to-end contra un dataset SQLite legacy sintetico y un Postgres limpio (solo schema+catalogos, sin contexto dev): 100% de las filas migradas, sumas de dinero exactas, snapshot de uploaded_by/created_by resuelto correctamente. - db/RUNBOOK-corte.md: checklist go/no-go para el corte por ambiente. - Fix de bug real encontrado al probar: sin --context-filter explicito, Liquibase corre TAMBIEN los changesets context=dev (comportamiento por defecto, no "modo seguro") -- documentado en db/README.md con el ejemplo correcto (--context-filter='!dev' para staging/produccion). Fase 5b: patron de compensacion para createTenant ya resuelto en el rewrite de saas.ts (fase 3/4). Fase 6 (backups): db/backups/ con plantillas pgBackRest por base, politica de retencion, nota de persistencia minima de Redis (RDB, sin retencion de negocio), checklist de simulacro de restauracion mensual, y la validacion pendiente de si Coolify permite WAL archiving antes de comprometerse a PITR real. Fase 7 (limpieza y CI): - Dockerfile.api simplificado (sin JRE/Liquibase/FFI). Dockerfile.migrate y Dockerfile.provision nuevos, de un solo uso, para el paso explicito de deploy (nunca sirven trafico). - docker-compose.yml: postgres+redis+provision+migrate para dev local end-to-end; api ya no arranca hasta que migrate termina bien. - docs/coolify.md y .env.example actualizados al modelo de 2 bases + Redis + Contabo. - api/schema.sql eliminado (desincronizado, competia con Liquibase como fuente de verdad). - .github/workflows/ci.yml: Postgres+Redis de servicio, aprovisiona roles/ACLs, dry-run de Liquibase (updateSQL) antes de aplicar, verify-isolation.sh, deno check + deno test, build de los dos frontends. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
108 lines
4.8 KiB
Markdown
108 lines
4.8 KiB
Markdown
# Backups y continuidad (Fase 6)
|
|
|
|
## 0. Validar antes de diseñar el resto
|
|
|
|
Coolify despliega Postgres como un recurso "managed" que en realidad es un
|
|
contenedor `postgres:16` estándar con volumen persistente. **Hay que
|
|
confirmar, para el ambiente real, si se puede:**
|
|
|
|
1. Montar un `postgresql.conf` custom (para `archive_mode`, `archive_command`, `wal_level`), y
|
|
2. Ejecutar un sidecar/proceso adicional (`pgbackrest`) con acceso al mismo volumen de datos y a WAL.
|
|
|
|
- **Si sí se puede**: seguir esta guía con pgBackRest (PITR real).
|
|
- **Si no se puede** (Coolify no expone esos hooks en el plan/versión
|
|
usada): el respaldo queda limitado a los `pg_dump` programados que
|
|
Coolify ya ofrece -- el RPO pasa a ser "la frecuencia del dump", no
|
|
segundos. En ese caso, para producción, evaluar auto-hospedar Postgres
|
|
(contenedor propio, fuera del recurso "managed" de Coolify) solo para
|
|
tener control de WAL -- es la única forma de bajar el RPO por debajo de
|
|
la frecuencia del dump.
|
|
|
|
Esta decisión condiciona todo lo demás; no asumir que WAL archiving va a
|
|
funcionar sin probarlo primero contra el Coolify real del ambiente.
|
|
|
|
## 1. Qué se respalda y con qué política
|
|
|
|
| Qué | Herramienta | Frecuencia | Retención | RPO objetivo |
|
|
|---|---|---|---|---|
|
|
| `panels_platform` (prod) | pgBackRest (o `pg_dump` si no hay WAL) | full diario + diferencial c/6h + WAL continuo | 30 días | segundos (con WAL) / 24h (solo dump) |
|
|
| `panels_product` (prod) | igual que arriba | igual | 30 días | igual |
|
|
| `panels_platform`/`panels_product` (staging/dev) | `pg_dump` | full diario | 7 días | 24h |
|
|
| Archivos (Contabo, Fase 4c) | versionado de objetos del bucket + backup del bucket | continuo (versionado) | igual que la BD | ver nota de coordinación abajo |
|
|
| Redis | RDB snapshot | al reiniciar/periódico | ninguna (no es fuente de verdad) | N/A -- ver `db/backups/redis-persistence.md` |
|
|
|
|
**Respaldar SIEMPRE ambas bases.** Es fácil configurar el backup de
|
|
`panels_product` (donde está "todo el negocio") y olvidar `panels_platform`
|
|
(que tiene el registro de TODOS los tenants) -- el día del incidente ahí es
|
|
cuando se descubre.
|
|
|
|
## 2. pgBackRest (si Coolify lo permite)
|
|
|
|
Ver [`pgbackrest-panels_platform.conf`](./pgbackrest-panels_platform.conf) y
|
|
[`pgbackrest-panels_product.conf`](./pgbackrest-panels_product.conf) --
|
|
plantillas, una `stanza` por base de datos (no por esquema: `panels_product`
|
|
es una sola stanza aunque tenga los esquemas iam/core adentro).
|
|
|
|
Pasos (por base):
|
|
|
|
```bash
|
|
# 1. postgresql.conf del contenedor
|
|
wal_level = replica
|
|
archive_mode = on
|
|
archive_command = 'pgbackrest --stanza=panels_platform archive-push %p'
|
|
archive_timeout = 60 # cap de RPO a 60s incluso en bases con poca escritura
|
|
|
|
# 2. Crear la stanza una vez
|
|
pgbackrest --stanza=panels_platform --log-level-console=info stanza-create
|
|
|
|
# 3. Verificar que el archiving realmente funciona ANTES de confiar en él
|
|
pgbackrest --stanza=panels_platform check
|
|
|
|
# 4. Backups programados (cron / scheduler de Coolify)
|
|
pgbackrest --stanza=panels_platform --type=full backup # 1x/semana
|
|
pgbackrest --stanza=panels_platform --type=diff backup # cada 6h
|
|
```
|
|
|
|
Repetir con `--stanza=panels_product` y su propio `archive_command`.
|
|
|
|
### Point-in-time recovery
|
|
|
|
```bash
|
|
pgbackrest --stanza=panels_platform --type=time \
|
|
--target="2026-09-01 14:00:00-06" --target-action=promote restore
|
|
```
|
|
|
|
## 3. Si NO hay WAL archiving (solo pg_dump)
|
|
|
|
```bash
|
|
pg_dump --format=custom --file=panels_platform-$(date +%Y%m%d).dump "$DATABASE_URL_PLATFORM_OWNER"
|
|
pg_dump --format=custom --file=panels_product-$(date +%Y%m%d).dump "$DATABASE_URL_CORE_OWNER"
|
|
```
|
|
|
|
`--format=custom` permite restore paralelo (`pg_restore -j4`) y restore
|
|
selectivo por tabla si algún día hace falta. Subir los `.dump` a un bucket
|
|
S3-compatible (puede ser el mismo de Contabo, con prefijo/bucket distinto
|
|
al de documentos) con retención por lifecycle policy del bucket.
|
|
|
|
## 4. Coordinación con el respaldo de archivos (Fase 4c)
|
|
|
|
Un restore de Postgres a un punto en el tiempo, sin restaurar los archivos
|
|
de Contabo al MISMO punto, deja filas (`documents.storage_name`) apuntando
|
|
a objetos que ya no existen o que cambiaron. Política:
|
|
|
|
- Activar versionado de objetos en el bucket de Contabo.
|
|
- Si se hace un PITR de Postgres a una hora `T`, documentar que los
|
|
archivos subidos/borrados después de `T` pueden quedar huérfanos o
|
|
inconsistentes -- es una ventana de inconsistencia aceptada, no un bug
|
|
a corregir en caliente durante el incidente.
|
|
|
|
## 5. Redis
|
|
|
|
Ver [`redis-persistence.md`](./redis-persistence.md). Resumen: solo RDB
|
|
para evitar deslogueo masivo, sin retención de negocio, fuera del alcance
|
|
del simulacro de restauración de abajo.
|
|
|
|
## 6. Simulacro de restauración (mensual, obligatorio)
|
|
|
|
Ver [`restore-drill-checklist.md`](./restore-drill-checklist.md). Un
|
|
backup que nunca se probó restaurar no es un backup, es una esperanza.
|