# 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.