Diagramme de réplication et de basculement d'une base de données : primaire, réplicas et protection contre le split-brain

Un diagramme de réplication doit montrer où vont les écritures, le retard possible des lectures, qui décide d'un basculement et ce qui empêche deux primaires.

· Fondateur et développeur

Publié le

Diagramme de réplication et de basculement d'une base de données : primaire, réplicas et protection contre le split-brain

La plupart des diagrammes de réplication montrent un primaire, deux réplicas et des flèches entre eux. Cette image est juste, mais presque inutile pendant un incident : elle ne dit pas quel nœud accepte les écritures en ce moment, quel réplica peut servir des données en retard, ni ce qui se passe quand le primaire cesse de répondre.

Un diagramme de réplication utile répond à quatre questions. Ce guide les traite avec PostgreSQL, Patroni et etcd, puis dessine le résultat. Les mêmes questions s'appliquent à MySQL, aux autres gestionnaires de basculement et aux services managés.

Quatre questions auxquelles tout diagramme de réplication doit répondre

  1. Où vont les écritures ? Il doit y avoir exactement un primaire, et les clients ont besoin d'un moyen de le retrouver après un basculement.
  2. À quel point les lectures peuvent-elles être en retard ? Chaque réplica est synchrone ou asynchrone, et les asynchrones prennent du retard.
  3. Qui décide d'un basculement ? Quelque chose doit détecter la panne, choisir un réplica et le promouvoir.
  4. Qu'est-ce qui empêche deux primaires ? Un primaire isolé doit cesser d'accepter des écritures avant qu'un autre nœud soit promu.

Réplication synchrone et asynchrone

La réplication en streaming de PostgreSQL est asynchrone par défaut. Le primaire confirme un commit avant que le standby ne l'ait reçu ; un crash peut donc faire perdre les transactions qui n'étaient pas encore arrivées. La documentation de PostgreSQL le dit sans détour : la quantité de données perdues est proportionnelle au retard de réplication au moment du basculement.

La réplication synchrone fait attendre les commits sur un ou plusieurs standbys listés dans synchronous_standby_names. La durée de cette attente dépend de synchronous_commit :

synchronous_commit Un commit revient une fois que le standby synchrone l'a
remote_apply Appliqué, si bien que les requêtes sur le standby le voient
on (par défaut) Écrit sur un stockage durable
remote_write Transmis à son système d'exploitation, sans flush
local Pas d'attente du standby ; seulement le flush local
off Aucune attente, pas même pour le disque local

Le prix, c'est la latence : chaque commit inclut un aller-retour vers le standby, donc gardez les standbys synchrones dans une zone proche, pas dans une autre région. Et si le seul standby synchrone disparaît, les commits peuvent attendre indéfiniment. Désignez plusieurs candidats, ou laissez un gestionnaire de basculement modifier le réglage pour vous.

Réplicas en lecture et routage des lectures

Un réplica asynchrone est un bon endroit pour les rapports et les endpoints très sollicités en lecture, à condition que le code sache que les données peuvent être en retard. Sur le primaire, la vue pg_stat_replication affiche write_lag, flush_lag et replay_lag pour chaque standby ; replay_lag donne une estimation du délai avant qu'un commit devienne visible sur le réplica.

Décidez requête par requête lesquelles peuvent lire des données en retard. Un utilisateur qui vient d'enregistrer un formulaire et recharge la page doit lire depuis le primaire. Un tableau de bord peut lire depuis le réplica.

Basculement : qui décide

PostgreSQL ne détecte pas seul un primaire en panne et ne promeut pas seul un standby ; sa documentation laisse cela à des outils externes. Patroni est un choix courant. Chaque nœud exécute un agent Patroni, et les agents partagent leur état via un stockage de configuration distribué comme etcd :

  • Le primaire détient une clé de leader avec un TTL et la renouvelle à chaque boucle (loop_wait, 10 secondes par défaut).
  • S'il ne peut pas la renouveler pendant ttl secondes (30 par défaut), la clé expire et les réplicas éligibles lancent une course au leader.
  • Le meilleur candidat prend la clé et se promeut ; les autres le suivent.

Les clients trouvent le nouveau primaire via un proxy. L'API REST de Patroni ne répond HTTP 200 à GET /primary que sur le nœud qui détient la clé de leader ; HAProxy peut donc envoyer les écritures au nœud qui répond oui :

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 second listener sur le port 5001 vérifie /replica?lag=16MB, ce qui écarte un réplica qui a plus de 16 Mo de WAL de retard. Faites tourner HAProxy sur plusieurs machines, sinon il devient le point de défaillance unique que vous cherchiez à supprimer.

