Procédures planifiées de bascules Failover - Failback avec la réplication en continu PostgreSQL (streaming replication)

Introduction

De nombreux articles traitent de la promotion d'un serveur de secours PostgreSQL en serveur principal avec la commande pg_ctl promote. Beaucoup d'entre eux concluent que le serveur de secours doit être reconstruit lorsque le serveur principal est de retour.

Qu'en est-il d'une maintenance programmée, maintenance incluant des arrêts, sur le serveur primaire ? Doit-on reconstruire le système primaire/standby ? Évidemment la réponse est : NON.

Postgres Failover Failback

En supposant qu'il y a suffisamment d'espace disque pour conserver les fichiers WAL pendant la tâche de maintenance, en gérant prudemment le point de réplication (replication slot) et l'option de récupération recovery_timeline_target durant les procédures failover/failback, il n'y a pas besoin de reconstruire le système standby impliquant des sauvegardes/restaurations de bases de données et/ou d'utiliser pg_rewind.

src="./images/icon-lazyload.png" data-afwk-img Postgres Failover Failback - TimeLine ID

Pour plus d'informations sur l'installation et la configuration d'un serveur standby PostgreSQL avec la réplication en continu (streaming replication) : Réplication en continu avec PostgreSQL 9.6, 10 et 11 (Streaming replication) - Bases de données PostgreSQL Standby

Les applications clientes se connectent à la base de données avec le nom de serveur logique LSRVSQLPAC (adresse IP virtuelle) sur le port 30001. Le serveur DNS résout la bonne adresse physique en fonction du contexte (failover, failback...).

Postgres Failover Failback- Résolution adresse logique

Avant de réaliser une bascule, les serveurs doivent être vérifiés et préparés.

Voyons comment faire correctement les bascules failover/failback d'un serveur PostgreSQL lors d'une maintenance planifiée sur le serveur primaire sans perdre de transactions et sans avoir besoin de reconstuire quoi que ce soit.

Sur chaque serveur, le contexte est le suivant :

Système d'exploitation : Ubuntu 18.04 Distribution (PostgreSQL 9.6.15) : /opt/postgres/pgsql-9.6/bin $PATH : /opt/postgres/pgsql-9.6/bin:$PATH $PGLIB : /opt/postgres/pgsql-9.6/lib Port : 30001 $PGDATA : /sqlpac/postgres/srvpg1 $CFG : /opt/postgres/dba/srvpg1/cfg Fichiers de configuration : $CFG/postgresql.conf $CFG/pg_hba.conf $CFG/pg_ident.conf Contrôle du serveur PostgreSQL :
pg_ctl start|stop|restart…  -D $CFG
Compte PostgreSQL de la réplication (role) : repmgr Nom du point de réplication (Replication slot name) : standby1
La procédure ci-dessous n'est valable que pour les versions 9, 10 et 11 de PostgreSQL. Pour les versions 12 et supérieures, la procédure est légèrement différente et non abordée ici.

Préparer le serveur primaire VPSFRSQLPAC1 à devenir un serveur standby

En avance, sur le serveur primaire, le paramètre hot_standby est défini à on (ce paramètre est ignoré quand il s'agit d'un serveur primaire).

VPSFRSQLPAC1 : postgresql.conf
hot_standby = on

Préparer le serveur de standby VPSFRSQLPAC2 à devenir un serveur primaire

Sur le serveur de standby, vérifier que les paramètres nécessaires à un serveur primaire sont définis de manière appropriée (valeurs minimales...) :

VPSFRSQLPAC2 : postgresql.conf
listen_addresses = '*'
wal_level = replica 
max_wal_senders = 3
max_replication_slots = 3

Si des paramètres statiques doivent être ajustés, modifier les valeurs et éventuellement redémarrer le serveur de standby (optionnel) :

postgres@vpsfrsqlpac2$ pg_ctl restart -D $CFG

Vérifier que le compte PostgreSQL de la réplication est défini dans le fichier d'authentification pg_hba.conf du serveur de standby avec l'adresse IP du serveur primaire actuel, cette entrée autorisera les connexions depuis VPSFRSQLPAC1 lorsque celui-ci deviendra le serveur de standby dans une prochaine étape.

VPSFRSQLPAC2 : pg_hba.conf
host    replication     repmgr          79.xxx.xxx.xxx/32        md5

Recharger la configuration si l'entrée a dû être ajoutée :

