↓ Ir al contenido

Recuperación de bases de datos PostgreSQL en Docker tras fallo de replicación: diagnóstico de lag, resincronización selectiva y validación

Rogelio Guerra Riverón
Autor
Rogelio Guerra Riverón
Construyendo mi propia infraestructura web desde cero. Aquí documento cada paso: servidores, redes, contenedores y lo que vaya surgiendo.

El problema
#

Ayer por la noche mi réplica de PostgreSQL dejó de sincronizarse. El servidor primario seguía funcionando, pero los datos nuevos no llegaban al standby. Downtime cero, pero tampoco quería perder esa capa de respaldo.

Documenté el proceso completo porque es algo que vuelve a pasar cada cierto tiempo y necesitaba un proceso reproducible.

Paso 1: Verificar el estado de replicación
#

Primero, entré en el contenedor primario y ejecuté:

docker exec postgres-primary psql -U postgres -c "SELECT slot_name, restart_lsn, confirmed_flush_lsn FROM pg_replication_slots;"

Me mostró que el slot de replicación estaba ahí, pero el confirmed_flush_lsn llevaba casi una hora sin avanzar. Eso confirmaba el lag.

En el standby:

docker exec postgres-standby psql -U postgres -c "SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();"

Los valores eran idénticos y antiguos. El standby ni siquiera estaba intentando recibir WAL.

Paso 2: Revisar los logs
#

docker logs postgres-standby | tail -50

Encontré el culpable: FATAL: could not connect to the primary server. El standby tenía una DNS obsoleta o el puerto 5432 del primario no estaba accesible desde la red interna del contenedor.

Verifiqué la conectividad:

docker exec postgres-standby nc -zv postgres-primary 5432

Confirmado: sin conexión. El primario tenía problemas de red.

Paso 3: Recuperación sin downtime
#

No quería reiniciar nada si podía evitarlo. Mi plan:

  1. Arreglar la conectividad entre contenedores
  2. Forzar el standby a reconectar
  3. Validar la sincronización

Revisé la configuración de la red en docker-compose.yml. El alias del contenedor primario estaba correcto, pero el puerto expuesto no coincidía con el que usaba para replicación interna. Cambié la variable de entorno PRIMARY_CONNINFO en el standby:

postgres-standby:
  environment:
    PRIMARY_CONNINFO: "host=postgres-primary port=5432 user=replication password=***** application_name=standby"

Reinicié solo el contenedor standby (el primario siguió corriendo):

docker restart postgres-standby

Paso 4: Monitorear la resincronización
#

Ejecuté un script cada 30 segundos para ver el progreso:

watch -n 30 'docker exec postgres-primary psql -U postgres -c "SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) as bytes_behind FROM pg_replication_slots;"'

Tardó unos 15 minutos en sincronizar completamente. El lag bajó de 2GB a 0 bytes progresivamente.

Paso 5: Validación de consistencia
#

Una vez sincronizado, necesitaba asegurarme de que los datos eran idénticos:

# En el primario
docker exec postgres-primary psql -U postgres -c "SELECT COUNT(*) as total_rows FROM informacion_importante;"

# En el standby
docker exec postgres-standby psql -U postgres -c "SELECT COUNT(*) as total_rows FROM informacion_importante;"

Los conteos coincidían. Hice una validación más profunda con checksums:

docker exec postgres-primary psql -U postgres -c "SELECT md5(string_agg(id::text, ',' ORDER BY id)) FROM informacion_importante;" > /tmp/primary_hash.txt

docker exec postgres-standby psql -U postgres -c "SELECT md5(string_agg(id::text, ',' ORDER BY id)) FROM informacion_importante;" > /tmp/standby_hash.txt

diff /tmp/primary_hash.txt /tmp/standby_hash.txt

Sin diferencias. La replicación estaba íntegra.

Lecciones aprendidas
#

  • Los problemas de replicación suelen ser conectividad, no corrupción de datos
  • Revisar logs es el primer paso, siempre
  • Monitorear pg_replication_slots previene sorpresas
  • Un standby desincronizado es mejor que nada, pero no es lo ideal

Ahora tengo un cron que ejecuta cada hora:

docker exec postgres-primary psql -U postgres -c "SELECT CASE WHEN pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) > 1073741824 THEN 'ALERTA: Lag > 1GB' ELSE 'OK' END;"

Nunca más me cojo desprevenido.