# PostgreSQL desde cero (V): replicación, backups y producción

*Quinta y última entrega de la serie **[PostgreSQL desde cero a pro](/search?tag=postgresql-desde-cero)**. Tiempo de lectura estimado: 14 minutos.*

Último tramo. En las entregas anteriores has aprendido a instalar PostgreSQL ([I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)), diseñar esquemas serios ([II](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)), escribir consultas avanzadas ([III](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)) y diagnosticar rendimiento ([IV](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)). Ahora toca lo que separa un PostgreSQL "que funciona" de uno con el que tu jefe puede dormir tranquilo: **replicación**, **backups**, **monitorización** y **HA**.

No voy a cubrir absolutamente todo. Voy a dar la columna vertebral, suficiente para que puedas llevar a producción un clúster pequeño-mediano y saber qué buscar cuando necesites escalar.

## WAL: lo que hay que entender antes de nada

PostgreSQL escribe cada cambio primero en el **Write-Ahead Log** (WAL). Es un log binario secuencial: cualquier modificación se persiste en el WAL **antes** de cambiar los ficheros de datos. Todo lo demás —replicación, backups, point-in-time recovery— se construye sobre esto.

- Los WAL segments viven en `$PGDATA/pg_wal/`.
- Cada segmento es un fichero (16 MB por defecto).
- Se reciclan cuando ya no hacen falta para recovery ni para réplicas.

Si un servidor tiene `archive_mode = on`, cada WAL lleno se copia a un almacenamiento externo vía `archive_command`. Esa copia es la base de los backups físicos y del *point-in-time recovery*.

## Backups

### Tipos de backup

- **Lógicos** (`pg_dump`, `pg_dumpall`): exportan SQL. Portables entre versiones, lentos para datasets grandes, no permiten PITR.
- **Físicos** (`pg_basebackup`, `WAL-G`, `pgBackRest`, `Barman`): copias binarias del directorio de datos + los WAL archivados. Rápidos para restaurar, permiten PITR.

Para producción, **siempre físicos**. `pg_dump` sigue siendo útil para migraciones entre versiones mayores o para snapshots de tablas específicas.

### pg_dump

Lógico, por base de datos:

```bash
pg_dump -Fc -f blog.dump blog
# Formato custom (-Fc): comprimido, permite restore selectivo
pg_restore -d blog_nuevo blog.dump
```

Para una sola tabla:

```bash
pg_dump -t posts -Fc -f posts.dump blog
```

Útil en desarrollo y en migraciones. En producción de un sistema grande, no es tu herramienta principal.

### pg_basebackup

Backup físico básico, desde una réplica o desde el master con `primary`:

```bash
pg_basebackup \
  -h primary.internal \
  -U replicator \
  -D /var/lib/postgresql/17/backup \
  -Fp -Xs -P -R
```

- `-Fp`: plain (los ficheros tal cual).
- `-Xs`: streamea WAL durante el backup.
- `-P`: muestra progreso.
- `-R`: escribe `standby.signal` y `primary_conninfo`, preparando el directorio para ser réplica.

Rápido y suficiente para clusters pequeños. Para los serios, la siguiente herramienta.

### WAL-G, pgBackRest, Barman

Las tres herramientas de facto para backups en producción:

- **WAL-G**: rápida, pensada para cloud (S3, GCS, Azure), paraleliza bien. Mi elección por defecto.
- **pgBackRest**: muy completa, excelentes opciones de retención y verificación. Estándar en entornos on-prem.
- **Barman**: veterana, buena integración con Debian.

Configuración típica de WAL-G con S3 (en `postgresql.conf`):

```conf
archive_mode = on
archive_command = 'wal-g wal-push %p'
archive_timeout = 60
```

Y la variable de entorno con la config:

```bash
WALG_S3_PREFIX=s3://backups-pg/prod
AWS_REGION=eu-west-1
WALG_COMPRESSION_METHOD=brotli
```

Para hacer un base backup:

```bash
wal-g backup-push /var/lib/postgresql/17/main
```

Y para listar/restaurar:

```bash
wal-g backup-list
wal-g backup-fetch /var/lib/postgresql/17/main LATEST
```

### Point-in-time recovery

Lo que hace insustituibles los backups físicos: restaurar a un instante exacto.

```conf
# recovery.conf o postgresql.auto.conf
restore_command = 'wal-g wal-fetch %f %p'
recovery_target_time = '2026-05-01 12:34:56 UTC'
recovery_target_action = 'promote'
```

Recuperas el último base backup anterior a esa hora y aplicas los WAL hasta llegar justo al punto pedido. Perfecto para deshacer un DROP TABLE accidental, un UPDATE que olvidó el WHERE, o cualquier error de los que duelen.

**Probá tus backups**. Un backup sin restore probado es placebo. Programa restores periódicos a un servidor staging. Si no has restaurado nunca, no tienes backup.

