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

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)

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

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