Configuración de Replicación Síncrona para Alta Disponibilidad en PostgreSQL

Aprende a configurar alta disponibilidad en PostgreSQL con pérdida de datos cero (RPO=0) usando replicación síncrona por streaming. Este tutorial paso a paso cubre configuraciones esenciales para `wal_level`, slots de replicación, `pg_basebackup` y el ajuste correcto de los parámetros `synchronous_commit` en los servidores primario y en espera para garantizar la durabilidad de las transacciones en entornos críticos.

Configuración de Replicación Síncrona para Alta Disponibilidad en PostgreSQL

Configurar PostgreSQL para alta disponibilidad (HA) generalmente comienza con una pregunta difícil: ¿cuántos datos puedes permitirte perder si el servidor primario desaparece justo después de un commit? Con la replicación asíncrona por streaming normal, la respuesta es "tal vez algunos". El primario puede informar a la aplicación que una transacción se confirmó antes de que el servidor en espera haya recibido o reproducido el registro WAL. Si el primario falla durante esa pequeña ventana, el servidor en espera promovido puede no contener las últimas transacciones confirmadas.

La replicación síncrona por streaming cambia esa compensación. PostgreSQL espera a uno o más servidores en espera nombrados antes de informar el éxito del commit. Dependiendo del nivel de synchronous_commit, el servidor en espera solo puede necesitar escribir el WAL en el sistema operativo, vaciarlo al almacenamiento duradero o reproducirlo para que las consultas en el servidor en espera puedan verlo. Eso puede darte un RPO de cero para las transacciones confirmadas, pero también significa que la ruta de escritura ahora depende de la red y del estado del servidor en espera.

Esa compensación importa. La replicación síncrona es adecuada para el pequeño conjunto de datos donde perder incluso una transacción reconocida es inaceptable: pagos, saldos de cuentas, reservas de inventario, estado de pedidos, pistas de auditoría. A menudo es una mala opción para registros de eventos de alto volumen, datos de flujo de clics, métricas o cargas de trabajo donde la disponibilidad y la latencia importan más que la durabilidad perfecta entre nodos. Antes de habilitarlo globalmente, decide qué parte de tu carga de trabajo realmente lo necesita.

Prerrequisitos

Antes de comenzar, asegúrate de tener dos servidores PostgreSQL configurados (Primario y en Espera) ejecutando versiones principales idénticas de PostgreSQL. Ambos servidores deben tener conectividad de red. Para esta guía, asumimos:

  • Nombre de host/IP del primario: pg_primary
  • Nombre de host/IP del servidor en espera: pg_standby
  • Usuario de replicación: repl_user
  • Nombre de la base de datos: mydb

También necesitas una copia de seguridad funcional y una ventana de mantenimiento para la copia de seguridad base inicial. Los ejemplos asumen PostgreSQL 12 o más reciente, donde el modo de espera se controla con standby.signal y la configuración de conexión generalmente se escribe mediante pg_basebackup -R.

Paso 1: Configuración del Servidor Primario

El servidor primario requiere configuraciones específicas para habilitar la replicación por streaming y gestionar el registro de escritura anticipada (WAL) requerido por los commits síncronos.

A. Ajuste de postgresql.conf en el Primario

Edita el archivo postgresql.conf del servidor primario. Los siguientes parámetros son obligatorios para la replicación por streaming:

# --- Requerido para Replicación ---
listen_addresses = '*'         # Permite conexiones desde el servidor en espera
wal_level = replica            # Debe ser 'replica' o superior (ej., 'logical')
max_wal_senders = 10           # Máximo de conexiones concurrentes desde servidores en espera
max_replication_slots = 10     # Slots necesarios para flujos de replicación persistentes

# --- Esencial para Commit Síncrono ---
synchronous_standby_names = 'FIRST 1 (standby1)' # Especifica los servidores en espera requeridos por application_name

# --- Opcional pero Recomendado ---
wal_log_hints = on             # Recomendado para una replicación más segura, aunque aumenta el volumen de WAL
shared_preload_libraries = 'pg_stat_statements' # Si se usa monitoreo