postgres@vpsfrsqlpac2$ pg_ctl reload -D $CFG

Failover - Bascule sur le site de secours

Failover - Étape 1 : Stopper les applications, sauvegarder la base puis finalement éteindre la base sur le serveur primaire VPSFRSQLPAC1

postgres@vpsfrsqlpac1$  pg_ctl stop -D $CFG

À ce stade, désactiver tous les scripts qui pourraient redémarrer la base sur VPSFRSQLPAC1 (scripts shell de démarrage...). La maintenance peut à présent démarrer sur le serveur VPSFRSQLPAC1.

Failover - Étape 2 : Promotion du serveur de standby en serveur primaire

Lancer pg_ctl promote pour promouvoir le serveur de standby sur VPSFRSQLPAC2 en serveur primaire.

postgres@vpsfrsqlpac2$  pg_ctl promote -D $CFG

server promoting

Vérifier le fichier de log :

2019-11-22 16:23:20.394 CET LOG:  received promote request
2019-11-22 16:23:20.394 CET LOG:  redo done at 0/3016198
2019-11-22 16:23:20.394 CET LOG:  last completed transaction was at log time 2019-11-22 16:22:12.212852+01
2019-11-22 16:23:20.407 CET LOG:  selected new timeline ID: 2
2019-11-22 16:23:20.600 CET LOG:  archive recovery complete
2019-11-22 16:23:20.620 CET LOG:  MultiXact member wraparound protections are now enabled
2019-11-22 16:23:20.623 CET LOG:  database system is ready to accept connections
2019-11-22 16:23:20.623 CET LOG:  autovacuum launcher started

Un nouvel identifiant de chronologie (timeline id) est appliqué lors de la promotion. "selected new timeline ID: 2" dans le fichier de log.

postgres@vpsfrsqlpac2$  psql -p 30001

select substr(pg_xlogfile_name(pg_current_xlog_location()), 1, 8);

  substr
----------
 00000002

Le fichier recovery.conf dans le répertoire $PGDATA est renommé en recovery.done lorsque la commande pg_ctl promote a été exécutée.

Lorsque la bascule vers VPSFRSQLPAC2 est réalisée pour la première fois, le point de réplication standby1 n'existe pas ($PGDATA/pg_replslot).

select slot_name, restart_lsn from pg_replication_slots
          
 slot_name | restart_lsn
-----------+-------------
(0 rows)

Dans ce contexte, créer et activer immédiatement le point de réplication standby1 sur le nouveau serveur primaire :

postgres@vpsfrsqlpac2$  psql -p 30001

select * from pg_create_physical_replication_slot('standby1',true);
          
 slot_name | xlog_position
-----------+---------------
 standby1  |0/3016318

L'ancien serveur de standby est à présent le serveur primaire.

Failover - Étape 3 : Bascule des applications sur le nouveau serveur primaire

L'adresse logique LSRVSQLPAC bascule sur l'adresse physique de VPSFRSQLPAC2 dans le serveur DNS. Les applications peuvent se connecter.

Failover - Étape 4 : Conversion de l'ancien serveur primaire en serveur de standby

L'ancien serveur primaire VPSFRSQLPAC1 est à présent de retour et disponible, la tâche de maintenance est terminée.

À cette étape, ne pas démarrer la base de données, une configuration doit être réalisée !

Sur l'ancien serveur primaire, créer le fichier recovery.conf dans le répertoire $PGDATA.

VPSFRSQLPAC1 : $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repmgr host=vpsfrsqlpac2 port=30001 password=***********!'
primary_slot_name = 'standby1'
recovery_target_timeline = 'latest'
  • Le mode standby est défini à on.
  • Les informations de connexion au serveur primaire VPSFRSQLPAC2 sont stockées dans le paramètre primary_conninfo.
  • Le nom du point de réplication précédemment (re)créé sur VPSFRSQLPAC2 est évidemment spécifié (primary_slot_name).
  • Le paramètre recovery_target_timeline est défini à latest : cette information est importante car l'id de chronologie (timeline id) est passé de 1 à 2 sur le nouveau serveur primaire. Si il n'est pas défini à latest, le recovery au démarrage ne bascule pas automatiquement sur le nouvel id de chronologie.

