Diagrama de replicación y failover de bases de datos: primario, réplicas y protección contra split-brain

Un diagrama de replicación debe mostrar adónde van las escrituras, cuánto pueden atrasarse las lecturas, quién decide un failover y qué impide tener dos primarios.

· Fundador y desarrollador

Publicado el

Diagrama de replicación y failover de bases de datos: primario, réplicas y protección contra split-brain

La mayoría de los diagramas de replicación muestran un primario, dos réplicas y flechas entre ellos. Esa imagen es cierta y casi inútil durante un incidente, porque no dice qué nodo acepta escrituras ahora mismo, qué réplica puede servir datos atrasados ni qué pasa cuando el primario deja de responder.

Un diagrama de replicación útil responde a cuatro preguntas. Esta guía las trabaja con PostgreSQL, Patroni y etcd, y después dibuja el resultado. Las mismas preguntas valen para MySQL, para otros gestores de failover y para los servicios gestionados.

Cuatro preguntas que todo diagrama de replicación debe responder

  1. ¿Adónde van las escrituras? Tiene que haber exactamente un primario, y los clientes necesitan una forma de encontrarlo tras un failover.
  2. ¿Cuánto pueden atrasarse las lecturas? Cada réplica es síncrona o asíncrona, y las asíncronas van con retraso.
  3. ¿Quién decide un failover? Algo tiene que detectar el fallo, elegir una réplica y promoverla.
  4. ¿Qué impide tener dos primarios? Un primario aislado debe dejar de aceptar escrituras antes de que se promueva otro nodo.

Replicación síncrona y asíncrona

La replicación en streaming de PostgreSQL es asíncrona por defecto. El primario confirma un commit antes de que el standby lo tenga, así que una caída puede perder las transacciones que aún no habían llegado. La documentación de PostgreSQL lo dice claramente: la cantidad de datos perdidos es proporcional al retraso de replicación en el momento del failover.

La replicación síncrona hace que los commits esperen a uno o más standbys de los listados en synchronous_standby_names. Cuánto esperan depende de synchronous_commit:

synchronous_commit Un commit vuelve cuando el standby síncrono lo ha
remote_apply Aplicado, así que las consultas en el standby lo ven
on (por defecto) Escrito en almacenamiento duradero
remote_write Entregado a su sistema operativo, aún sin volcar a disco
local Sin esperar al standby; solo al volcado local
off Sin esperar nada, ni siquiera al disco local

El precio es la latencia: cada commit incluye un viaje de ida y vuelta al standby, así que mantén los standbys síncronos en una zona cercana, no en otra región. Y si desaparece el único standby síncrono, los commits pueden esperar para siempre. Nombra más de un candidato o deja que un gestor de failover cambie el ajuste por ti.

Réplicas de lectura y enrutado de lecturas

Una réplica asíncrona es un buen sitio para informes y endpoints con muchas lecturas, siempre que el código sepa que los datos pueden ir con retraso. En el primario, la vista pg_stat_replication muestra write_lag, flush_lag y replay_lag para cada standby; replay_lag aproxima cuánto tarda un commit en ser visible en la réplica.

Decide por consulta cuáles pueden leer datos atrasados. Un usuario que acaba de guardar un formulario y recarga la página debería leer del primario. Un dashboard puede leer de la réplica.

Failover: quién decide

PostgreSQL no detecta por sí solo un primario caído ni promueve un standby; su documentación deja eso a herramientas externas. Patroni es una opción habitual. Cada nodo ejecuta un agente de Patroni, y los agentes comparten estado a través de un almacén de configuración distribuido como etcd:

  • El primario tiene una clave de líder con un TTL y la renueva en cada ciclo (loop_wait, 10 segundos por defecto).
  • Si no puede renovarla durante ttl segundos (30 por defecto), la clave caduca y las réplicas elegibles inician una carrera por el liderazgo.
  • El mejor candidato se queda con la clave y se promueve; los demás lo siguen.

Los clientes encuentran el nuevo primario a través de un proxy. La API REST de Patroni responde a GET /primary con HTTP 200 solo en el nodo que tiene la clave de líder, así que HAProxy puede enviar las escrituras al nodo que diga que sí:

listen primary
    bind *:5000
    option httpchk OPTIONS /primary
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server pg-a 10.0.1.10:5432 check port 8008
    server pg-b 10.0.2.10:5432 check port 8008
    server pg-c 10.0.3.10:5432 check port 8008

Un segundo listener en el puerto 5001 comprueba /replica?lag=16MB, lo que retira una réplica que vaya más de 16 MB de WAL por detrás. Ejecuta HAProxy en más de una máquina, o se convertirá en el punto único de fallo que intentabas eliminar.

