LOXODATA

Réplication logique et Patroni

2024-06-24   1549 mots, 8 minutes de lecture

Dans ce précédent article, nous vous présentions l’évolution des fonctionnalités de la réplication logique apportées par les différentes versions de PostgreSQL et dans cet autre article, nous avions vu la mise en place d’une réplication logique.

Réplication logique et haute disponibilité

Dans le cadre de la haute disponibilité en réplication physique avec un primaire et plusieurs secondaires, la bascule lors d’un incident va bloquer la réplication logique.

Pour rappel, la mise en place de la réplication logique implique de créer un slot de réplication sur le primaire, ce slot permettant de conserver les fichiers WAL requis par les souscriptions (un slot de réplication par souscription).

Réplication logique

Lors d’une bascule, le slot de réplication logique créé initialement sur le primaire ne sera pas présent sur le secondaire promu. La souscription abonnée à cette publication se retrouvera alors bloquée car ne recevant plus les changements émis par la publication, le temps de recréer un slot de réplication logique, avec le risque de perdre des données pendant la bascule.

Nativement, PostgreSQL n’offre pas encore de solution intégrée pour la gestion des slots de réplication pendant une bascule. Mais la prochaine version majeure de PostgreSQL, la version 17, va commencer à intégrer des changements pour y pallier.

En attendant, vous pouvez utiliser l’extension pg_failover_slots. Sinon, si vous utilisez Patroni, il est possible de configurer ce dernier pour gérer les slots de réplication. C’est l’objet de la suite de l’article.

Patroni, c’est quoi déjà ?

Patroni est un framework de gestion de cluster PostgreSQL pour assurer la haute disponibilité de service, en assurant une bascule automatique et offrant une gestion de la configuration centralisée.

La mise en place d’un cluster Patroni pourra faire l’objet d’un prochain article, mais pour l’instant nous allons considérer que vous possédez déjà un cluster Patroni opérationnel avec une instance primaire, et deux instances secondaires.

Cluster Patroni

Configuration

Première étape à la mise en place d’une réplication logique avec Patroni, il faut passer le paramètre wal_level à logical afin d’ajouter dans les fichiers WAL toutes les informations nécessaires au support du décodage logique.

Nous utiliserons la commande patronictl qui permet de gérer le cluster Patroni et vérifier son état.

Vérifier le statut et la topologie actuelle d’un cluster avec l’option topology:

postgres@pgdeb01:~$ patronictl -c /etc/patroni/patroni.yml topology

+ Cluster: loxodemo (7382147555638198668) ------+----+-----------+
| Member    | Host        | Role    | State     | TL | Lag in MB |
+-----------+-------------+---------+-----------+----+-----------+
| pgdeb01   | 10.200.0.11 | Leader  | running   |  1 |           |
| + pgdeb02 | 10.200.0.12 | Replica | streaming |  1 |         0 |
| + pgdeb03 | 10.200.0.13 | Replica | streaming |  1 |         0 |
+-----------+-------------+---------+-----------+----+-----------+

Consulter la configuration du cluster avec l’option show-config :

postgres@pgdeb01:~$ patronictl -c /etc/patroni/patroni.yml show-config

loop_wait: 10
maximum_lag_on_failover: 1048576
postgresql:
  parameters:
    archive_command: pgbackrest --stanza=loxodemo archive-push %p
    archive_mode: 'on'
  recovery_conf:
    restore_command: pgbackrest --stanza=loxodemo archive-get %f %p
  use_pg_rewind: false
  use_slots: false
retry_timeout: 10
ttl: 30

Nous modifions le paramètre PostgreSQL wal_level à logical avec l’option edit-config :

postgres@pgdeb01:~$ patronictl -c /etc/patroni/config.yml edit-config
---
+++
@@ -4,6 +4,7 @@
   parameters:
     archive_command: pgbackrest --stanza=loxodemo archive-push %p
     archive_mode: 'on'
+    wal_level: 'logical'
   recovery_conf:
     restore_command: pgbackrest --stanza=loxodemo archive-get %f %p
   use_pg_rewind: false

