Configuration de la Réplication Synchrone pour la Haute Disponibilité dans PostgreSQL

Apprenez à configurer une haute disponibilité PostgreSQL avec zéro perte de données (RPO=0) en utilisant la réplication synchrone en continu. Ce tutoriel étape par étape couvre les configurations essentielles pour `wal_level`, les slots de réplication, `pg_basebackup`, et le réglage correct des paramètres `synchronous_commit` sur les serveurs primaire et de secours pour garantir la durabilité des transactions dans les environnements critiques.

Configuration de la Réplication Synchrone pour la Haute Disponibilité dans PostgreSQL

Configurer PostgreSQL pour la haute disponibilité (HA) commence généralement par une question difficile : combien de données pouvez-vous vous permettre de perdre si le serveur primaire disparaît juste après une validation ? Avec la réplication asynchrone en continu normale, la réponse est "peut-être un peu". Le primaire peut indiquer à l'application qu'une transaction a été validée avant que le serveur de secours n'ait reçu ou rejoué l'enregistrement WAL. Si le primaire échoue pendant cette courte fenêtre, le serveur de secours promu peut ne pas contenir les dernières transactions validées.

La réplication synchrone en continu change ce compromis. PostgreSQL attend un ou plusieurs serveurs de secours nommés avant de signaler le succès de la validation. Selon le niveau de synchronous_commit, le serveur de secours peut seulement avoir besoin d'écrire le WAL dans le système d'exploitation, de le vider sur un stockage durable, ou de le rejouer pour que les requêtes sur le serveur de secours puissent le voir. Cela peut vous donner un RPO de zéro pour les transactions validées, mais cela signifie également que le chemin d'écriture dépend désormais du réseau et de la santé du serveur de secours.

Ce compromis est important. La réplication synchrone est adaptée à un petit ensemble de données où la perte d'une seule transaction accusée réception est inacceptable : paiements, soldes de comptes, réservations d'inventaire, état des commandes, pistes d'audit. Elle est souvent inadaptée pour les journaux d'événements à volume élevé, les données de clics, les métriques, ou les charges de travail où la disponibilité et la latence sont plus importantes que la durabilité parfaite entre les nœuds. Avant de l'activer globalement, décidez quelle partie de votre charge de travail en a réellement besoin.

Prérequis

Avant de commencer, assurez-vous d'avoir deux serveurs PostgreSQL configurés (Primaire et Secours) exécutant des versions majeures identiques de PostgreSQL. Les deux serveurs doivent avoir une connectivité réseau. Pour ce guide, nous supposons :

  • Nom d'hôte/IP du Primaire : pg_primary
  • Nom d'hôte/IP du Secours : pg_standby
  • Utilisateur de Réplication : repl_user
  • Nom de la Base de Données : mydb

Vous avez également besoin d'une sauvegarde fonctionnelle et d'une fenêtre de maintenance pour la sauvegarde de base initiale. Les exemples supposent PostgreSQL 12 ou plus récent, où le mode secours est contrôlé avec standby.signal et les paramètres de connexion sont généralement écrits par pg_basebackup -R.

Étape 1 : Configuration du Serveur Primaire

Le serveur primaire nécessite des paramètres spécifiques pour activer la réplication en continu et gérer le journal des transactions (WAL) requis par les validations synchrones.

A. Ajustement de postgresql.conf sur le Primaire

Modifiez le fichier postgresql.conf du serveur primaire. Les paramètres suivants sont obligatoires pour la réplication en continu :

# --- Requis pour la Réplication ---
listen_addresses = '*'         # Permet les connexions depuis le secours
wal_level = replica            # Doit être 'replica' ou supérieur (ex. 'logical')
max_wal_senders = 10           # Nombre max de connexions simultanées des secours
max_replication_slots = 10     # Slots nécessaires pour les flux de réplication persistants

# --- Essentiel pour la Validation Synchrone ---
synchronous_standby_names = 'FIRST 1 (standby1)' # Spécifie les secours requis par application_name