Split-brain: qué impide tener dos primarios

Dos primarios que aceptan escrituras son dos historias que divergen, y una de ellas se tirará. La documentación de PostgreSQL llama al remedio STONITH: hay que decirle al primario antiguo que ya no es el primario. Patroni superpone varias defensas:

  • El arrendamiento. Un primario que no puede actualizar la clave de líder deja de aceptar escrituras: Patroni detiene o degrada PostgreSQL. Si el propio almacén está inaccesible más tiempo que retry_timeout, Patroni degrada el primario, salvo que esté activado su modo failsafe de DCS.
  • Un watchdog. Si el proceso de Patroni se cuelga, un watchdog de Linux reinicia la máquina. Patroni lo configura para que salte 5 segundos antes de que caduque el TTL.
  • Un quórum. etcd necesita la mayoría de sus miembros, dos de tres, para aceptar una escritura. Tres miembros en tres zonas sobreviven a la pérdida de una zona, y una minoría aislada no puede elegir a nadie.
  • Un solo proceso al mando. Solo Patroni puede arrancar PostgreSQL. Un servicio del sistema que reinicie Postgres por su cuenta tras una caída puede traer de vuelta un segundo primario.

Cuando el primario antiguo vuelve, tiene que reincorporarse como réplica. pg_rewind resincroniza su directorio de datos con el nuevo primario sin copiar los bloques que no han cambiado; Patroni lo ejecuta cuando use_pg_rewind está activado.

# Patroni dynamic configuration (patronictl edit-config)
ttl: 30
loop_wait: 10
retry_timeout: 10
synchronous_mode: on        # promote only a node that has every acknowledged commit
postgresql:
  use_pg_rewind: true
  parameters:
    wal_log_hints: "on"

# Local patroni.yml tags
# pg-a: noloadbalance: true            (failover target, no reads)
# pg-c: nosync: true, nofailover: true (reads only)

Con synchronous_mode activado, Patroni solo promueve un standby del que sabe que tiene todos los commits notificados como correctos. La contrapartida es la disponibilidad: si el primario falla mientras no hay ningún standby síncrono conectado, Patroni no promueve a nadie y las escrituras siguen caídas hasta que alguien intervenga.

La replicación no es una copia de seguridad

Un DROP TABLE llega a todas las réplicas en milisegundos. Mantén el archivado continuo de WAL y copias base periódicas, que te dan recuperación a un punto en el tiempo a cualquier momento posterior a la copia base, y prueba las restauraciones de forma programada.

El diagrama

Tres zonas de disponibilidad con nodos PostgreSQL. HAProxy envía las escrituras del servicio de pedidos a pg-b, el primario, y las lecturas a pg-c, una réplica asíncrona. pg-b envía WAL a pg-a de forma síncrona y a pg-c de forma asíncrona, tiene la clave de líder en etcd y archiva el WAL.

11 componentes · 8 conexiones

Abrir en Archboard
Contenido del diagrama
  • Order service
  • HAProxy
  • AZ a
  • AZ b
  • AZ c
  • pg-a · sync standby
  • pg-b · primary
  • pg-c · async replica
  • etcd
  • WAL archive + base backups
  • SQL
  • writes :5000
  • reads :5001
  • WAL (sync)
  • WAL (async)
  • leader key, TTL 30 s
  • races if key expires
  • archived WAL
Un clúster de PostgreSQL en tres zonas gestionado por Patroni: escrituras al primario a través de HAProxy, un standby síncrono para el failover, una réplica asíncrona para lecturas, una clave de líder en etcd y un archivo de WAL para la recuperación a un punto en el tiempo.

Cómo leerlo:

  • Escrituras. El servicio de pedidos se conecta a HAProxy. El puerto 5000 va a pg-b, el único nodo que responde 200 a /primary.
  • Lecturas. El puerto 5001 va a pg-c, una réplica asíncrona en otra zona. pg-a no atiende lecturas.
  • Replicación. pg-b envía WAL a pg-a de forma síncrona (línea continua) y a pg-c de forma asíncrona (discontinua).
  • Failover. pg-b renueva la clave de líder en etcd. Si la clave caduca, pg-a, el standby síncrono, compite por ella. pg-c está etiquetado como nofailover.
  • Recuperación. pg-b archiva el WAL y las copias base en un almacenamiento de objetos.
  • Objetivos. La nota de arriba a la izquierda recoge el RPO, el RTO, el presupuesto de retraso y la retención de copias.