Split-brain : ce qui empêche deux primaires

Deux primaires qui acceptent des écritures, ce sont deux historiques qui divergent, et l'un des deux sera jeté. La documentation de PostgreSQL appelle le remède STONITH : il faut signifier à l'ancien primaire qu'il ne l'est plus. Patroni empile plusieurs défenses :

  • Le bail. Un primaire qui ne peut pas mettre à jour la clé de leader cesse d'accepter des écritures : Patroni arrête ou rétrograde PostgreSQL. Si le stockage lui-même est injoignable plus longtemps que retry_timeout, Patroni rétrograde le primaire, sauf si son mode failsafe DCS est activé.
  • Un watchdog. Si le processus Patroni se bloque, un watchdog Linux redémarre la machine. Patroni le règle pour qu'il se déclenche 5 secondes avant l'expiration du TTL.
  • Un quorum. etcd a besoin de la majorité de ses membres, deux sur trois, pour accepter une écriture. Trois membres dans trois zones survivent à la perte d'une zone, et une minorité isolée ne peut élire personne.
  • Un seul processus aux commandes. Seul Patroni doit démarrer PostgreSQL. Un service système qui redémarre Postgres tout seul après un crash peut faire revenir un second primaire.

Quand l'ancien primaire revient, il doit rejoindre le cluster en tant que réplica. pg_rewind resynchronise son répertoire de données avec le nouveau primaire sans copier les blocs inchangés ; Patroni l'exécute quand use_pg_rewind est activé.

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

Avec synchronous_mode activé, Patroni ne promeut qu'un standby dont il sait qu'il possède tous les commits signalés comme réussis. Le compromis porte sur la disponibilité : si le primaire tombe alors qu'aucun standby synchrone n'est connecté, Patroni ne promeut personne, et les écritures restent indisponibles jusqu'à ce que quelqu'un intervienne.

La réplication n'est pas une sauvegarde

Un DROP TABLE atteint chaque réplica en quelques millisecondes. Gardez un archivage WAL continu et des sauvegardes de base régulières : ils permettent une restauration à un instant donné, à n'importe quel moment postérieur à la sauvegarde de base. Testez les restaurations à intervalles réguliers.

Le diagramme

Trois zones de disponibilité avec des nœuds PostgreSQL. HAProxy envoie les écritures du service de commandes à pg-b, le primaire, et les lectures à pg-c, un réplica asynchrone. pg-b envoie le WAL à pg-a en synchrone et à pg-c en asynchrone, et tient la clé de leader dans etcd.

11 composants · 8 connexions

Ouvrir dans Archboard
Contenu du diagramme
  • 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 cluster PostgreSQL sur trois zones géré par Patroni : les écritures vont au primaire via HAProxy, un standby synchrone sert au basculement, un réplica asynchrone sert les lectures, une clé de leader vit dans etcd et une archive WAL permet la restauration à un instant donné.

Comment le lire :

  • Écritures. Le service de commandes se connecte à HAProxy. Le port 5000 mène à pg-b, le seul nœud qui répond 200 à /primary.
  • Lectures. Le port 5001 mène à pg-c, un réplica asynchrone dans une autre zone. pg-a ne sert pas de lectures.
  • Réplication. pg-b envoie le WAL à pg-a en synchrone (trait plein) et à pg-c en asynchrone (tirets).
  • Basculement. pg-b renouvelle la clé de leader dans etcd. Si la clé expire, pg-a, le standby synchrone, se lance dans la course. pg-c porte le tag nofailover.
  • Restauration. pg-b archive le WAL et les sauvegardes de base dans un stockage objet.
  • Objectifs. La note en haut à gauche contient le RPO, le RTO, le budget de retard et la durée de conservation des sauvegardes.

Ce qu'il faut étiqueter sur un diagramme de réplication

  • Le mode sur chaque flèche de réplication, synchrone ou asynchrone, et le budget de retard des réplicas asynchrones.
  • Le rôle de chaque réplica : candidat au basculement, réplica de lecture, ou les deux.
  • L'emplacement du verrou de leader et son TTL, ou le service managé qui joue ce rôle.
  • La façon dont les clients trouvent le primaire : un proxy, un nom DNS ou une IP virtuelle.
  • Les domaines de panne : les zones de disponibilité sous forme de cadres, y compris l'endroit où tournent les membres du consensus.
  • Les objectifs de RPO et de RTO, pour que les relecteurs puissent confronter la conception à ces cibles.

