6.5 KiB
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:
- S3 bucket (AWS o compatible: Backblaze B2, Wasabi, DigitalOcean Spaces) — off-site, durable, ~$5/mes para este volumen
- Servidor remoto dedicado via SSH (rsync/SFTP) — más lento, más control
- Disco adicional local — NO recomendado (falla del server = pierde backup)
Configuración Postgres
# 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
- Elegir destino de WAL (recomiendo Backblaze B2 o Wasabi por precio / durabilidad)
- Instalar wal-g en el server de Postgres
- Configurar credenciales (env vars para S3, o archivo de config)
- Modificar
postgresql.confconarchive_mode,archive_command, etc. + reiniciar Postgres - Tomar primer base backup manual con
wal-g backup-push - Crontab: base backup semanal, monitoreo
- Script de restore documentado y probado (¡sin probar no es backup!)
- Monitoreo: alerta si no hay WAL push en >10 min (indica que archive falla)
- Runbook de disaster recovery — pasos exactos para restaurar
Testing de restore
Componente crítico — sin haberlo probado, no hay backup real.
Ritual cada trimestre:
- Elegir timestamp arbitrario en el pasado (ej: hace 3 días a las 14:00)
- Spawn Postgres nuevo en máquina de staging
wal-g backup-fetchdel backup basewal-g wal-fetchdel WAL hasta el timestamp- Verificar que la BD está consistente + data esperada está presente
- 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
- Archive command backup pressure: si S3 está lento o hay ratelimit,
archive_commandse enchola y Postgres se satura. Mitigación: usararchive_libraryen Postgres 15+ con async archiving. - Reinicio de Postgres requerido: cambiar
wal_leveloarchive_moderequiere restart. Coordinar con deploy window. - 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.
- Billing de S3: vigilar primeros meses por si el volumen es mayor al estimado.
- 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-gdeploy/wal-g.conf(nuevo) — config de wal-gdeploy/postgres/postgresql.confsnippet (nuevo) — cambios a aplicardocs/architecture/disaster-recovery.md(nuevo) — runbookdocs/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.