Qué etiquetar en un diagrama de replicación

  • El modo en cada flecha de replicación, síncrono o asíncrono, y el presupuesto de retraso de las réplicas asíncronas.
  • El papel de cada réplica: candidata a failover, réplica de lectura o ambas cosas.
  • Dónde vive el bloqueo de líder y su TTL, o el servicio gestionado que cumple ese papel.
  • Cómo encuentran los clientes el primario: un proxy, un nombre DNS o una IP virtual.
  • Dominios de fallo: las zonas de disponibilidad como marcos, incluido dónde se ejecutan los miembros del consenso.
  • Los objetivos de RPO y RTO, para que quien revise pueda contrastar el diseño con ellos.

Qué hacen por ti los servicios gestionados

Las bases de datos gestionadas responden a las mismas preguntas, pero algunas respuestas vienen fijadas. En una instancia de Amazon RDS Multi-AZ, el standby es síncrono, está en otra zona y no puede atender lecturas. El failover cambia el registro DNS al standby y suele tardar entre 60 y 120 segundos. Un clúster Multi-AZ tiene dos standbys legibles, usa replicación semisíncrona y suele hacer el failover en menos de 35 segundos. En el diagrama, el endpoint sustituye a HAProxy y AWS sustituye a Patroni y etcd. Aun así, sigues etiquetando el retraso, el enrutado de lecturas y los objetivos.

Prueba el failover

Cambia de primario a propósito, por ejemplo en cada ventana de mantenimiento con patronictl switchover. La documentación de PostgreSQL recomienda conmutar con regularidad porque sirve también como prueba del mecanismo de failover. Mide cuánto tiempo fallan las escrituras, comprueba que HAProxy sigue al nuevo primario y confirma que no falta ningún commit confirmado. Para el failover entre regiones, consulta el diagrama de arquitectura multirregión de AWS.

Constrúyelo en Archboard

  1. Inicia sesión con Google o GitHub y crea un diagrama.
  2. Añade tres nodos Marco del pack Básico para las zonas de disponibilidad y, en cada uno, un nodo Postgres del pack Datos: pg-a (standby síncrono), pg-b (primario) y pg-c (réplica asíncrona).
  3. Encima de las zonas, añade un Contenedor del pack Modelo C4 para el servicio de pedidos y un Balanceador de carga del pack Red para HAProxy.
  4. Debajo, añade un Contenedor del pack Modelo C4 para etcd y un Almacén de objetos del pack Datos para el archivo de WAL.
  5. Etiqueta las conexiones: "writes :5000", "reads :5001", "WAL (sync)" en continuo, "WAL (async)" en discontinuo, "leader key, TTL 30 s" de pg-b a etcd y una conexión discontinua de pg-a a etcd para la carrera por el liderazgo.
  6. Añade un bloque Markdown del pack Básico con el RPO, el RTO, el presupuesto de retraso y la retención de copias.

El ejemplo usa 11 nodos de diagrama, dentro del plan Free. Para mantener el diagrama al día a medida que cambia el clúster, consulta Diagramas de arquitectura que no se quedan obsoletos y, para el método general, el ejemplo de diagrama de diseño de sistemas.

Preguntas frecuentes

¿Una réplica de lectura es una copia de seguridad? No. Los borrados y las migraciones erróneas también se replican. Las copias con archivado de WAL te permiten volver a un momento anterior al error.

¿La replicación síncrona significa cero pérdida de datos? Para los commits que un standby síncrono ha confirmado, sí, siempre que el failover promueva solo ese standby. Los commits aceptados mientras no había ningún standby síncrono conectado existen solo en el primario.

¿Por qué tres miembros de etcd y no dos? etcd necesita una mayoría para aceptar escrituras. Dos miembros no pueden perder a ninguno; tres sobreviven a la pérdida de uno y mantienen la mayoría a un lado de una partición de red.

¿Puedo usar DNS en lugar de un proxy para encontrar el primario? Sí, los servicios gestionados lo hacen. Mantén TTL de DNS cortos y asegúrate de que los clientes no guarden las resoluciones en caché durante mucho tiempo; AWS recomienda un TTL de caché DNS de la JVM de no más de 60 segundos.

¿Con qué frecuencia deberíamos probar el failover? Con la suficiente regularidad como para que el equipo confíe en él, por ejemplo una conmutación planificada en cada ventana de mantenimiento más una prueba de fallo no planificado en staging.

Empieza gratis — un puesto, sin tarjeta de crédito.

Artículos relacionados

← Volver al blog

Abre un lienzo en un minuto

Inicia sesión con Google o GitHub. Gratis para un asiento. Mejora el plan cuando se una el equipo.