# --- Optionnel mais Recommandé ---
wal_log_hints = on             # Recommandé pour une réplication plus sûre, bien qu'il augmente le volume WAL
shared_preload_libraries = 'pg_stat_statements' # Si utilisation de la surveillance

Explication des Paramètres Clés :

  • wal_level = replica : Cela garantit que suffisamment d'informations sont écrites dans le WAL pour permettre à un serveur de secours de reconstruire l'état de la base de données. Pour les validations synchrones, ce niveau est le minimum requis.
  • synchronous_standby_names : C'est le paramètre central pour définir quels serveurs de secours doivent accuser réception des écritures. Les noms ici sont des valeurs application_name de connexion de réplication, pas des noms de slots de réplication. FIRST 1 (standby1) signifie que PostgreSQL attend le premier serveur de secours synchrone disponible de cette liste. ANY 1 (standby1, standby2) signifie que n'importe lequel des serveurs de secours listés peut satisfaire la validation.

B. Configuration de l'Authentification Basée sur l'Hôte (pg_hba.conf)

Le serveur primaire doit autoriser l'utilisateur de réplication du ou des serveurs de secours à se connecter à des fins de réplication.

Ajoutez une entrée dans pg_hba.conf sur le primaire :

# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     repl_user       pg_standby/32           scram-sha-256

Remplacez pg_standby/32 par l'adresse IP réelle ou le sous-réseau de votre serveur de secours.

C. Création du Slot de Réplication et de l'Utilisateur

Connectez-vous à PostgreSQL sur le serveur primaire pour créer l'utilisateur nécessaire et le slot de réplication.

1. Créer l'Utilisateur de Réplication :

CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'a_strong_password';

2. Créer le Slot de Réplication :

Ce slot garantit que les segments WAL sont conservés jusqu'à ce que le secours confirme leur réception, empêchant le secours de prendre tellement de retard qu'il nécessite une nouvelle sauvegarde de base. Les slots sont utiles, mais ils peuvent aussi remplir les disques si un secours est hors service pendant une longue période, donc surveillez le WAL retenu.

SELECT pg_create_physical_replication_slot('standby1_slot');

Le nom du slot ne doit pas nécessairement correspondre à synchronous_standby_names. Dans cet exemple, standby1 est le application_name utilisé pour la sélection du secours synchrone, tandis que standby1_slot est le slot de réplication physique utilisé pour la rétention WAL.

D. Redémarrage du Primaire

Appliquez toutes les modifications de configuration en redémarrant le service PostgreSQL sur le serveur primaire.

sudo systemctl restart postgresql

Étape 2 : Configuration du Serveur de Secours

Le serveur de secours est configuré pour diffuser les enregistrements WAL du primaire en utilisant une configuration de récupération.

A. Sauvegarde de Base

Avant de démarrer la diffusion, le secours a besoin d'une copie complète du répertoire de données du primaire. Arrêtez d'abord PostgreSQL sur le secours.

sudo systemctl stop postgresql

Effectuez la sauvegarde de base en utilisant pg_basebackup. Remplacez les chemins et les détails de connexion si nécessaire :

# Exemple utilisant l'utilitaire pg_basebackup
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
  • -D : Le répertoire de données cible sur le secours.
  • -U : L'utilisateur de réplication.
  • -P : Afficher la progression.
  • -Xs : Inclure les fichiers WAL nécessaires pendant la sauvegarde de base.
  • -R : Créer automatiquement le fichier standby.signal et générer les paramètres de connexion nécessaires dans postgresql.auto.conf (ou la configuration de récupération).

B. Configuration de postgresql.conf sur le Secours

Sur le secours, assurez-vous que PostgreSQL sait comment se reconnecter au primaire. Le détail clé pour la réplication synchrone est application_name ; il doit correspondre au nom listé dans synchronous_standby_names.

# --- Requis sur le Secours ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=a_strong_password application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on          # Permet les requêtes en lecture pendant le mode récupération/secours