Explicación de Parámetros Clave:

  • wal_level = replica: Esto asegura que se escriba suficiente información en el WAL para permitir que un servidor en espera reconstruya el estado de la base de datos. Para commits síncronos, este nivel es el requisito mínimo.
  • synchronous_standby_names: Esta es la configuración central para definir qué servidores en espera deben reconocer las escrituras. Los nombres aquí son valores de application_name de la conexión de replicación, no nombres de slots de replicación. FIRST 1 (standby1) significa que PostgreSQL espera al primer servidor en espera síncrono disponible de esa lista. ANY 1 (standby1, standby2) significa que cualquiera de los servidores en espera listados puede satisfacer el commit.

B. Configuración de la Autenticación Basada en Host (pg_hba.conf)

El servidor primario debe permitir que el usuario de replicación del servidor(es) en espera se conecte con fines de replicación.

Agrega una entrada a pg_hba.conf en el primario:

# TIPO  BASE DE DATOS        USUARIO            DIRECCIÓN                 MÉTODO
host    replication          repl_user          pg_standby/32             scram-sha-256

Reemplaza pg_standby/32 con la dirección IP o subred real de tu servidor en espera.

C. Creación del Slot de Replicación y el Usuario

Conéctate a PostgreSQL en el servidor primario para crear el usuario necesario y el slot de replicación.

1. Crear Usuario de Replicación:

CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'una_contraseña_fuerte';

2. Crear Slot de Replicación:

Este slot asegura que los segmentos WAL se retengan hasta que el servidor en espera confirme su recepción, evitando que el servidor en espera se retrase tanto que necesite una nueva copia de seguridad base. Los slots son útiles, pero también pueden llenar los discos si un servidor en espera está caído durante mucho tiempo, así que monitorea el WAL retenido.

SELECT pg_create_physical_replication_slot('standby1_slot');

El nombre del slot no tiene que coincidir con synchronous_standby_names. En este ejemplo, standby1 es el application_name utilizado para la selección del servidor en espera síncrono, mientras que standby1_slot es el slot de replicación física utilizado para la retención de WAL.

D. Reinicio del Primario

Aplica todos los cambios de configuración reiniciando el servicio de PostgreSQL en el servidor primario.

sudo systemctl restart postgresql

Paso 2: Configuración del Servidor en Espera

El servidor en espera está configurado para transmitir registros WAL desde el primario utilizando una configuración de recuperación.

A. Copia de Seguridad Base

Antes de iniciar la transmisión, el servidor en espera necesita una copia completa del directorio de datos del primario. Detén PostgreSQL en el servidor en espera primero.

sudo systemctl stop postgresql

Realiza la copia de seguridad base usando pg_basebackup. Reemplaza las rutas y los detalles de conexión según sea necesario:

# Ejemplo usando la utilidad pg_basebackup
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
  • -D: El directorio de datos de destino en el servidor en espera.
  • -U: El usuario de replicación.
  • -P: Mostrar progreso.
  • -Xs: Incluir archivos WAL necesarios durante la copia de seguridad base.
  • -R: Crear automáticamente el archivo standby.signal y generar la configuración de conexión necesaria en postgresql.auto.conf (o configuración de recuperación).

B. Configuración de postgresql.conf en el Servidor en Espera

En el servidor en espera, asegúrate de que PostgreSQL sepa cómo conectarse de vuelta al primario. El detalle clave para la replicación síncrona es application_name; debe coincidir con el nombre listado en synchronous_standby_names.

# --- Requerido en el Servidor en Espera ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=una_contraseña_fuerte application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on          # Permite consultas de lectura durante el modo de recuperación/espera

C. Inicio del Servidor en Espera

Inicia el servicio de PostgreSQL en el servidor en espera.

sudo systemctl start postgresql

Paso 3: Verificación y Prueba del Commit Síncrono