## Replicación streaming

Una primaria acepta escrituras y envía su WAL a una o más réplicas. Las réplicas aplican el WAL y quedan sincronizadas (con algo de lag).

### Configuración básica en la primaria

En `postgresql.conf`:

```conf
wal_level = replica            # o 'logical' si además haces logical replication
max_wal_senders = 10
wal_keep_size = 1GB            # o usa replication slots
hot_standby = on
```

Crear el rol de replicación:

```sql
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'xxx';
```

En `pg_hba.conf`:

```
host replication replicator 10.0.0.0/8 scram-sha-256
```

### Configuración de la réplica

1. Parar PostgreSQL en la réplica.
2. Limpiar `$PGDATA`.
3. `pg_basebackup ... -R` desde la primaria.
4. Arrancar. Aparece como standby.

El fichero `standby.signal` marca que el servidor es una réplica; `postgresql.auto.conf` incluye el `primary_conninfo` generado por `-R`.

Verificar:

```sql
-- En la primaria
SELECT client_addr, state, sent_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes
FROM pg_stat_replication;

-- En la réplica
SELECT pg_is_in_recovery();
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;
```

### Replication slots

Sin replication slot, la primaria puede reciclar un WAL que la réplica aún necesita y romper la replicación. Los slots garantizan que la primaria conserve los WAL necesarios:

```sql
SELECT pg_create_physical_replication_slot('replica_1');
```

En la réplica, `primary_slot_name = 'replica_1'` en `postgresql.conf`.

Cuidado con el revés: si una réplica se cae y el slot queda huérfano, la primaria acumulará WAL indefinidamente hasta llenar el disco. Monitoriza siempre los slots.

### Replicación sincrónica

Por defecto, la replicación es **asíncrona**: la primaria no espera a la réplica. Si quieres que los commits solo confirmen cuando la réplica los ha aplicado:

```conf
synchronous_standby_names = 'ANY 1 (replica1, replica2)'
```

Coste: latencia de escritura aumenta. Ganancia: cero data loss ante caída de la primaria. Para cuentas bancarias, sí. Para analítica, normalmente no.

## Alta disponibilidad y failover

PostgreSQL no tiene failover automático integrado. Necesitas orquestarlo. Opciones:

- **Patroni** + etcd/Consul/ZooKeeper. Es el estándar de facto.
- **repmgr**: más simple, menos potente.
- **Managed services** (RDS, Cloud SQL, Crunchy Bridge): delegas el failover.

Ideas comunes a todas:

- El "leader" es uno, decidido por consenso externo.
- Las réplicas promocionan cuando el leader se cae.
- Un **proxy** delante (HAProxy, PgBouncer con `server_routing`) enruta al leader actual.

Una vez promocionada una réplica, la antigua primaria **no puede reincorporarse sin más**: habría divergido. Herramientas como `pg_rewind` reconcilian automáticamente; Patroni lo hace por ti.

## Logical replication

Distinta a la streaming: en lugar de replicar WAL binario, replica **cambios lógicos** (INSERT/UPDATE/DELETE en tablas concretas). Usa:

- Replicación entre versiones mayores distintas (16 → 17).
- Migración online de un servidor a otro.
- Partir datos por tablas: "estas 5 tablas al servidor A, estas 3 al B".
- Pipelines de CDC (change data capture) a Kafka, Debezium, analíticas.

Setup básico:

```sql
-- En el origen
ALTER SYSTEM SET wal_level = 'logical';
-- reiniciar
CREATE PUBLICATION pub_all FOR ALL TABLES;

-- En el destino
CREATE SUBSCRIPTION sub_all
  CONNECTION 'host=origen ...'
  PUBLICATION pub_all;
```

Caveats: no replica DDL (solo datos), los slots pueden acumular WAL si el subscriber no está al día, no todas las operaciones se replican (TRUNCATE sí desde 11, DDL no).

## Monitorización mínima

Métricas imprescindibles:

- **Latencia de consultas** (p95, p99).
- **Lag de replicación**: segundos de retraso entre primaria y réplicas.
- **Tasa de commits y rollbacks**.
- **Uso de conexiones** vs `max_connections`.
- **Tasa de cache hit**: `blks_hit / (blks_hit + blks_read)` desde `pg_stat_database`. Objetivo: >99%.
- **Tamaño de `pg_wal/`** y tamaño de WAL archivados pendientes.
- **Autovacuum**: tablas con `n_dead_tup` creciente y `last_autovacuum` antiguo.
- **Disco**: llenado de `$PGDATA` y del volumen de WAL.

Herramientas:

- **Prometheus + `postgres_exporter` + Grafana**. Dashboards listos en grafana.com.
- **pgwatch2**, **pganalyze** (comercial), **pgHero** (simple y bueno).

