Files
HoruxDespachosNuevo/docs/plans/2026-04-14-postgres-pitr.md

162 lines
6.5 KiB
Markdown

# PostgreSQL Point-in-Time Recovery (PITR)
**Estado:** PENDIENTE — no implementado. Plan capturado para ejecución posterior. Es una tarea de infraestructura, no de código.
## Problema
Hoy el backup es un dump diario a las 01:00 AM (`scripts/backup.sh`). Si el disco o la BD fallan a las 23:59, **se pierden hasta 24 horas** de datos:
- CFDIs sincronizados del SAT
- CFDIs emitidos vía Facturapi (críticos — ya se cobraron)
- Payments registrados
- Cambios de suscripción
- Alertas resueltas
- Cualquier configuración modificada ese día
Para datos fiscales esto es **legalmente problemático**. El SAT tiene plazos de declaración; perder un día completo puede significar multas o rechazos.
PITR permite recovery a cualquier momento en el pasado (granularidad minutos/segundos) combinando:
- **Base backup** periódico (ej: diario)
- **WAL (Write-Ahead Log) archiving** continuo
## Propuesta
Migrar de `pg_dump` diario a **pgBackRest** o **wal-g** con archivos WAL subidos a almacenamiento externo.
### Stack recomendado
**Opción A: pgBackRest** (más robusto, más maduro)
- Pros: backups incrementales, encryption, verificación de integridad, restoration testing
- Cons: más complejo de setup, más moving parts
- Docs: https://pgbackrest.org/
**Opción B: wal-g** (más simple, cloud-native)
- Pros: setup más rápido, buen fit para S3/GCS
- Cons: menos features que pgBackRest
- Docs: https://github.com/wal-g/wal-g
**Recomendación:** wal-g si ya usamos/planeamos usar S3; pgBackRest si queremos más control y el almacenamiento es NFS/disco dedicado.
### Almacenamiento de WAL archives
Opciones, de mejor a peor:
1. **S3 bucket** (AWS o compatible: Backblaze B2, Wasabi, DigitalOcean Spaces) — off-site, durable, ~$5/mes para este volumen
2. **Servidor remoto dedicado** via SSH (rsync/SFTP) — más lento, más control
3. **Disco adicional local** — NO recomendado (falla del server = pierde backup)
### Configuración Postgres
```ini
# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'wal-g wal-push %p' # O equivalente pgBackRest
archive_timeout = 300 # Force a WAL segment at least every 5 min
```
### Frequency
- **Base backup:** 1x / semana (domingos 02:00 AM)
- **WAL archive:** continuo (cada WAL de 16MB o cada 5 min, lo que ocurra primero)
Recuperación: base del último domingo + WAL segments hasta el momento T → reconstruye exactamente el estado en T.
### RPO / RTO target
- **RPO (Recovery Point Objective):** 5 minutos de pérdida máxima (por el `archive_timeout`)
- **RTO (Recovery Time Objective):** 30-60 min (download base backup + replay WAL + switch)
vs. hoy: RPO 24h, RTO ~1h con el dump.
## Pasos para implementar
1. **Elegir destino de WAL** (recomiendo Backblaze B2 o Wasabi por precio / durabilidad)
2. **Instalar wal-g** en el server de Postgres
3. **Configurar credenciales** (env vars para S3, o archivo de config)
4. **Modificar `postgresql.conf`** con `archive_mode`, `archive_command`, etc. + reiniciar Postgres
5. **Tomar primer base backup** manual con `wal-g backup-push`
6. **Crontab:** base backup semanal, monitoreo
7. **Script de restore** documentado y probado (¡sin probar no es backup!)
8. **Monitoreo:** alerta si no hay WAL push en >10 min (indica que archive falla)
9. **Runbook de disaster recovery** — pasos exactos para restaurar
## Testing de restore
Componente crítico — sin haberlo probado, no hay backup real.
Ritual cada trimestre:
1. Elegir timestamp arbitrario en el pasado (ej: hace 3 días a las 14:00)
2. Spawn Postgres nuevo en máquina de staging
3. `wal-g backup-fetch` del backup base
4. `wal-g wal-fetch` del WAL hasta el timestamp
5. Verificar que la BD está consistente + data esperada está presente
6. Documentar tiempo real de restore (ajusta RTO estimado)
## Consideraciones extra
### Encriptación
wal-g soporta encryption de WAL + backups con AWS KMS o pgp. **Recomendado para datos fiscales** — incluso si alguien compromete el bucket S3, no puede leer los archivos.
### Retention
- Backups/WAL de últimos 30 días: retain
- 31-90 días: 1 backup base semanal
- 90+ días: 1 backup base mensual (para compliance SAT de 5 años)
Configurable en wal-g via `WALG_DELTA_MAX_STEPS` y policy manual.
### Costo estimado
Volumen actual (estimado):
- Base backup: ~1-5 GB comprimido (depende de CFDIs acumulados)
- WAL diario: ~100-500 MB/día
S3 cost (Backblaze B2 ~$6/TB/mes):
- 30 días hot retention ≈ 30 GB ≈ $0.20/mes
- 5 años retention (archivos mensuales) ≈ 60 GB ≈ $0.40/mes
**Total estimado: ~$1-5/mes** dependiendo del vendor. Peanuts comparado con lo que protege.
## Alcance
| Tarea | Estimación |
|-------|-----------|
| Elegir vendor + crear bucket | 30 min |
| Instalar + configurar wal-g | 2 h |
| Configurar Postgres + reiniciar | 1 h |
| Primer base backup + verificar | 1 h |
| Script de restore + primer drill | 2 h |
| Crontab + monitoreo básico | 1 h |
| Runbook de DR escrito | 2 h |
| **Total** | **1-2 días** |
## Riesgos
1. **Archive command backup pressure:** si S3 está lento o hay ratelimit, `archive_command` se enchola y Postgres se satura. Mitigación: usar `archive_library` en Postgres 15+ con async archiving.
2. **Reinicio de Postgres requerido:** cambiar `wal_level` o `archive_mode` requiere restart. Coordinar con deploy window.
3. **Credenciales de S3 en server:** si server se compromete, las keys permiten borrar backups. Mitigación: usar IAM policy con "write only" (no delete) al bucket.
4. **Billing de S3:** vigilar primeros meses por si el volumen es mayor al estimado.
5. **Testing real importante:** sin drill de restore, es teatro de seguridad.
## Out of scope
- Replicación streaming (hot standby) — mucho más caro, diferente problem (HA vs DR)
- Multi-region — hasta que haya tráfico significativo fuera de CDMX
- Backup de BDs tenant individualmente — las BDs tenant viven en el mismo cluster Postgres, backup a nivel cluster las cubre todas
## Archivos a tocar
- `scripts/backup.sh` — deprecar o convertir en wrapper de wal-g
- `deploy/wal-g.conf` (nuevo) — config de wal-g
- `deploy/postgres/postgresql.conf` snippet (nuevo) — cambios a aplicar
- `docs/architecture/disaster-recovery.md` (nuevo) — runbook
- `docs/architecture/deployment.md` — actualizar sección de backups
## Relación con otros planes
- **`2026-04-14-audit-log.md`:** si un evento se perdió en la ventana de PITR, el audit log en S3 puede complementar la reconstrucción.
- **Infra futura (HA / multi-region):** PITR es precursor lógico — una vez que tenemos WAL archiving, agregar una standby que consuma esos WAL es incremental.