Apply these changes? [y/N]: y
Configuration changed

Le changement de ce paramètre nécessite un redémarrage des instances PostgreSQL. Le redémarrage est indiqué par Patroni dans la sortie de l’option list, dans la colonne Pending restart :

postgres@pgdeb01:~$ patronictl -c /etc/patroni/config.yml list
+ Cluster: loxodemo (7382147555638198668) ----+----+-----------+-----------------+-----------------------------+
| Member  | Host        | Role    | State     | TL | Lag in MB | Pending restart | Pending restart reason      |
+---------+-------------+---------+-----------+----+-----------+-----------------+-----------------------------+
| pgdeb01 | 10.200.0.11 | Leader  | running   |  1 |           | *               | wal_level: replica->logical |
| pgdeb02 | 10.200.0.12 | Replica | streaming |  1 |         0 | *               | wal_level: replica->logical |
| pgdeb03 | 10.200.0.13 | Replica | streaming |  1 |         0 | *               | wal_level: replica->logical |
+---------+-------------+---------+-----------+----+-----------+-----------------+-----------------------------+

Il faut alors procéder au redémarrage en utilisant l’option restart :

postgres@pgdeb01:~$ patronictl -c /etc/patroni/config.yml restart --force loxodemo
+ Cluster: loxodemo (7382147555638198668) ----+----+-----------+-----------------+-----------------------------+
| Member  | Host        | Role    | State     | TL | Lag in MB | Pending restart | Pending restart reason      |
+---------+-------------+---------+-----------+----+-----------+-----------------+-----------------------------+
| pgdeb01 | 10.200.0.11 | Leader  | running   |  1 |           | *               | wal_level: replica->logical |
| pgdeb02 | 10.200.0.12 | Replica | streaming |  1 |         0 | *               | wal_level: replica->logical |
| pgdeb03 | 10.200.0.13 | Replica | streaming |  1 |         0 | *               | wal_level: replica->logical |
+---------+-------------+---------+-----------+----+-----------+-----------------+-----------------------------+
Success: restart on member pgdeb01
Success: restart on member pgdeb02
Success: restart on member pgdeb03

Il peut être intéressant aussi de revoir la configuration des paramètres suivants, qui demandent aussi un redémarrage de PostgreSQL :

max_replication_slots: 10
max_wal_senders: 10
max_worker_processes: 10
  • max_replication_slots (défaut à 10) indiquant le nombre de slots de réplication maximum (réplication logique et physique). Doit être au moins égal au nombre de réplica et de souscription (pour chaque souscription, un apply worker et plusieurs tablesync worker);
  • max_wal_senders (défaut à 10) doit être équivalent ou plus au nombre de slots de réplication, plus le nombre de réplica présents;
  • max_worker_processes (défaut à 8) doit être supérieur aux valeurs précédentes, et supérieures sur les réplicas;

Du côté des souscriptions, il peut aussi être utile de modifier les paramètres suivants propre aux souscriptions :

max_logical_replication_workers: 4
max_sync_workers_per_subscription: 2

Il convient à présent de déclarer les slots de réplication dans la configuration de Patroni, afin de les rendre permanents et qu’ils soient préservés lors d’une bascule.

La déclaration dans la configuration va créer les slots de réplication logique sur tous les nœuds secondaires, et Patroni se chargera d’avancer leur position via l’appel de la fonction pg_replication_slot_advance. Le paramètre hot_standby_feedback est activé aussi par Patroni sur les secondaires.

postgres@pgdeb01:~$ patronictl -c /etc/patroni/config.yml edit-config
---
+++
@@ -8,6 +8,11 @@
   recovery_conf:
     restore_command: pgbackrest --stanza=loxodemo archive-get %f %p
   use_pg_rewind: false
-  use_slots: false
+  use_slots: true
 retry_timeout: 10
 ttl: 30
+slots:
+  logical_slot_emp:
+    database: employees
+    plugin: pgoutput
+    type: logical