C. Démarrage du Secours

Démarrez le service PostgreSQL sur le serveur de secours.

sudo systemctl start postgresql

Étape 3 : Vérification et Test de la Validation Synchrone

Une fois les deux serveurs en cours d'exécution, vérifiez la connexion puis testez le comportement synchrone.

A. Vérification du Statut de la Réplication

Connectez-vous à la base de données primaire et vérifiez la vue pg_stat_replication :

SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;

Vous devriez voir une entrée pour standby1 avec sync_state comme sync. Si elle affiche potential, le secours est connecté mais n'est pas actuellement celui qui satisfait les validations synchrones. Si elle affiche async, PostgreSQL ne le traite pas comme un secours synchrone ; vérifiez l'orthographe de application_name et synchronous_standby_names.

B. Test de la Validation Synchrone

Le paramètre global qui dicte à quel point PostgreSQL attend est synchronous_commit. Pour RPO=0, vous devez utiliser une valeur qui force la synchronisation.

1. Définition du Comportement Global

Si vous avez configuré synchronous_standby_names sur le primaire comme indiqué à l'Étape 1, la valeur par défaut synchronous_commit = on attend que le secours synchrone ait vidé l'enregistrement WAL sur un stockage durable. remote_write attend que le secours ait écrit l'enregistrement WAL dans le système d'exploitation, ce qui est généralement plus rapide mais moins fort si l'hôte secours plante avant le vidage. remote_apply attend que le secours ait rejoué la transaction, ce qui est utile lorsque votre application lit depuis le secours immédiatement après avoir écrit sur le primaire.

Pour la plupart des configurations HA à zéro perte de données, on est le point de départ pratique. Utilisez remote_apply uniquement lorsque le comportement de lecture après écriture sur le secours est suffisamment important pour justifier la latence supplémentaire.

# Dans postgresql.conf sur le Primaire
synchronous_commit = on

Avertissement : La validation synchrone peut augmenter sensiblement la latence d'écriture par rapport aux modes asynchrones (off ou local). La latence supplémentaire provient des allers-retours réseau, de la vitesse d'écriture WAL du secours, et, pour remote_apply, de la vitesse de rejeu.

2. Test dans une Transaction

Pour tester transactionnellement (sans nécessiter une modification de configuration globale), vous pouvez le définir par session ou transaction :

-- Connectez-vous au Primaire

BEGIN;
SET LOCAL synchronous_commit = on;

INSERT INTO ventes (article, montant) VALUES ('Widget A', 100);
-- Cette INSERT prépare le WAL qui doit être accusé réception par le secours synchrone.

COMMIT;
-- La COMMIT réussit seulement après que le secours accuse réception de l'écriture WAL.

Si aucun secours synchrone configuré n'est disponible au moment de la validation, la validation attend. C'est le but de la fonctionnalité, mais cela peut surprendre les équipes lors d'une panne : le primaire peut encore être opérationnel, mais les écritures semblent figées car PostgreSQL attend un accusé de réception synchrone. Il n'y a pas de paramètre PostgreSQL général qui "revient" automatiquement à la validation asynchrone lorsqu'un secours synchrone disparaît. Si vous voulez ce comportement, vos outils HA doivent modifier synchronous_standby_names, ou vous devez avoir un manuel d'exploitation pour le faire manuellement après avoir décidé que la disponibilité est plus importante que la perte de données nulle.

Un Modèle Plus Sûr à Deux Secours

Un seul secours synchrone offre une durabilité solide lorsque tout est sain, mais il crée également un point unique de disponibilité en écriture. Si ce secours est hors service, lent ou isolé du primaire, les validations attendent. En production, un modèle courant consiste à exécuter au moins deux secours et à exiger un accusé de réception synchrone :

synchronous_standby_names = 'ANY 1 (standby1, standby2)'