Una vez que ambos servidores estén en ejecución, verifica la conexión y luego prueba el comportamiento síncrono.

A. Verificación del Estado de Replicación

Conéctate a la base de datos primaria y verifica la vista pg_stat_replication:

SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;

Deberías ver una entrada para standby1 con sync_state como sync. Si muestra potential, el servidor en espera está conectado pero no es el que actualmente satisface los commits síncronos. Si muestra async, PostgreSQL no lo está tratando como un servidor en espera síncrono; verifica la ortografía de application_name y synchronous_standby_names.

B. Prueba del Commit Síncrono

El parámetro global que dicta cuánto espera PostgreSQL es synchronous_commit. Para RPO=0, debes usar un valor que fuerce la sincronización.

1. Configuración del Comportamiento Global

Si configuraste synchronous_standby_names en el primario como se muestra en el Paso 1, el valor predeterminado synchronous_commit = on espera hasta que el servidor en espera síncrono haya vaciado el registro WAL al almacenamiento duradero. remote_write espera hasta que el servidor en espera haya escrito el registro WAL en el sistema operativo, lo que generalmente es más rápido pero no tan fuerte si el host del servidor en espera falla antes de vaciar. remote_apply espera hasta que el servidor en espera haya reproducido la transacción, lo cual es útil cuando tu aplicación lee desde el servidor en espera inmediatamente después de escribir en el primario.

Para la mayoría de las configuraciones de HA con pérdida de datos cero, on es el punto de partida práctico. Usa remote_apply solo cuando el comportamiento de lectura-después-de-escritura en el servidor en espera sea lo suficientemente importante como para justificar la latencia adicional.

# En postgresql.conf en el Primario
synchronous_commit = on

Advertencia: El commit síncrono puede aumentar notablemente la latencia de escritura en comparación con los modos asíncronos (off o local). La latencia adicional proviene de los viajes de ida y vuelta de la red, la velocidad de escritura del WAL del servidor en espera y, para remote_apply, la velocidad de reproducción.

2. Prueba Dentro de una Transacción

Para probar transaccionalmente (sin requerir un cambio de configuración global), puedes configurarlo por sesión o transacción:

-- Conectar al Primario

BEGIN;
SET LOCAL synchronous_commit = on;

INSERT INTO sales (item, amount) VALUES ('Widget A', 100);
-- Este INSERT prepara WAL que debe ser reconocido por el servidor en espera síncrono.

COMMIT;
-- El COMMIT tiene éxito solo después de que el servidor en espera reconoce la escritura del WAL.

Si ningún servidor en espera síncrono configurado está disponible en el momento del commit, el commit espera. Ese es el punto de la característica, pero puede sorprender a los equipos durante una interrupción: el primario puede seguir activo, pero las escrituras parecen congeladas porque PostgreSQL está esperando un reconocimiento síncrono. No hay una configuración general de PostgreSQL que automáticamente "vuelva" al commit asíncrono cuando un servidor en espera síncrono desaparece. Si deseas ese comportamiento, tus herramientas de HA deben cambiar synchronous_standby_names, o debes tener un runbook para hacerlo manualmente después de decidir que la disponibilidad es más importante que la pérdida de datos cero.

Un Patrón Más Seguro de Dos Servidores en Espera

Un solo servidor en espera síncrono proporciona una durabilidad sólida mientras todo está saludable, pero también crea un punto único de disponibilidad de escritura. Si ese servidor en espera está caído, lento o aislado del primario, los commits esperan. En producción, un patrón común es ejecutar al menos dos servidores en espera y requerir un reconocimiento síncrono:

synchronous_standby_names = 'ANY 1 (standby1, standby2)'

Con esta configuración, cualquiera de los servidores en espera puede satisfacer el commit. Si standby1 se está reiniciando, standby2 aún puede reconocer las escrituras. Aún necesitas monitorear ambas réplicas, porque una interrupción prolongada en un servidor en espera puede hacer que su slot de replicación retenga una gran cantidad de WAL, pero es menos probable que el primario se detenga debido a una falla de un solo servidor en espera.