Apply these changes? [y/N]: y
Configuration changed

Un exemple de configuration :

loop_wait: 10
maximum_lag_on_failover: 1048576
postgresql:
  parameters:
    archive_command: pgbackrest --stanza=loxodemo archive-push %p
    archive_mode: 'on'
    wal_level: logical
  recovery_conf:
    restore_command: pgbackrest --stanza=loxodemo archive-get %f %p
  use_pg_rewind: false
  use_slots: true
retry_timeout: 10
slots:
  logical_slot_emp:
    database: employees
    plugin: pgoutput
    type: logical
ttl: 30
  • use_slots permet d’activer les slots de réplication;
  • section slots permet de définir les slots de réplication permanents;
  • logical_slot_emp correspond au nom du slot de réplication;
  • database est le nom de la base de données depuis laquelle la publication est créée;
  • plugin est le plugin de décodage utilisé (le plugin pgoutput est le plugin natif de PostgreSQL);
  • type est le type de réplication, ici logical (sinon physical);

Il est aussi possible de créer le slot de réplication logique auparavant, et de spécifier le nom de ce slot dans la configuration de Patroni. Par contre, la suppression de la définition de ce slot dans la configuration supprimera également le slot.

Attention au nom du slot donné dans la configuration, qui ne doit pas rentrer en conflit avec les slots de réplication physique.

On peut vérifier l’existence des slots de réplications avec la vue pg_replication_slots, en s’intéressant à celui de type logical :

employees=# select * from pg_replication_slots;

    slot_name     |  plugin  | slot_type | datoid | database  | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn | wal_status | safe_wal_size | two_phase | conflicting
------------------+----------+-----------+--------+-----------+-----------+--------+------------+------+--------------+-------------+---------------------+------------+---------------+-----------+-------------
 logical_slot_emp | pgoutput | logical   |  16389 | employees | f         | f      |            |      |          817 | 0/170001C0  | 0/170001F8          | reserved   |               | f         | f
  • catalog_xmin correspondant à la transaction la plus ancienne affectant le catalogue système et requis par le slot;
  • restart_lsn la position du plus ancien WAL requis par la souscription;
  • confirmed_flush_lsn la dernière position reçue et rejouée côté souscription;

Supervision

L’essentiel à suite de la mise en place de cette configuration est de superviser la réplication logique et son état d’avancement.

Pour ce faire, il existe de nombreuses vues système pour la supervision.

Côté publication, nous pouvons utiliser les vues suviantes :

Et côté souscription :

Par exemple avec la vue pg_replication_slots et la requête suivante :

SELECT slot_name,
       active,
       confirmed_flush_lsn,
       pg_current_wal_lsn(),
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_walsize,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS subscriber_lag
FROM pg_replication_slots;


    slot_name                | active | confirmed_flush_lsn | pg_current_wal_lsn | retained_walsize | subscriber_lag
-----------------------------------------+--------+---------------------+--------------------+------------------+----------------
 logical_slot_emp                        | t      | 0/72B96B50          | 0/72B96B50         | 385 kB           | 0 bytes
  • active donnant le status du slot de réplication;
  • confirmed_flush_lsn la dernière position reçue et rejouée côté souscription;
  • retained_walsize la quantité de WAL (en octet) retenue par le slot côté publication
  • subscriber_lag le retard de réplication logique (en octet) entre la publication et la souscription.

Qu’attendre de la version 17 de PostgreSQL ?

Comme nous l’avons vu, les versions actuelles ne prennent pas en charge les bascules des slots de réplication logique, ce qui dans un contexte de haute disponibilité de service assuré par Patroni, rend la mise en place de la réplication logique plus contraignante.

La version 17 de PostgreSQL va permettre de gérer nativement la bascule des slots de réplication et rendre plus aisée l’utilisation de la réplication logique.

Cependant, il reste encore des fonctionnalités attendues comme la réplication des séquences ou des schémas de bases de données, qui doivent se faire manuellement avant la mise en place d’une souscription.