Avec cette configuration, l'un ou l'autre des secours peut satisfaire la validation. Si standby1 redémarre, standby2 peut toujours accuser réception des écritures. Vous devez toujours surveiller les deux réplicas, car une panne prolongée sur un secours peut entraîner la rétention d'une grande quantité de WAL par son slot de réplication, mais le primaire est moins susceptible de se bloquer en raison d'une seule panne de secours.

Exiger deux accusés de réception est possible :

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

C'est un choix de durabilité plus strict. Il est généralement réservé aux environnements avec des liaisons à très faible latence et une raison claire d'exiger plus d'une copie distante avant la validation. Pour de nombreuses bases de données d'application, "n'importe lequel de deux secours proches" est le meilleur équilibre.

Que Surveiller Après l'Activation

Ne vous arrêtez pas à "le secours se connecte." La réplication synchrone peut fonctionner techniquement tandis que la latence côté utilisateur se dégrade. Surveillez ces signaux après le déploiement :

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

Sur le primaire, sync_state = 'sync' vous indique quel secours est actuellement synchrone. write_lag, flush_lag et replay_lag aident à expliquer où le temps est passé. Si write_lag est élevé, suspectez le réseau ou la pression d'écriture WAL du secours. Si flush_lag est élevé, suspectez le stockage. Si seul replay_lag est élevé, le secours reçoit peut-être le WAL mais l'applique lentement en raison d'E/S, de CPU, de verrous ou de requêtes longues sur le secours.

Surveillez également la rétention des 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 réplication protège un secours, mais il ne protège pas votre disque. Si un slot est inactif et que le WAL retenu continue de croître, soit réparez le secours rapidement, soit supprimez le slot après avoir confirmé que vous n'en avez plus besoin.

Un Plan de Déploiement Pratique

Pour un système de production occupé, traitez la réplication synchrone comme un changement progressif plutôt qu'une simple modification de configuration en une ligne.

D'abord, construisez le secours de manière asynchrone et laissez-le fonctionner pendant un certain temps. Confirmez qu'il peut suivre pendant les périodes d'écriture de pointe. S'il prend du retard de manière asynchrone, il nuira à la latence de validation lorsqu'il deviendra synchrone.

Ensuite, définissez application_name et vérifiez que le primaire voit le secours exactement comme vous l'attendez dans pg_stat_replication. Les fautes d'orthographe sont courantes ici car synchronous_standby_names correspond au application_name d'exécution, pas au nom d'hôte et pas au slot.

Troisièmement, activez la réplication synchrone pendant une fenêtre à faible trafic et surveillez la latence de validation du côté de l'application. Les métriques PostgreSQL peuvent sembler correctes tandis que le pool de connexions de l'application s'accumule car les transactions maintiennent désormais les connexions un peu plus longtemps.

Enfin, notez la décision en cas d'échec. Si le secours synchrone est absent et que le primaire attend les validations, qui est autorisé à assouplir synchronous_standby_names ? Dans quelles conditions ? Comment vérifierez-vous si l'ancien primaire ou l'ancien secours contient les données les plus récentes avant de reconnecter les nœuds ? Ce sont des décisions opérationnelles, pas seulement des paramètres de base de données.

Meilleures Pratiques pour la HA Synchrone

  • Utilisez des Secours Dédiés : N'affectez à votre liste de réplication synchrone que des secours physiquement proches (faible latence) du primaire. Une latence élevée se répercutera directement sur le temps de validation.
  • Surveillez le Retard de Réplication : Même en mode synchrone, surveillez le retard du secours. Un secours lent qui est toujours techniquement 'sync' mais qui prend trop de temps pour traiter le WAL peut toujours avoir un impact sur l'expérience utilisateur.
  • Planifiez le Compromis de Disponibilité : Décidez à l'avance si un opérateur peut temporairement retirer un secours manquant de synchronous_standby_names lors d'un incident.
  • Utilisez Plusieurs Secours : Pour une meilleure disponibilité en écriture, configurez synchronous_standby_names = 'ANY 1 (standby1, standby2)' afin que l'un ou l'autre des secours puisse accuser réception des validations.