Lorsque le fichier recovery.conf est correctement défini, démarrer l'ancien serveur primaire, vérifier le fichier de log et tester la réplication. Au démarrage, VPSFRSQLPAC1 traite les fichiers WAL de VPSFRSQLPAC2 générés depuis sa promotion en serveur primaire. VPSFRSQLPAC1 devient le serveur de standby.

postgres@vpsfrsqlpac1$  pg_ctl start -D $CFG
2019-11-22 18:33:09.163 CET LOG:  entering standby mode
2019-11-22 18:33:09.176 CET LOG:  consistent recovery state reached at 0/3016208
2019-11-22 18:33:09.176 CET LOG:  invalid record length at 0/3016208: wanted 24, got 0
2019-11-22 18:33:09.177 CET LOG:  database system is ready to accept read only connections
2019-11-22 18:33:09.203 CET LOG:  fetching timeline history file for timeline 2 from primary server
2019-11-22 18:33:09.211 CET LOG:  started streaming WAL from primary at 0/3000000 on timeline 1
2019-11-22 18:33:09.215 CET LOG:  replication terminated by primary server
2019-11-22 18:33:09.215 CET DETAIL:  End of WAL reached on timeline 1 at 0/3016208.
2019-11-22 18:33:09.216 CET LOG:  new target timeline is 2
2019-11-22 18:33:09.217 CET LOG:  restarted WAL streaming at 0/3000000 on timeline 2
2019-11-22 18:33:09.579 CET LOG:  redo starts at 0/3016208

Attendre que la réplication soit complètement synchronisée.

Si le paramètre recovery_target_timeline n'est pas défini à latest, le recovery boucle avec l'erreur ci-dessous lorsqu'il atteint la fin de son id de chronologie, il ne bascule pas sur l'id de chronologie suivant (timeline id 2) :

LOG:  started streaming WAL from primary at 0/36000000 on timeline 1
LOG:  replication terminated by primary server
DETAIL:  End of WAL reached on timeline 1 at 0/362E3B70.
LOG:  restarted WAL streaming at 0/36000000 on timeline 1
LOG:  replication terminated by primary server
DETAIL:  End of WAL reached on timeline 1 at 0/362E3B70.

Dans ce cas de figure, redémarrer simplement la base avec recovery_target_timeline correctement défini à latest : il ne s'agit pas ici d'une erreur irrécupérable pour la base de données.

Maintenant VPSFRSQLPAC1 est le serveur de standby. Les scripts automatiques peuvent être réactivés sur VPSFRSQLPAC1.

Retour arrière (Failback)

La procédure de retour arrière sera similaire à la procédure de bascule sur le site de secours.

Failback - Étape 1 : Stopper les applications, sauvegarder la base de données et éteindre la base sur le serveur primaire VPSFRSQLPAC2.

postgres@vpsfrsqlpac2$  pg_ctl stop -D $CFG

Désactiver tous les scripts qui pourraient redémarrer la base sur VPSFRSQLPAC2 (shells, cron...).

Failback - Étape 2 : Promotion du serveur de standby en serveur primaire

Lancer pg_ctl promote pour promouvoir le serveur de standby VPSFRSQLPAC1 en serveur primaire.

postgres@vpsfrsqlpac1$  pg_ctl promote -D $CFG

server promoting

Vérifier le fichier de log :

2019-11-22 18:59:53.743 CET LOG:  received promote request
2019-11-22 18:59:53.743 CET LOG:  redo done at 0/E98A7988
2019-11-22 18:59:53.743 CET LOG:  last completed transaction was at log time 2019-11-22 18:40:13.347683+01
2019-11-22 18:59:53.752 CET LOG:  selected new timeline ID: 3
2019-11-22 18:59:54.074 CET LOG:  archive recovery complete
2019-11-22 18:59:54.092 CET LOG:  MultiXact member wraparound protections are now enabled
2019-11-22 18:59:54.122 CET LOG:  database system is ready to accept connections
2019-11-22 18:59:54.123 CET LOG:  autovacuum launcher started

L'id de chronologie (timeline id) est incrémenté de 1 lors de la promotion. "selected new timeline ID: 3" dans le fichier de log.

Le fichier recovery.conf dans le répertoire $PGDATA est renommé en recovery.done avec pg_ctl promote.

Lors du retour arrière sur VPSFRSQLPAC1, un point de réplication standby1 existe déjà ($PGDATA/pg_replslot), mais celui-ci est obsolète car il s'agit du point de réplication existant avant la bascule sur VPSFRSQLPAC2.