Requerir dos reconocimientos es posible:

synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'

Esa es una elección de durabilidad más estricta. Generalmente se reserva para entornos con enlaces de latencia muy baja y una razón clara para requerir más de una copia remota antes del commit. Para muchas bases de datos de aplicaciones, "cualquiera de dos servidores en espera cercanos" es el mejor equilibrio.

Qué Monitorear Después de Habilitarlo

No te detengas en "el servidor en espera se conecta". La replicación síncrona puede estar funcionando técnicamente mientras la latencia orientada al usuario empeora. Observa estas señales después de la implementación:

SELECT
    application_name,
    client_addr,
    state,
    sync_state,
    write_lag,
    flush_lag,
    replay_lag
FROM pg_stat_replication;

En el primario, sync_state = 'sync' te indica qué servidor en espera es actualmente síncrono. write_lag, flush_lag y replay_lag ayudan a explicar dónde se está yendo el tiempo. Si write_lag es alto, sospecha de la red o de la presión de escritura del WAL del servidor en espera. Si flush_lag es alto, sospecha del almacenamiento. Si solo replay_lag es alto, el servidor en espera puede estar recibiendo WAL pero aplicándolo lentamente debido a E/S, CPU, bloqueos o consultas de larga duración en el servidor en espera.

También monitorea la retención de slots:

SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;

Un slot de replicación protege un servidor en espera, pero no protege tu disco. Si un slot está inactivo y el WAL retenido sigue creciendo, o arregla el servidor en espera rápidamente o elimina el slot después de confirmar que ya no lo necesitas.

Un Plan de Implementación Práctico

Para un sistema de producción ocupado, trata la replicación síncrona como un cambio por etapas en lugar de un ajuste de configuración de una línea.

Primero, construye el servidor en espera de forma asíncrona y déjalo funcionar por un tiempo. Confirma que puede mantenerse al día durante los períodos pico de escritura. Si se retrasa asíncronamente, perjudicará la latencia del commit cuando se vuelva síncrono.

Segundo, configura application_name y verifica que el primario vea el servidor en espera exactamente como esperas en pg_stat_replication. Los errores ortográficos son comunes aquí porque synchronous_standby_names coincide con el application_name en tiempo de ejecución, no con el nombre de host ni con el slot.

Tercero, habilita la replicación síncrona durante una ventana de bajo tráfico y observa la latencia del commit desde el lado de la aplicación. Las métricas de PostgreSQL pueden verse bien mientras el grupo de conexiones de la aplicación se acumula porque las transacciones ahora mantienen las conexiones un poco más de tiempo.

Finalmente, escribe la decisión de falla. Si el servidor en espera síncrono desaparece y el primario está esperando commits, ¿quién puede relajar synchronous_standby_names? ¿Bajo qué condiciones? ¿Cómo verificarás si el primario antiguo o el servidor en espera antiguo contiene los datos más recientes antes de volver a unir los nodos? Estas son decisiones operativas, no solo configuraciones de base de datos.

Mejores Prácticas para HA Síncrona

  • Usa Servidores en Espera Dedicados: Solo asigna servidores en espera que estén físicamente cerca (baja latencia) del primario a tu lista de replicación síncrona. La latencia alta se mostrará directamente en el tiempo de commit.
  • Monitorea el Retraso de Replicación: Incluso en modo síncrono, monitorea el retraso del servidor en espera. Un servidor en espera lento que todavía está técnicamente 'sync' pero que tarda demasiado en procesar el WAL aún puede afectar la experiencia del usuario.
  • Planifica la Compensación de Disponibilidad: Decide de antemano si un operador puede eliminar temporalmente un servidor en espera faltante de synchronous_standby_names durante un incidente.
  • Usa Múltiples Servidores en Espera: Para una mejor disponibilidad de escritura, configura synchronous_standby_names = 'ANY 1 (standby1, standby2)' para que cualquiera de los servidores en espera pueda reconocer commits.