Ce que les services managés font pour vous

Les bases de données managées répondent aux mêmes questions, mais certaines réponses sont imposées. Dans une instance Amazon RDS Multi-AZ, le standby est synchrone, se trouve dans une autre zone et ne peut pas servir de lectures. Un basculement fait pointer l'enregistrement DNS vers le standby et prend généralement entre 60 et 120 secondes. Un cluster Multi-AZ dispose de deux standbys lisibles, utilise une réplication semi-synchrone et bascule généralement en moins de 35 secondes. Sur le diagramme, l'endpoint remplace HAProxy et AWS remplace Patroni et etcd. Vous étiquetez quand même le retard, le routage des lectures et les objectifs.

Testez le basculement

Changez de primaire volontairement, par exemple à chaque fenêtre de maintenance avec patronictl switchover. La documentation de PostgreSQL recommande de basculer régulièrement, car cela teste aussi le mécanisme de basculement. Mesurez combien de temps les écritures échouent, vérifiez que HAProxy suit et confirmez qu'aucun commit acquitté ne manque. Pour un basculement entre régions, consultez le diagramme d'architecture AWS multirégion.

Le construire dans Archboard

  1. Connectez-vous avec Google ou GitHub et créez un diagramme.
  2. Ajoutez trois nœuds Cadre du pack Base pour les zones de disponibilité, et dans chacun un nœud Postgres du pack Données : pg-a (standby synchrone), pg-b (primaire) et pg-c (réplica asynchrone).
  3. Au-dessus des zones, ajoutez un Conteneur du pack Modèle C4 pour le service de commandes et un Répartiteur de charge du pack Réseau pour HAProxy.
  4. En dessous, ajoutez un Conteneur du pack Modèle C4 pour etcd et un Magasin d'objets du pack Données pour l'archive WAL.
  5. Étiquetez les liens : « writes :5000 », « reads :5001 », « WAL (sync) » en trait plein, « WAL (async) » en tirets, « leader key, TTL 30 s » de pg-b vers etcd, et un lien en tirets de pg-a vers etcd pour la course au leader.
  6. Ajoutez un bloc Markdown du pack Base avec le RPO, le RTO, le budget de retard et la conservation des sauvegardes.

L'exemple utilise 11 nœuds de diagramme et reste dans les limites de l'offre Free. Pour garder le diagramme à jour quand le cluster évolue, consultez Des diagrammes d'architecture qui ne se périment pas, et pour la méthode générale, l'exemple de diagramme de conception de systèmes.

FAQ

Un réplica en lecture est-il une sauvegarde ? Non. Les suppressions et les migrations ratées sont répliquées elles aussi. Les sauvegardes avec archivage WAL permettent de revenir à un moment antérieur à l'erreur.

La réplication synchrone garantit-elle zéro perte de données ? Pour les commits qu'un standby synchrone a confirmés, oui, tant que le basculement ne promeut que ce standby. Les commits acceptés alors qu'aucun standby synchrone n'était connecté n'existent que sur le primaire.

Pourquoi trois membres etcd et pas deux ? etcd a besoin d'une majorité pour accepter des écritures. Avec deux membres, on ne peut en perdre aucun ; avec trois, on survit à la perte d'un membre et l'un des côtés garde la majorité en cas de coupure réseau.

Puis-je utiliser le DNS plutôt qu'un proxy pour trouver le primaire ? Oui, c'est ce que font les services managés. Gardez des TTL DNS courts et assurez-vous que les clients ne mettent pas les résolutions en cache trop longtemps ; AWS recommande un TTL de cache DNS de la JVM de 60 secondes au maximum.

À quelle fréquence tester le basculement ? Assez régulièrement pour que l'équipe lui fasse confiance, par exemple un switchover planifié à chaque fenêtre de maintenance, plus un test de panne non planifiée en préproduction.

Commencez gratuitement — un siège, sans carte bancaire.

Articles associés

← Retour au blog

Ouvrez un canevas en une minute

Connectez-vous avec Google ou GitHub. Gratuit pour un siège. Passez à un forfait supérieur quand l’équipe vous rejoint.