select slot_name, restart_lsn from pg_replication_slots
          
 slot_name | restart_lsn
-----------+-------------
 standby1  | 0/3016198

Recréer et activer immédiatement le point réplication standby1 sur le nouveau serveur primaire :

postgres@vpsfrsqlpac1$  psql -p 30001

select * from pg_drop_replication_slot('standby1');

select * from pg_create_physical_replication_slot('standby1',true);
          
 slot_name | xlog_position
-----------+---------------
 standby1  | 0/E98A7B08

VPSFRSQLPAC1 est de nouveau le serveur primaire.

Failback - Étape 3 : Bascule des applications sur le nouveau serveur primaire

L'adresse logique LSRVSQLPAC bascule sur l'adresse physique de VPSFRSQLPAC1 dans le serveur DNS. Les applications peuvent se connecter.

Failback - Étape 4 : Conversion de VPSFRSQLPAC2 en serveur de standby

Maintenant, VPSFRSQLPAC2 redevient le serveur de standby.

À cette étape, ne pas démarrer la base sur VPSFRSQLPAC2

Sur VPSFRSQLPAC2, renommer le fichier recovery.done en recovery.conf dans le répertoire $PGDATA. Ajouter le paramètre recovery_target_timeline défini à latest si ce paramètre n'était pas existant dans le fichier d'origine recovery.conf avant bascule, dans le cas contraire la bascule sur l'id de chronologie 3 (timeline ID 3) ne sera pas automatiquement réalisée durant le recovery.

VPSFRSQLPAC2 : $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'user=repmgr host=vpsfrsqlpac1 port=30001 password=***********!'
primary_slot_name = 'standby1'
recovery_target_timeline = 'latest'

Lorsque le fichier recovery.conf est correctement paramétré, démarrer la base sur VPSFRSQLPAC2, vérifier le fichier de log et tester la réplication. Au démarrage, VPSFRSQLPAC2 traite les fichiers WAL de VPSFRSQLPAC1 générés depuis sa promotion en serveur principal et VPSFRSQLPAC2 redevient le serveur de standby.

postgres@vpsfrsqlpac2$  pg_ctl start -D $CFG
2019-11-22 19:05:17.799 CET LOG:  entering standby mode
2019-11-22 19:05:17.811 CET LOG:  consistent recovery state reached at 0/E98A79F8
2019-11-22 19:05:17.812 CET LOG:  database system is ready to accept read only connections
2019-11-22 19:05:17.812 CET LOG:  invalid record length at 0/E98A79F8: wanted 24, got 0
2019-11-22 19:05:17.824 CET LOG:  fetching timeline history file for timeline 3 from primary server
2019-11-25 19:05:17.835 CET LOG:  started streaming WAL from primary at 0/E9000000 on timeline 2
2019-11-22 19:05:18.568 CET LOG:  replication terminated by primary server
2019-11-22 19:05:18.568 CET DETAIL:  End of WAL reached on timeline 2 at 0/E98A79F8.
2019-11-22 19:05:18.570 CET LOG:  new target timeline is 3
2019-11-22 19:05:18.571 CET LOG:  restarted WAL streaming at 0/E9000000 on timeline 3
2019-11-22 19:05:19.524 CET LOG:  redo starts at 0/E98A79F8

Les scripts de maintenance et de démarrage automatiques peuvent être réactivés sur VPSFRSQLPAC2.

La situation est revenue à la normale.

Conclusion

Les procédures programmées de failover/failback avec la réplication en continu PostgreSQL sont très simples. En supposant qu'il y a assez d'espace disque pour la rétention des fichiers WAL durant les tâches de failover/failback, une bonne gestion des étapes et du point de réplication garantit la consistence, aucune perte de données et surtout il n'y a pas besoin de reconstruction complète incluant des backup/restore de bases de données ou autres méthodes douloureuses (pg_rewind)...

Avec ce cas pratique, il semble que cela soit une bonne pratique de (re)créer un point de réplication actif dès qu'un serveur de secours est promu en en serveur principal.

Si il n'y a pas assez d'espace disque pour la rétention des fichiers WAL durant la tâche de maintenance, la procédure est un tout petit peu différente et non abordée ici mais en quelques mots : le mode archive log doit être actif et chaque serveur de standby PostgreSQL doit pouvoir lire les archive logs générés par son serveur primaire.