Si ya montas Prometheus/Grafana, el post [Prometheus y Grafana para servicios pequeños](/post/prometheus-y-grafana-para-servicios-pequenos) aplica igual.

### Alertas mínimas

- `replication_lag_bytes` > 100 MB sostenido 5 minutos.
- `replication_lag_time` > 30 segundos.
- `disk_usage` > 80%.
- `connections_used / max_connections` > 80%.
- `cache_hit_ratio` < 95%.
- `deadlocks` > 0 en última hora.
- `autovacuum_running` con duración extrema.
- WAL archivados pendientes > umbral.

## Seguridad

Lo mínimo en producción:

1. **TLS obligatorio** en conexiones externas (`hostssl` en pg_hba, `ssl = on` en postgresql.conf).
2. **Contraseñas `scram-sha-256`**, nunca `md5` en nuevos despliegues.
3. **Roles sin `SUPERUSER`** para la app (ya lo vimos en la [entrega I](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)).
4. **Red privada**: PostgreSQL jamás expuesto a internet directamente.
5. **Secret management**: contraseñas en variables de entorno, vaults o equivalentes, no en repos.
6. **Row Level Security** para multitenancy:
   ```sql
   ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
   CREATE POLICY tenant_isolation ON posts
     USING (tenant_id = current_setting('app.tenant_id')::bigint);
   ```
7. **Audit log** si el compliance lo exige (extensión `pgaudit`).

## Upgrades

- **Minor** (17.1 → 17.2): parar, instalar paquete, arrancar. No hay cambios de formato en disco.
- **Major** (16 → 17): varias rutas:
  - `pg_upgrade`: rápido, in-place, requiere downtime corto.
  - Logical replication: sin downtime pero más complejo.
  - `pg_dump/pg_restore`: simple pero lento para datos grandes.

La recomendación: probar **siempre** en staging con una copia real antes de tocar producción. Y tener un plan de rollback explícito.

## Checklist rápida de producción

Antes de declarar "en producción", repasa:

- [ ] Backups físicos automatizados a almacenamiento externo.
- [ ] **Restore probado** en entorno limpio.
- [ ] Al menos una réplica con lag monitorizado.
- [ ] Failover documentado y probado (runbook).
- [ ] Connection pooler (PgBouncer u otro).
- [ ] Monitorización con las métricas de arriba + alertas.
- [ ] TLS y autenticación robusta.
- [ ] `postgresql.conf` ajustado a los recursos reales (ver [entrega IV](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)).
- [ ] Autovacuum ajustado para tablas calientes.
- [ ] Plan de upgrades (minor y major) con ventanas conocidas.
- [ ] Logs centralizados y con retención.
- [ ] Segurización de red (sin PostgreSQL expuesto a internet).

Si marcas todo, estás muy por encima de la media.

## Cierre de la serie

En cinco posts hemos recorrido PostgreSQL desde instalarlo hasta operarlo con seriedad. Como siempre, el 80% del valor está en las decisiones tempranas: modelo bien pensado, tipos correctos, restricciones que garantizan invariantes, índices donde hacen falta. Lo demás es operación: importante pero secundaria.

Mis dos recomendaciones finales:

1. **Empieza pequeño, instrumenta desde el día 1**. Un PostgreSQL con `pg_stat_statements` y métricas básicas te ahorra sorpresas. Las mejores optimizaciones las hace PostgreSQL por sí solo si tiene estadísticas buenas.
2. **No caigas en la tentación de microservicios-base-de-datos antes de tiempo**. Una PostgreSQL bien diseñada con schemas y RLS aguanta mucho más de lo que la gente cree. El coste de operar varias bases es real.

Serie completa:

- [I: instalación, psql y primeros pasos](/post/postgresql-desde-cero-i-instalacion-psql-y-primeros-pasos)
- [II: tipos, restricciones y relaciones](/post/postgresql-desde-cero-ii-tipos-restricciones-y-relaciones)
- [III: CTEs, window functions y consultas avanzadas](/post/postgresql-desde-cero-iii-ctes-window-functions-y-consultas-avanzadas)
- [IV: índices, EXPLAIN y rendimiento](/post/postgresql-desde-cero-iv-indices-explain-y-rendimiento)
- V: replicación, backups y producción *(estás aquí)*

Y si quieres seguir adentrándote en el ecosistema de bases de datos:

- [ClickHouse desde cero a pro](/search?tag=clickhouse-desde-cero): cuando PostgreSQL deja de rendir en analytics.
- [PostgreSQL: 10 consultas que todo desarrollador debería conocer](/post/postgresql-10-consultas-que-todo-desarrollador-deberia-conocer): repaso rápido de las herramientas que más he usado.

Si tienes dudas sobre montar replicación, diseñar un esquema concreto o depurar una consulta lenta, mis DMs siguen abiertos.
