# Réplication physique : rappels

> Réplication par streaming, promotion, installation de PostgreSQL et travaux pratiques avec solutions.

---

Index LLMS : [llms.txt](/fr/llms.txt)

---

<!-- Source PDF complet : pages 45-120, révision 26.09. -->
## Réplication physique : rappels {#hapat-ch3}

**Figure 3-45-f01.** Diapositive d’ouverture du chapitre 3\, page PDF 45\.

![Diapositive d’ouverture du chapitre 3\, page PDF 45\.](/images/hapat/manual/hapat-p045-01.png)


### Introduction


- Patroni repose sur la réplication physique de PostgreSQL
- la haute disponibilité implique les équipes techniques
- les équipes techniques doivent comprendre la mécanique


**Note :**


Patroni configure la réplication physique native de PostgreSQL pour assurer la redondance des données au sein de l’agrégat. Il est important de bien maîtriser comment cette réplication fonctionne.

Effectivement, Patroni ayant pour but d’optimiser la disponibilité, l’administrateur doit être lui-même réactif et identifier rapidement les causes d’un incident ou d’un problème de réplication, savoir ré-intégrer un nœud en réplication, etc. Et ce, sans créer de sur-incident bien entendu.


#### Au menu


- Mise en place de la réplication physique
- Promotion
- Retour à l’état stable


**Note :**


### Réplication par streaming


#### Mise en place de la réplication par streaming


- Réplication en flux
- Un processus du serveur primaire discute avec un processus du serveur secondaire
  - d’où un *lag* moins important
- Asynchrone ou synchrone
- En cascade


**Note :**


Le serveur PostgreSQL secondaire lance un processus appelé `walreceiver`, dont le but est de se connecter au serveur primaire et d’attendre les modifications de la réplication.

Le `walreceiver` a donc besoin de se connecter sur le serveur PostgreSQL primaire. Ce dernier doit être configuré pour accepter cette connexion. Quand elle est acceptée par le serveur primaire, le serveur PostgreSQL du serveur primaire lance un nouveau processus, appelé `walsender`. Ce dernier a pour but d’envoyer les données de réplication au serveur secondaire. Les données de réplication sont envoyées suivant l’activité et certains paramètres de configuration.

Cette méthode permet une réplication plus proche du serveur primaire que le *log shipping*. On peut même configurer un mode synchrone : un client du serveur primaire ne récupère pas la main tant que ses modifications ne sont pas enregistrées sur le serveur primaire **et** sur le serveur secondaire synchrone. Cela s’effectue à la validation de la transaction, implicite ou lors d’un `COMMIT`.

Enfin, la réplication en cascade permet à un secondaire de fournir les informations de réplication à un autre secondaire, déchargeant ainsi le serveur primaire d’un certain travail et diminuant aussi la bande passante réseau utilisée par le serveur primaire.


#### Serveur primaire (1/2) - Configuration


Dans `postgresql.conf` :

- `wal_level = replica` (ou `logical`)
- `max_wal_senders = X`
  - 1 par client par *streaming*
  - défaut : 10
- `wal_sender_timeout = 60s`


**Note :**


Il faut tout d’abord s’assurer que PostgreSQL enregistre suffisamment d’informations pour que le serveur secondaire puisse rejouer toutes les modifications survenant sur le serveur primaire. Dans certains cas, PostgreSQL peut économiser l’écriture de journaux quand cela ne pose pas de problème pour l’intégrité des données en cas de crash. Par exemple, sur une instance sans archivage ni réplication, il est inutile de tracer la totalité d’une transaction qui commence par créer une table, puis qui la remplit. En cas de crash pendant l’opération, l’opération complète est annulée, la table n’existera plus : PostgreSQL peut donc écrire directement son contenu sur le disque sans journaliser.

Cependant, pour restaurer cette table ou la répliquer, il est nécessaire d’avoir les étapes intermédiaires (le contenu de la table) et il faut donc écrire ces informations supplémentaires dans les journaux.

Le paramètre `wal_level` fixe le comportement à adopter. Comme son nom l’indique, il permet de préciser le niveau d’informations que l’on souhaite avoir dans les journaux. Il connaît trois valeurs :

- Le niveau `replica` est adapté à l’archivage ou la réplication, en plus de la sécurisation contre les arrêts brutaux. C’est le niveau par défaut. L’optimisation évoquée plus haut n’est pas possible.
- Le niveau `minimal` n’offre que la protection contre les arrêts brutaux, mais ne permet ni réplication ni sauvegarde PITR. Ce niveau ne sert plus guère qu’aux environnements ni archivés, ni répliqués, pour réduire la quantité de journaux générés, comme dans l’optimisation ci-dessus.
- Le niveau `logical` est le plus complet et doit être activé pour l’utilisation du décodage logique, notamment pour utiliser la réplication logique. Il n’est pas nécessaire pour la sauvegarde PITR ou la réplication physique, ni incompatible.

Le serveur primaire accepte un nombre maximum de connexions de réplication : il s’agit du paramètre `max_wal_senders`. Il faut compter au moins une connexion pour chaque serveur secondaire susceptible de se connecter, ou les outils utilisant le *streaming* comme `pg_basebackup` ou `pg_receivewal`. Il est conseillé de prévoir « large » d’entrée : l’impact mémoire est négligeable, et cela évite d’avoir à redémarrer l’instance primaire à chaque modification. La valeur par défaut de 10 devrait suffire dans la plupart des cas.

Le paramètre `wal_sender_timeout` permet de couper toute connexion inactive après le délai indiqué par ce paramètre. Par défaut, le délai est d’une minute. Cela permet au serveur primaire de ne pas conserver une connexion coupée ou dont le client a disparu pour une raison ou une autre. Le secondaire retentera par la suite une connexion complète.


#### Serveur primaire (2/2) - Authentification


- Le serveur secondaire doit pouvoir se connecter au serveur primaire
- Pseudo-base `replication`
- Utilisateur dédié conseillé avec attributs `LOGIN` et `REPLICATION`
- Configurer `pg_hba.conf` :


**Exemple 3-e001.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
host replication user_repli 10.2.3.4/32   scram-sha-256
```


- Recharger la configuration


**Note :**


Il est nécessaire après cela de configurer le fichier `pg_hba.conf`. Dans ce fichier, une ligne (par secondaire) doit indiquer les connexions de réplication. L’idée est d’éviter que tout le monde puisse se connecter pour répliquer l’intégralité des données.

Pour distinguer une ligne de connexion standard et une ligne de connexion de réplication, la colonne indiquant la base de données doit contenir le mot « replication ». Par exemple :


**Exemple 3-e002.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
host   replication   user_repli   10.0.0.2/32   scram-sha-256
```


Dans ce cas, l’utilisateur `user_repli` pourra entamer une connexion de réplication vers le serveur primaire à condition que la demande de connexion provienne de l’adresse IP `10.0.0.2` et que cette demande de connexion précise le bon mot de passe au format `scram-sha-256`.

Un utilisateur dédié à la réplication est conseillé pour des raisons de sécurité. On le créera avec les droits suivants :


**Exemple 3-e003.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
CREATE ROLE user_repli LOGIN REPLICATION ;
```


et bien sûr un mot de passe complexe.

Les connexions locales de réplication sont autorisées par défaut sans mot de passe.

Après modification du fichier `postgresql.conf` et du fichier `pg_hba.conf`, il est temps de demander à PostgreSQL de recharger sa configuration. L’action `reload` suffit dans tous les cas, sauf celui où `max_wal_senders` est modifié (auquel cas il faudra redémarrer PostgreSQL).


#### Serveur secondaire (1/4) - Copie des données


Copie des données du serveur primaire (à chaud !) :

- Copie généralement à chaud donc incohérente !
- Le plus simple : `pg_basebackup`
  - simple mais a des limites
- Idéal : outil PITR
- Possible : `rsync`, `cp`…
  - ne pas oublier `pg_backup_start()`/`pg_backup_stop()` !
  - exclure certains répertoires et fichiers
  - garantir la disponibilité des journaux de transaction


**Note :**


La première action à réaliser ressemble beaucoup à ce que propose la sauvegarde en ligne des fichiers. Il s’agit de copier le répertoire des données de PostgreSQL ainsi que les tablespaces associés.


**Attention :**


Rappelons que généralement cette copie aura lieu à chaud, donc une simple copie directe sera incohérente.


**pg_basebackup** :

L’outil le plus simple est `pg_basebackup`. Ses avantages sont sa disponibilité et sa facilité d’utilisation. Il sait ce qu’il n’y a pas besoin de copier et peut inclure les journaux nécessaires pour ne pas avoir à paramétrer l’archivage.

Il peut utiliser la connexion de réplication déjà prévue pour le secondaire, poser des slots temporaires ou le slot définitif.

Pour faciliter la mise en place d’un secondaire, il peut générer les fichiers de configuration à partir des paramètres qui lui ont été fournis (option `--write-recovery-conf`).

Malgré beaucoup d’améliorations dans les dernières versions, la limite principale de `pg_basebackup` reste d’exiger un répertoire cible vide : on doit toujours recopier l’intégralité de la base copiée. Cela peut être pénible lors de tests répétés avec une grosse base, ou avec une liaison instable. Toutefois, à partir de PostgreSQL 17, il permet une sauvegarde incrémentale.

**Outils PITR** :

L’idéal est un outil de restauration PITR permettant la restauration en mode delta, par exemple pgBackRest avec l’option `--delta`. Ne sont restaurés que les fichiers ayant changé, et le primaire n’est pas chargé par la copie.

**rsync** :

Un script de copie reste une option possible. Il est possible de le faire manuellement, tout comme pour une sauvegarde PITR.


**Attention :**


Une copie manuelle implique que les journaux sont archivés par ailleurs.


Rappelons les trois étapes essentielles :

- le `pg_backup_start()` ;
- la copie des fichiers : généralement avec `rsync --whole-file`, ou tout moyen permettant une copie fiable et rapide ;
- le `pg_backup_stop()`.

On exclura les fichiers inutiles lors de la copie qui pourraient gêner un redémarrage, notamment les fichiers `postmaster.pid`, `postmaster.opts`, `pg_internal.init`, les répertoires `pg_wal`, `pg_replslot`, `pg_dynshmem`, `pg_notify`, `pg_serial`, `pg_snapshots`, `pg_stat_tmp`, `pg_subtrans`, `pgslq_tmp*`. La liste complète figure dans la [documentation officielle](https://docs.postgresql.fr/current/continuous-archiving.html#BACKUP-LOWLEVEL-BASE-BACKUP).


#### Serveur secondaire (2/4) - Fichiers de configuration


- `postgresql.conf` & `postgresql.auto.conf`
  - paramètres
- `standby.signal` (dans PGDATA)
  - vide


**Note :**


Au choix, les paramètres sont à ajouter dans `postgresql.conf`, dans un fichier appelé par ce dernier avec une clause d’inclusion, ou dans `postgresql.auto.conf` (forcément dans le répertoire de données pour ce dernier, et qui surcharge les fichiers précédents). Cela dépend des habitudes, de la méthode d’industrialisation…

S’il y a des paramètres propres au primaire dans la configuration d’un secondaire, ils seront ignorés, et vice-versa. Dans les cas simples, le `postgresql.conf` peut donc être le même.

Puis il faut créer un fichier vide nommé `standby.signal` dans le répertoire `PGDATA`, qui indique à PostgreSQL que le serveur doit rester en *recovery* permanent.


**Conseil :**


Au cas où vous rencontreriez un vieux serveur en version antérieure à la 12 : jusqu’en version 11, on activait le mode *standby* non dans la configuration, mais en créant un fichier texte `recovery.conf` dans le `PGDATA` de l’instance, et en y plaçant le paramètre `standby_mode` à `on`. Les autres paramètres sont les mêmes. Toute modification impliquait un redémarrage.


#### Serveur secondaire (3/4) - Paramètres


- `primary_conninfo` (*streaming*) :


**Exemple 3-e004.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
primary_conninfo = 'user=user_repli host=prod port=5434
 application_name=standby '
```


- Optionnel :
  - `primary_slot_name`
  - `restore_command`
  - `wal_receiver_timeout`


**Note :**


PostgreSQL doit aussi savoir comment se connecter au serveur primaire. C’est le paramètre `primary_conninfo` qui le lui dit. Il s’agit d’un DSN standard où il est possible de spécifier l’adresse IP de l’hôte ou son alias, le numéro de port, le nom de l’utilisateur, etc. Il est aussi possible de spécifier le mot de passe, mais c’est risqué en terme de sécurité. En effet, PostgreSQL ne vérifie pas si ce fichier est lisible par quelqu’un d’autre que lui. Il est donc préférable de placer le mot de passe dans le fichier `.pgpass`, généralement dans `~postgres/` sur le secondaire, fichier qui n’est utilisé que s’il n’est lisible que par son propriétaire. Par exemple :


**Exemple 3-e005.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'user=postgres host=prod passfile=/var/lib/postgresql/.pgpass'
```


Toutes les options de la libpq sont accessibles. Par exemple, cette chaîne de connexion a été générée pour un nouveau secondaire par `pg_basebackup -R` :


**Exemple 3-e006.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'host=prod user=postgres passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer port=5436 sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable
```


S’y trouvent beaucoup de paramétrage par défaut dépendant de méthodes d’authentification, ou pour le SSL.

Parmi les autres paramètres optionnels de `primary_conninfo`, il est conseillé d’ajouter `application_name`, par exemple avec le nom du serveur. Cela facilite la supervision. C’est même nécessaire pour paramétrer une réplication synchrone.


**Exemple 3-e007.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'user=postgres host=prod passfile=/var/lib/postgresql/.pgpass  application_name=secondaire2 '
```


Si `application_name` n’est pas fourni, le `cluster_name` du secondaire sera utilisé, mais il est rarement correctement configuré (par défaut, il vaut `16/main` sur Debian/Ubuntu, et n’est pas configuré sur Red Hat/Rocky Linux).

De manière optionnelle, nous verrons que l’on peut définir aussi deux paramètres :

- `primary_slot_name`, pour sécuriser la réplication avec un slot de réplication ;
- `restore_command`, pour sécuriser la réplication avec un accès à la sauvegarde PITR.

Le paramètre `wal_receiver_timeout` sur le secondaire est le symétrique de `wal_sender_timeout` sur le primaire. Il indique au bout de combien de temps couper une connexion inactive. Le secondaire retentera la connexion plus tard.


#### Serveur secondaire (4/4) - Démarrage


- Démarrer PostgreSQL
- Suivre dans les traces que tout va bien


**Note :**


Il ne reste plus qu’à démarrer le serveur secondaire.

En cas de problème, le premier endroit où aller chercher est bien entendu le fichier de trace `postgresql.log`.


#### Processus


Sur le primaire :

- `walsender ... streaming 0/3BD48728`

Sur le secondaire :

- `walreceiver streaming 0/3BD48728`


**Note :**


Sur le primaire, un processus `walsender` apparaît pour chaque secondaire connecté. Son nom de processus est mis à jour en permanence avec l’emplacement dans le flux de journaux de transactions :


**Exemple 3-e008.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 postgres: 16/secondaire1: walsender postgres [local] streaming 15/6A6EF408
 postgres: 16/secondaire2: walsender postgres [local] streaming 15/6A6EF408
```


Symétriquement, sur chaque secondaire, un process `walreceiver` apparaît.


**Exemple 3-e009.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 postgres: 16/secondaire2: walreceiver streaming 0/DD73C218
```


### Promotion


#### Au menu


- Attention au *split-brain* !
- Vérification avant promotion
- Promotion : méthode et déroulement
- Retour à l’état stable


**Note :**


#### Attention au split-brain !


- Si un serveur secondaire devient le nouveau primaire
  - s’assurer que l’ancien primaire ne reçoit plus d’écriture
- Éviter que les deux instances soient ouvertes aux écritures
  - confusion et perte de données !


**Note :**


La pire chose qui puisse arriver lors d’une bascule est d’avoir les deux serveurs, ancien primaire et nouveau primaire promu, ouverts tous les deux en écriture. Les applications risquent alors d’écrire dans l’un ou l’autre…

Quelques histoires « d’horreur » à ce sujet :

- de [nombreux exemples sur diverses technologies de réplication](https://github.blog/2018-10-30-oct21-post-incident-analysis) ;
- *post mortem* d’un [gros problème chez Github en 2018](https://aphyr.com/posts/288-the-network-is-reliable).


#### Vérification avant promotion


- Primaire :


**Exemple 3-e010.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl stop postgresql-17
```


**Exemple 3-e011.** Extrait de code ou sortie de commande du chapitre 3\.

```text
$ pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(Database cluster state)|(REDO location)'
Database cluster state:               shut down
Latest checkpoint's REDO location:    0/3BD487D0
```


- Secondaire :


**Exemple 3-e012.** Extrait de code ou sortie de commande du chapitre 3\.

```text
$ psql -c 'CHECKPOINT;'
$ pg_controldata -D /var/lib/pgsql/17/data/ \
| grep -E '(Database cluster state)|(REDO location)'
Database cluster state:               in archive recovery
Latest checkpoint's REDO location:    0/3BD487D0
```


**Note :**


Avant une bascule, il est capital de vérifier que toutes les modifications envoyées par le primaire sont arrivées sur le secondaire. Si le primaire a été arrêté proprement, ce sera le cas. Après un `CHECKPOINT` sur le secondaire, on y retrouvera le même emplacement dans les journaux de transaction.

Ce contrôle doit être systématique avant une bascule. Même si toutes les écritures applicatives sont stoppées sur le primaire, quelques opérations de maintenance peuvent en effet écrire dans les journaux et provoquer un écart entre les deux serveurs (divergence). Il n’y aura alors pas de perte de données mais cela pourrait gêner la transformation de l’ancien primaire en secondaire, par exemple. En revanche, **même avec un arrêt propre du primaire**, il peut y avoir perte de données s’il y a un *lag* important entre primaire et secondaire : même si le rejeu va toujours jusqu’au bout avant le changement de *timeline*, les WAL qui n’ont pas pu être récupérés avant la déconnexion, ou après la récupération des archives (si le *log shipping* est en place) sont perdus pour le nouveau primaire.

Noter que `pg_controldata` n’est pas dans les chemins par défaut des distributions. La fonction SQL `pg_control_checkpoint()` affiche les même informations, mais n’est bien sûr pas accessible sur un primaire arrêté.


#### Promotion du standby : méthode


- Shell :
  - `pg_ctl promote`
- SQL :
  - fonction `pg_promote()`


**Note :**


Il existe plusieurs méthodes pour promouvoir un serveur PostgreSQL en mode *standby*. Les méthodes les plus appropriées sont :

- l’action `promote` de l’outil `pg_ctl`, ou de son équivalent dans les scripts des paquets d’installation, comme `pg_ctlcluster` sous Debian ;
- la fonction SQL `pg_promote`.

Ces deux méthodes sont à préférer à la méthode historique du fichier de déclenchement (*trigger file*), qui existe encore jusque PostgreSQL 15 inclus. Le paramètre `promote_trigger_file` sur une instance secondaire définit un fichier dont l’apparition provoque la promotion de l’instance. Par sécurité, utiliser un emplacement accessible uniquement aux administrateurs.


#### Promotion du standby : déroulement


Une promotion déclenche :

- déconnexion de la *streaming replication* (bascule programmée)
- rejeu des dernières transactions en attente d’application
- récupération et rejeu de toutes les archives disponibles
- choix d’une nouvelle *timeline* du journal de transaction
- suppression du fichier `standby.signal`
- nouvelle *timeline* et fichier `.history`
- ouverture aux écritures


**Note :**


La promotion se déroule en bonne partie comme un *recovery* après restauration PITR.

Une fois l’instance promue, elle finit de rejouer les derniers journaux de transaction en provenance du serveur principal en sa possession, puis se déconnecte de celui-ci (si l’on est encore connecté en *streaming*). Après la déconnexion, si une `restore_command` est configurée, toutes les archives disponibles sont récupérées et rejouées (en général, il n’y a pas d’archive contenant des WAL plus récents que le dernier récupéré en *streaming* ; mais des écritures lourdes et/ou un réseau trop lent peuvent entraîner un retard du *streaming*).

Le dernier journal reçu de l’ancien primaire est souvent incomplet. Il est renommé avec le suffixe `.partial` et archivé. Cela évite un conflit de nom éventuel avec le même fichier issu de l’ancien serveur, qui a pu aussi être archivé, à un point éventuellement postérieur à la divergence.

Ensuite, l’instance choisit une nouvelle *timeline* pour son journal de transactions. Rappelons que la *timeline* est le premier numéro dans le nom du segment (fichier WAL) ; par exemple une timeline 5 pour un fichier nommé `000000050000003200000031`. Le nouveau primaire choisit généralement le numéro suivant celui du primaire (à moins que les archives ne contiennent d’autres *timelines* de numéro supérieur, s’il y a eu plusieurs restaurations et retours en arrière, et il choisit alors le numéro suivant la dernière).

Le choix d’une nouvelle *timeline* permet à PostgreSQL de rendre les journaux de transactions de ce nouveau serveur en écriture incompatibles avec son ancien serveur principal. De plus, des journaux de nom différent permet l’archivage depuis ce primaire sans perturber l’ancien s’il existe encore. Il n’y a plus de fichier en commun même si l’espace d’archivage est partagé.


**Attention :**


Les *timelines* ne changent pas que lors des promotions, mais aussi lors des restaurations PITR. En général, on désire que les secondaires (parfois en cascade) suivent. Heureusement, ceci est le paramétrage par défaut depuis la version 12 :


**Exemple 3-e013.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
recovery_target_timeline = latest
```


Un secondaire suit donc par défaut les évolutions de *timeline* de son primaire, tant que celui-ci n’effectue pas de retour en arrière.


L’instance crée un fichier d’historique dans `pg_wal/`, par exemple `00000006.history` pour la nouvelle *timeline* 6. C’est un petit fichier texte qui contient les différentes *timelines* ayant mené à la nouvelle. Ce fichier est immédiatement archivé s’il y a archivage.

Enfin, l’instance autorise les connexions en lecture et en écriture.


#### Opérations après promotion du standby


- `VACUUM ANALYZE` conseillé
  - calcul d’informations nécessaires pour autovacuum


**Note :**


Il n’y a aucune opération obligatoire après une promotion. Cependant, il est conseillé d’exécuter un `VACUUM` ou un `ANALYZE` pour que PostgreSQL mette à jour les estimations de nombre de lignes vivantes et mortes. Ces estimations sont utilisées par l’autovacuum pour lutter contre la fragmentation des tables et mettre à jour les statistiques sur les données. Or ces estimations faisant partie des statistiques d’activité, elles ne sont pas répliquées vers les secondaires. Il est donc intéressant de les mettre à jour après une promotion.


#### Retour à l’état stable


Si un *standby* a été momentanément indisponible :

- Rattrapage possible si **tous** les journaux sont disponibles :
  - *streaming* depuis le primaire (slot, `wal_keep_size`)
  - *log shipping* depuis les archives (`restore_command`)
  - ou les deux (si configurés)
- Sinon :
  - « décrochage… »
  - reconstruction nécessaire


**Note :**


Si un serveur secondaire est momentanément indisponible mais revient en ligne sans perte de données (réseau coupé, problème OS…), alors il a de bonnes chances de se « raccrocher » à son serveur primaire. Il faut bien sûr que l’ensemble des journaux de transactions depuis son arrêt soit accessible à ce serveur, **sans exception**.


**Prudence :**


Si le secondaire ne peut rattraper le flux des journaux du primaire, il peut rester ouvert en lecture, mais il ne rattrapera jamais le primaire. Il est donc irrécupérable et doit être reconstruit par l’une des méthodes précédentes, ce qui peut être long et lourd.


Le secondaire cherche ces journaux par *log shipping* et par *streaming* auprès de son primaire, s’ils sont configurés et fonctionnels. Si une méthode échoue, le secondaire tentera l’autre, alternativement, et indéfiniment jusqu’à obtenir les journaux nécessaires. Si une seule méthode est configurée, le secondaire ne tente bien sûr que celle-ci. Tant qu’une méthode fonctionne, le secondaire l’utilise jusqu’à ce qu’elle tombe en échec.

**En cas de réplication par *streaming*** :

Le secondaire utilise la `primary_conninfo` pour demander au primaire les journaux à partir de sa propre position dans le flux de journaux, successivement et dans l’ordre. Le primaire ne doit pas avoir recyclé ou supprimé ces journaux devenus inutiles après leur archivage et un *checkpoint*, ce qui peut ne prendre que quelques minutes.

Si le primaire n’a plus le journal nécessaire, les deux instances afficheront cette erreur :


**Exemple 3-e014.** Extrait de code ou sortie de commande du chapitre 3\.

```text
ERROR:  requested WAL segment 000000010000009900000045 has already been removed
```


On peut forcer le primaire à conserver des journaux pour un serveur secondaire :

- La première méthode est simple et consiste à définir un paramètre nommé `wal_keep_size`, pour que le primaire conserve toujours une certaine quantité de journaux, inutiles pour lui, au cas où un secondaire le demande.
- La méthode la plus propre consiste à créer un « slot de réplication » sur le primaire, et dédié à ce secondaire (paramètre `primary_slot` à définir sur celui-ci). Le primaire sait alors en permanence où en est son secondaire, et lui conserve toujours les journaux nécessaires. C’est très sûr, mais peut avoir des inconvénients.

**En cas de réplication par *log shipping*** :

Il faut que la `restore_command` fonctionne, que le stock des journaux remonte assez loin dans le temps (jusqu’au moment où le secondaire a perdu contact), et qu’aucun journal ne manque ni ne soit corrompu. Souvent, le secondaire utilise le *log shipping* depuis les archives PITR pour rattraper son retard, jusqu’à ce que l’archivage tombe en erreur après épuisement de tous les journaux disponibles ; puis il bascule sur le *streaming* si c’est possible.


### Conclusion


- La réplication physique native est très robuste
- Patroni peut automatiser :
  - la mise en réplication
  - la promotion
  - le raccrochage d’un ancien primaire


**Note :**


La robustesse de la réplication physique est éprouvée depuis longtemps. C’est à cette base solide que Patroni amène l’automatisation de :

- l’ajout de nœuds supplémentaires ;
- la promotion automatique en fonction du nœud ;
- le raccrochage d’un ancien primaire au nouveau primaire.

Ces mécanismes sont abordé dans le module consacré à [Patroni](https://dali.bo/r58_html).


### Quiz


**<https://dali.bo/r56_quiz>**


**Note :**


### Installation de PostgreSQL depuis les paquets communautaires


**Note :**


L’installation est détaillée ici pour Rocky Linux 8 et 9 (similaire à Red Hat et à d’autres variantes comme Oracle Linux et Fedora), et Debian/Ubuntu.

Elle ne dure que quelques minutes.

#### Sur Rocky Linux 8, 9 ou 10


**Attention :**


**ATTENTION** : Red Hat, CentOS, Rocky Linux fournissent souvent par défaut des versions de PostgreSQL qui ne sont plus supportées. Ne jamais installer les packages `postgresql`, `postgresql-client` et `postgresql-server` ! L’utilisation des dépôts du PGDG est fortement conseillée.


**Installation du dépôt communautaire**  :

Les dépôts de la communauté sont sur <https://yum.postgresql.org/>. Les commandes qui suivent sont inspirées de celles générées par l’assistant sur <https://www.postgresql.org/download/linux/redhat/>. Il n’y a guère que les informations de version qui diffèrent en fonction de :

- la version majeure de PostgreSQL (ici la 18) ;
- la distribution (ici Rocky Linux 9) ;
- l’architecture (ici x86_64, la plus courante).

Les commandes sont à lancer sous **root** :


**Exemple 3-e015.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm

# dnf -qy module disable postgresql
```


**Installation de PostgreSQL 18 (client, serveur, librairies, extensions)** :


**Exemple 3-e016.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# dnf install -y postgresql18-server postgresql18-contrib
```


Les outils clients et les librairies nécessaires seront automatiquement installés.

Une fonctionnalité avancée optionnelle, le JIT (*Just In Time compilation*), nécessite un paquet séparé.


**Exemple 3-e017.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# dnf install postgresql18-llvmjit
```


**Création d’une première instance** :

C’est optionnel, mais il est conseillé de déclarer `PGSETUP_INITDB_OPTIONS`, notamment pour forcer les traces en anglais. Depuis la version 18, les sommes de contrôle sont automatiquement activées.


**Exemple 3-e018.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# export PGSETUP_INITDB_OPTIONS='--lc-messages=C'
# /usr/pgsql-18/bin/postgresql-18-setup initdb
# cat /var/lib/pgsql/18/initdb.log
```


Ce dernier fichier permet de vérifier que tout s’est bien passé et doit finir par :


**Exemple 3-e019.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Success. You can now start the database server using:

    /usr/pgsql-18/bin/pg_ctl -D /var/lib/pgsql/18/data/ -l logfile start
```


**Chemins** :


**Tableau 3-t01.** Tableau du chapitre 3\, extrait du manuel HAPAT\.

| **Objet**                                | **Chemin**               |
|:-----------------------------------------|:-------------------------|
| Binaires                                 | `/usr/pgsql-18/bin`      |
| Répertoire de l’utilisateur **postgres** | `/var/lib/pgsql`         |
| `PGDATA` par défaut                      | `/var/lib/pgsql/18/data` |
| Fichiers de configuration                | dans `PGDATA/`           |
| Traces                                   | dans `PGDATA/log`        |


**Configuration** :

Modifier `postgresql.conf` est facultatif pour un premier lancement.

**Commandes d’administration habituelles** :

Démarrage, arrêt, statut, rechargement à chaud de la configuration, redémarrage :


**Exemple 3-e020.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl start postgresql-18
# systemctl stop postgresql-18
# systemctl status postgresql-18
# systemctl reload postgresql-18
# systemctl restart postgresql-18
```


**Test rapide de bon fonctionnement et connexion à psql** :


**Exemple 3-e021.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl --all |grep postgres
# sudo -iu postgres psql
```


**Démarrage de l’instance au lancement du système d’exploitation** :


**Exemple 3-e022.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl enable postgresql-18
```


**Ouverture du *firewall* pour le port 5432** :

Voir si le *firewall* est actif :


**Exemple 3-e023.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl status firewalld
```


Si c’est le cas, autoriser un accès extérieur :


**Exemple 3-e024.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# firewall-cmd --zone=public --add-port=5432/tcp --permanent
# firewall-cmd --reload
# firewall-cmd --list-all
```


(Rappelons que `listen_addresses` doit être également modifié dans `postgresql.conf`.)

**Création d’autres instances** :

Si des instances de *versions majeures différentes* doivent être installées, il faut d’abord installer les binaires pour chacune (adapter le numéro dans `dnf install …`) et appeler le script d’installation de chaque version. l’instance par défaut de chaque version vivra dans un sous-répertoire numéroté de `/var/lib/pgsql` automatiquement créé à l’installation. Il faudra juste modifier les ports dans les `postgresql.conf` pour que les instances puissent tourner simultanément.

Si plusieurs instances d’une *même version majeure* (forcément de la même version mineure) doivent cohabiter sur le même serveur, il faut les installer dans des `PGDATA` différents.

- Ne pas utiliser de tiret dans le nom d’une instance (problèmes potentiels avec systemd).
- Respecter les normes et conventions de l’OS : placer les instances dans un nouveau sous-répertoire de `/var/lib/pgsqsl/18/` (ou l’équivalent pour d’autres versions majeures).

Pour créer une seconde instance, nommée par exemple **infocentre** :

- Création du fichier service de la deuxième instance :


**Exemple 3-e025.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# cp /lib/systemd/system/postgresql-18.service \
        /etc/systemd/system/postgresql-18-infocentre.service
```


- Modification de ce dernier fichier avec le nouveau chemin :


**Exemple 3-e026.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
Environment=PGDATA=/var/lib/pgsql/18/infocentre
```


- Option 1 : création d’une nouvelle instance vierge :


**Exemple 3-e027.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# export PGSETUP_INITDB_OPTIONS='--lc-messages=C'
# /usr/pgsql-18/bin/postgresql-18-setup initdb postgresql-18-infocentre
```


- Option 2 : restauration d’une sauvegarde : la procédure dépend de votre outil.

- Adaptation de `/var/lib/pgsql/18/infocentre/postgresql.conf` (port surtout).

- Commandes de maintenance de cette instance :


**Exemple 3-e028.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl [start|stop|reload|status] postgresql-18-infocentre
# systemctl [enable|disable] postgresql-18-infocentre
```


- Ouvrir le nouveau port dans le firewall au besoin.

#### Sur Debian / Ubuntu

L’installation ne varie quasiment pas selon les versions de Debian et Ubuntu. Ce qui suit a été testé sur Debian 10 à 13 et Ubuntu LTS 22.04 et 24.04.

Sauf précision, tout est à effectuer en tant qu’utilisateur **root**.

Référence : <https://apt.postgresql.org/>

**Installation du dépôt communautaire** :

L’installation des dépôts du PGDG est prévue dans le paquet Debian `postgresql-common` :


**Exemple 3-e029.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# apt update
# apt install -y  gnupg2  postgresql-common
# /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
```


Ce dernier ordre créera le fichier du dépôt `/etc/apt/sources.list.d/pgdg.list` adapté à la distribution en place.

**Installation de PostgreSQL 18** :

La méthode la plus propre consiste à modifier la configuration par défaut avant l’installation :

Dans `/etc/postgresql-common/createcluster.conf`, paramétrer au moins les traces en anglais. Depuis la version 18, les sommes de contrôle sont automatiquement activées.


**Exemple 3-e030.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
initdb_options = '--lc-messages=C'
```


Puis installer les paquets serveur et clients et leurs dépendances :


**Exemple 3-e031.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# apt install postgresql-18 postgresql-client-18
```


La première instance est automatiquement créée, avec un nom (`main` par défaut), déclarée comme service à lancer au démarrage du système, puis démarrée.


**Exemple 3-e032.** Extrait de code ou sortie de commande du chapitre 3\.

```text
$ pg_lsclusters
Ver Cluster Port Status Owner    Data directory              Log file
18  main    5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log

$ systemctl status postgresql@18-main
● postgresql@18-main.service - PostgreSQL Cluster 18-main
     Loaded: loaded (/usr/lib/systemd/system/postgresql@.service; enabled-runtime; preset: enabled)
     Active: active (running) since Fri 2026-01-30 15:18:43 UTC; 2min 13s ago
…
```


L’utilisateur système **postgres** peut y accéder immédiatement.

**Chemins** :


**Tableau 3-t02.** Tableau du chapitre 3\, extrait du manuel HAPAT\.

| **Objet**                                | **Chemin**                      |
|:-----------------------------------------|:--------------------------------|
| Binaires                                 | `/usr/lib/postgresql/18/bin/`   |
| Répertoire de l’utilisateur **postgres** | `/var/lib/postgresql`           |
| PGDATA de l’instance par défaut          | `/var/lib/postgresql/18/main`   |
| Fichiers de configuration                | dans `/etc/postgresql/18/main/` |
| Traces                                   | dans `/var/log/postgresql/`     |


**Configuration**

Modifier `postgresql.conf` est facultatif pour un premier essai.

**Démarrage/arrêt de l’instance, rechargement de configuration** :

Debian fournit ses propres outils, qui demandent en paramètre la version et le nom de l’instance :


**Exemple 3-e033.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_ctlcluster 18 main [start|stop|reload|status|restart]
```


**Démarrage de l’instance avec le serveur** :

C’est en place par défaut, et modifiable dans `/etc/postgresql/18/main/start.conf`.

**Ouverture du firewall** :

Debian et Ubuntu n’installent pas de firewall par défaut.

**Statut des instances du serveur** :


**Exemple 3-e034.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_lsclusters
```


**Exemple 3-e035.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Ver Cluster Port Status Owner    Data directory              Log file
18  main    5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
```


**Test rapide de bon fonctionnement et connexion à psql** :


**Exemple 3-e036.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# systemctl --all |grep postgres
# sudo -iu postgres psql
```


**Destruction d’une instance** :


**Exemple 3-e037.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_dropcluster 18 main
```


**Création d’autres instances** :

Ce qui suit est valable pour remplacer l’instance par défaut par une autre, par exemple pour activer les sommes de contrôles (*checksums*) avant la version v18 :

- optionnellement, `/etc/postgresql-common/createcluster.conf` permet de mettre en place tout d’entrée les messages en anglais, le format des traces, l’emplacement séparé pour les journaux ou les *checksums* pour une version inférieure à 18 :


**Exemple 3-e038.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
initdb_options = '--data-checksums --lc-messages=C'
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
waldir = '/var/lib/postgresql/wal/%v/%c/pg_wal'
```


- créer une instance :


**Exemple 3-e039.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_createcluster 18 infocentre
```


Il est également possible de préciser certains paramètres du fichier `postgresql.conf`, voire les chemins des fichiers (il est conseillé de conserver les chemins par défaut) :


**Exemple 3-e040.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_createcluster 18 infocentre \
  --port=12345 \
  --datadir=/PGDATA/18/infocentre \
  --pgoption shared_buffers='8GB' --pgoption work_mem='50MB' \
  --  --data-checksums --waldir=/ssd/postgresql/18/infocentre/journaux
```


- adapter au besoin `/etc/postgresql/18/infocentre/postgresql.conf` ;

- démarrage :


**Exemple 3-e041.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# pg_ctlcluster 18 infocentre start
```


#### Accès à l’instance depuis le serveur même (toutes distributions)

Par défaut, l’instance n’est accessible que par l’utilisateur système **postgres**, qui n’a pas de mot de passe. Un détour par `sudo` est nécessaire :


**Exemple 3-e042.** Extrait de code ou sortie de commande du chapitre 3\.

```text
$ sudo -iu postgres psql
psql (18.1)
Type "help" for help.
postgres=#
```


Ce qui suit permet la connexion directement depuis un utilisateur du système :

Pour des tests (pas en production !), il suffit de passer à `trust` le type de la connexion en local dans le `pg_hba.conf` :


**Exemple 3-e043.** Extrait de code ou sortie de commande du chapitre 3\.

```text
local   all             postgres                               trust
```


La connexion en tant qu’utilisateur `postgres` (ou tout autre) n’est alors plus sécurisée :


**Exemple 3-e044.** Extrait de code ou sortie de commande du chapitre 3\.

```text
dalibo:~$ psql -U postgres
psql (18.1)
Type "help" for help.
postgres=#
```


Une authentification par mot de passe est plus sécurisée :

- dans `pg_hba.conf`, paramétrer une authentification par mot de passe pour les accès depuis `localhost` (déjà en place sous Debian) :


**Exemple 3-e045.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256
```


(Ne pas oublier de recharger la configuration en cas de modification.)

- ajouter un mot de passe à l’utilisateur `postgres` de l’instance :


**Exemple 3-e046.** Extrait de code ou sortie de commande du chapitre 3\.

```text
dalibo:~$ sudo -iu postgres psql
psql (18.1)
Type "help" for help.
postgres=# \password
Enter new password for user "postgres":
Enter it again:
postgres=# quit

dalibo:~$ psql -h localhost -U postgres
Password for user postgres:
psql (18.1)
Type "help" for help.
postgres=#
```


- Pour se connecter sans taper le mot de passe à une instance, un fichier `.pgpass` dans le répertoire personnel doit contenir les informations sur cette connexion :


**Exemple 3-e047.** Extrait de code ou sortie de commande du chapitre 3\.

```text
localhost:5432:*:postgres:motdepassetrèslong
```


Ce fichier doit être protégé des autres utilisateurs :


**Exemple 3-e048.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
$ chmod 600 ~/.pgpass
```


- Pour n’avoir à taper que `psql`, on peut définir ces variables d’environnement dans la session voire dans `~/.bashrc` :


**Exemple 3-e049.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
export PGUSER=postgres
export PGDATABASE=postgres
export PGHOST=localhost
```


**Rappels** :

- en cas de problème, consulter les traces (dans `/var/lib/pgsql/18/data/log` ou `/var/log/postgresql/`) ;
- toute modification de `pg_hba.conf` ou `postgresql.conf` impliquant de recharger la configuration peut être réalisée par une de ces trois méthodes en fonction du système :


**Exemple 3-e050.** Extrait de code ou sortie de commande du chapitre 3\.

```text
root:~# systemctl reload postgresql-18
```


**Exemple 3-e051.** Extrait de code ou sortie de commande du chapitre 3\.

```text
root:~# pg_ctlcluster 18 main reload
```


**Exemple 3-e052.** Extrait de code ou sortie de commande du chapitre 3\.

```text
postgres:~$ psql -c 'SELECT pg_reload_conf()'
```


### Travaux pratiques


**Note :**


La version en ligne des solutions de ces TP est disponible sur <https://dali.bo/r56_solutions>.


**Attention :**


Ce TP utilise plusieurs VM : **p1**, **p2**, **p3** et **b1**.

Les commandes `systemctl` doivent être exécutées avec l’utilisateur **root** ou (de préférence) avec la commande `sudo`, depuis un utilisateur qui dispose des droits nécessaire. Par défaut, ce n’est pas le cas pour l’utilisateur **postgres**. Dans les machines virtuelles, l’utilisateur **admin** dispose de ces droits.

Il est possible de changer ce comportement, en exécutant la commande `sudo visudo` et en ajoutant la ligne `postgres` après `root` :


**Exemple 3-e053.** Extrait de code ou sortie de commande du chapitre 3\.

```text
root     ALL=(ALL:ALL)
postgres ALL=(ALL) NOPASSWD: ALL
```


L’utilisateur **postgres** sera sudoer au prochain démarrage de session. Attention à la syntaxe et à l’éventuel message d’erreur de `visudo`, une erreur dans le fichier peut empêcher tout accès à la VM. Cette solution n’est pas recommandée en production car elle donne trop de droits a l’utilisateur **postgres**.


#### Sur Rocky Linux 8 ou 9


**Note :**


**Attention :**


Ce TP utilise deux VM : **p1** et **p2**.

Dans la réalité, les accès entre VM seront probablement filtrés par des firewalls qu’il faudra configurer. `firewalld` n’est pas activé sur les VM de TP.


##### Réplication asynchrone en flux avec un seul secondaire


**Note :**


**But** : Mettre en place une réplication asynchrone en flux.


> - Créer l’instance principale dans `/var/lib/pgsql/17/main` sur le serveur **p1**. Les sommes de contrôles devront être activées.

> - Mettre en place la configuration de la réplication par *streaming*.
> - L’utilisateur dédié sera nommé **repli**.

> - Créer la première instance secondaire sur le serveur **p2**, par copie **à chaud** du répertoire de données.

> - Démarrer la nouvelle instance sur **p2** et s’assurer que la réplication fonctionne bien avec `ps`.
> - Tenter de se connecter au serveur secondaire.
> - Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.


##### Promotion de l’instance secondaire


**Note :**


**But** : Promouvoir un serveur secondaire en primaire.


> - En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.

> - Tenter de se connecter au serveur secondaire fraîchement promu.
> - Les écritures y sont-elles possibles ?


##### Retour à la normale


**Note :**


**But** : Revenir à l’architecture d’origine.


> - Reconstruire l’instance initiale sur **p1** comme nouvelle instance secondaire en repartant d’une copie complète de **p2** en utilisant `pg_basebackup`.

> - Démarrer cette nouvelle instance.
> - Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.

> - Inverser à nouveau les rôles des deux instances afin que **p2** redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.


##### Optionnel: Types de réplication synchrone


**Note :**


**But** : Montrer le fonctionnement de la réplication synchrone en fonction du paramétrage de `synchronous_standby_names`.


Au début du TP, deux instances sont disponibles **p1**, la primaire, et **p2** sa standby.

> - Configurer la réplication synchrone de **p1** vers **p2**.
> - Stopper l’instance secondaire **p2**, créer une table `t1`, que se passe-t-il ?
> - Redémarrer l’instance **p2**, que se passe-t-il ?
> - Stopper l’instance **p2**, créer une table `t2`. Annuler la requête bloquée avec la commande `pg_cancel_backend()`, qu’observez-vous ?

> - Suivre la méthode utilisée dans les TP précédants pour mettre en place une seconde instance standby sur p3.

> - Configurer la réplication synchrone de **p1** vers **p2** et **p3** en utilisant la syntaxe `p2, p3` pour `synchronous_standby_names`. Observer les champs `sync_priority` et `sync_state` de `pg_stat_replication`.
> - Stopper **p2** puis **p3**, redémarrer **p3** puis **p2**. À chaque étape regarder l’état de la vue `pg_stat_replication`.
> - Modifier la configuration de **p1** pour demander deux standbys synchrones.

> - Configurer `synchronous_standby_names` à `*`. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
> - Redémarrer **p2**.

> - Mettre en place la réplication synchrone par *quorum* avec un serveur synchrone minimum.

> - Désactiver la réplication synchrone.


##### Optionnel: Log shipping et initialisation d’instance avec pgBackRest


**Note :**


**But** : Montrer l’utilisation de pgBackRest dans une architecture de réplication


Nous allons utiliser la vm **b1** comme serveur de sauvegarde et y créer un dépôt pgBackRest dédié.

> - Installer pgBackRest sur tous les serveurs (**p1**, **p2**, **p3**, **b1**).

> - Configurer pgBackRest sur le serveur de sauvegarde.
> - Configurer pgBackRest sur les serveurs de base de données.
> - Créer la stanza, tester l’archivage.
> - Lancer une sauvegarde.

> - Si **p3** existe, le stopper et détruire le répertoire de données.
> - Créer l’instance à partir de la sauvegarde.
> - Interdire la connexion à **p1** depuis **p3**, que se passe-t-il sur **p3** ?
> - Faire marche arrière sur la modification, que se passe-t-il sur **p3** ?


##### Optionnel: pg_rewind


**Note :**


**But** : Remonter une instance secondaire après une divergence.


L’instance **p1** est l’instance primaire.

> - Promouvoir l’instance **p2**. Générer de l’activité sur **p1**.
> - Stopper **p1** et configurer la réplication pour raccrocher **p1** à **p2**. Qu’observez-vous ?

> - Utiliser `pg_rewind` pour remettre l’instance **p1** d’aplomb.


#### Sur Debian 12


**Note :**


**Attention :**


Ce TP utilise deux machines virtuelles : **p1** et **p2**.

Dans la réalité, les accès entre VM seront probablement filtrés par des firewalls qu’il faudra configurer. Par défaut, aucun firewall n’est installé sur Debian, cette étape sera donc inutile dans notre cas.


##### Réplication asynchrone en flux avec un seul secondaire


**Note :**


**But** : Mettre en place une réplication asynchrone en flux.


> Ce TP suppose que les binaires de PostgreSQL sont présents mais qu’aucune instance n’est en place. - Vérifier avec `pg_lsclusters` que les binaires de PostgreSQL sont installés et qu’il n’existe pas d’autre instance. - Créer l’instance principale en utilisant `pg_createcluster` sur le serveur **p1**. Les sommes de contrôles devront être activées.

> - Vérifier la configuration de la réplication par *streaming*.
> - L’utilisateur dédié sera nommé **repli**.

> - Créer la première instance secondaire sur le serveur **p2**, par copie **à chaud** du répertoire de données. Le répertoire dédié aux fichiers de configuration devra également être copié.

> - Démarrer la nouvelle instance sur **p2** et s’assurer que la réplication fonctionne bien avec `ps`.
> - Tenter de se connecter au serveur secondaire.
> - Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.


##### Promotion de l’instance secondaire


**Note :**


**But** : Promouvoir un serveur secondaire en primaire.


> - En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.

> - Tenter de se connecter au serveur secondaire fraîchement promu.
> - Les écritures y sont-elles possibles ?


##### Retour à la normale


**Note :**


**But** : Revenir à l’architecture d’origine.


> - Reconstruire l’instance initiale sur **p1** comme nouvelle instance secondaire en repartant d’une copie complète de **p2** en utilisant `pg_basebackup`.

> - Démarrer cette nouvelle instance.
> - Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.

> - Inverser à nouveau les rôles des deux instances afin que **p2** redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.


##### Optionnel: Types de réplication synchrone


**Note :**


**But** : Montrer le fonctionnement de la réplication synchrone en fonction du paramétrage de `synchronous_standby_names`.


Au début du TP, deux instances sont disponibles **p1**, la primaire, et **p2** sa standby.

> - Configurer la réplication synchrone de **p1** vers **p2**.
> - Stopper l’instance secondaire **p2**, créer une table `t1`, que se passe-t-il ?
> - Redémarrer l’instance **p2**, que se passe-t-il ?
> - Stopper l’instance **p2**, créer une table `t2`. Annuler la requête bloquée avec la commande `pg_cancel_backend()`, qu’observez-vous ?

> - Suivre la méthode utilisée dans les TP précédants pour mettre en place une seconde instance standby sur p3.

> - Configurer la réplication synchrone de **p1** vers **p2** et **p3** en utilisant la syntaxe `p2, p3` pour `synchronous_standby_names`. Observer les champs `sync_priority` et `sync_state` de `pg_stat_replication`.
> - Stopper **p2** puis **p3**, redémarrer **p3** puis **p2**. À chaque étape regarder l’état de la vue `pg_stat_replication`.
> - Modifier la configuration de **p1** pour demander deux standbys synchrones.

> - Configurer `synchronous_standby_names` à `*`. Répéter les mêmes tests que précédemment. Qu’observez-vous ?
> - Redémarrer **p2**.

> - Mettre en place la réplication synchrone par *quorum* avec un serveur synchrone minimum.

> - Désactiver la réplication synchrone.


##### Optionnel: Log shipping et initialisation d’instance avec pgBackRest


**Note :**


**But** : Montrer l’utilisation de pgBackRest dans une architecture de réplication


Nous allons utiliser la vm **b1** comme serveur de sauvegarde et y créer un dépôt pgBackRest dédié.

> - Installer pgBackRest sur tous les serveurs (**p1**, **p2**, **p3**, **b1**).

> - Configurer pgBackRest sur le serveur de sauvegarde.
> - Configurer pgBackRest sur les serveurs de base de données.
> - Créer la stanza, tester l’archivage.
> - Lancer une sauvegarde.

> - Si **p3** existe, le stopper et détruire le répertoire de données.
> - Créer l’instance à partir de la sauvegarde.
> - Interdire la connexion à **p1** depuis **p3**, et générer de l’activité. Que se passe-t-il sur **p3** ?
> - Faire marche arrière sur la modification, que se passe-t-il sur **p3** ?


##### Optionnel: pg_rewind


**Note :**


**But** : Remonter une instance secondaire après une divergence.


L’instance **p1** est l’instance primaire.

> - Promouvoir l’instance **p2**. Générer de l’activité sur **p1**.
> - Stopper **p1** et configurer la réplication pour raccrocher **p1** à **p2**. Qu’observez-vous ?

> - Utiliser `pg_rewind` pour remettre l’instance **p1** d’aplomb.


### Travaux pratiques (solutions)


**Note :**


**Conseil :**


La version de PostgreSQL est la version 17. Adapter au besoin pour une version ultérieure.


#### Sur Rocky Linux 8 ou 9


**Note :**


**Conseil :**


Cette solution se base sur un système Rocky Linux 8, installé à minima depuis les paquets du PGDG, et en anglais.


En préalable, nettoyer les instances précédemment créées sur le serveur.


**Note :**


##### Réplication asynchrone en flux avec un seul secondaire

> - Créer l’instance principale dans `/var/lib/pgsql/17/data` sur le serveur **p1**. Les sommes de contrôles devront être activées.


**Exemple 3-e054.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo PGSETUP_INITDB_OPTIONS='--data-checksums' \
     /usr/pgsql-17/bin/postgresql-17-setup initdb
```


**Exemple 3-e055.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Initializing database ... OK
```


**Exemple 3-e056.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17.service
```


> - Mettre en place la configuration de la réplication par *streaming*.
> - L’utilisateur dédié sera nommé **repli**.

Au sein du fichier `/var/lib/pgsql/17/data/pg_hba.conf`, ajouter les entrées ci-dessous pour que l’utilisateur **repli** (avec l’attribut *REPLICATION*) ait accès en réplication à l’instance depuis **p2** et **p3** :


**Exemple 3-e057.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
# Allow replication
host    replication     repli           10.0.0.22/32            scram-sha-256
host    replication     repli           10.0.0.23/32            scram-sha-256
```


Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :


**Exemple 3-e058.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            scram-sha-256
host    replication     all             ::1/128                 scram-sha-256
```


Créer le rôle **repli**, qui sera dédié à la réplication, en lui affectant le mot de passe `confidentiel` :


**Exemple 3-e059.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createuser --no-superuser --no-createrole --no-createdb --replication -P repli
```


**Exemple 3-e060.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Enter password for new role:
Enter it again:
```


Configurer ensuite le fichier `.pgpass` de l’utilisateur système `postgres` :


**Exemple 3-e061.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
```


Il faut adapter la configuration pour que PostgreSQL écoute sur l’interface réseau qui sera utilisé pour la réplication (`listen_addresses = '*'`). Afin d’éviter que la mise en réplication n’échoue car les WAL ont été recyclés par PostgreSQL, il faut dire à PostgreSQL de conserver des WAL (`wal_keep_size = '256MB'`). Cette modification est facultative mais elle permet d’avoir plus de confort dans les manipulations. Un effet similaire peut être obtenu en utilisant des slots de réplication. La mise à jour de la configuration doit être réalisée dans `/var/lib/pgsql/17/data/postgresql.conf`.


**Exemple 3-e062.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
```


**Exemple 3-e063.** Extrait de code ou sortie de commande du chapitre 3\.

```text
                    List of configuration parameters
    Parameter     |   Value   |  Type   |  Context   | Access privileges
------------------+-----------+---------+------------+-------------------
 listen_addresses | localhost | string  | postmaster |
 wal_keep_size    | 0         | integer | sighup     |
(2 rows)
```


Comme vous pouvez le voir dans la colonne `context`, `listen_addresses` nécessite un redémarrage pour être pris en compte.


**Exemple 3-e064.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql-17.service  # redémarage
sudo systemctl status postgresql-17.service   # contrôle
```


**Exemple 3-e065.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
```


**Exemple 3-e066.** Extrait de code ou sortie de commande du chapitre 3\.

```text
                  List of configuration parameters
    Parameter     | Value |  Type   |  Context   | Access privileges
------------------+-------+---------+------------+-------------------
 listen_addresses | *     | string  | postmaster |
 wal_keep_size    | 256MB | integer | sighup     |

(2 rows)
```


> - Créer la première instance secondaire sur le serveur **p2**, par copie **à chaud** du répertoire de données avec `pg_basebackup`.
> - Penser à copier les fichiers de configuration

Créer un squelette d’instance sur le serveur **p2** :


**Exemple 3-e067.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
```


Supprimer le répertoire de données :


**Exemple 3-e068.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
rm -Rf /var/lib/pgsql/17/data
```


Créer le fichier `~/.pgpass` :


**Exemple 3-e069.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
```


Utiliser `pg_basebackup` pour créer l’instance secondaire :


**Exemple 3-e070.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pg_basebackup --pgdata /var/lib/pgsql/17/data \
              --progress \
              --write-recovery-conf \
              --checkpoint fast \
              --host 10.0.0.21 \
              --username repli
```


**Exemple 3-e071.** Extrait de code ou sortie de commande du chapitre 3\.

```text
23172/23172 kB (100%), 1/1 tablespace
```


L’option `-R` ou `--write-recovery-conf` de `pg_basebackup` a préparé la configuration de la mise en réplication en créant le fichier `standby.signal` ainsi qu’en configurant `primary_conninfo` dans le fichier `postgresql.auto.conf` (dans les versions antérieures à la 11, il renseignerait `recovery.conf`) :


**Exemple 3-e072.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file /var/lib/pgsql/17/data/standby.signal
```


**Exemple 3-e073.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/pgsql/17/data/standby.signal: empty
```


**Exemple 3-e074.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat /var/lib/pgsql/17/data/postgresql.auto.conf
```


**Exemple 3-e075.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/pgsql/.pgpass''
                    channel_binding=prefer host=10.0.0.21 port=5432
                    sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
                    ssl_min_protocol_version=TLSv1.2
                    gssencmode=prefer krbsrvname=postgres gssdelegation=0
                    target_session_attrs=any load_balance_hosts=disable'
```


Supprimer les traces :


**Exemple 3-e076.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
rm /var/lib/pgsql/17/data/log/*
```


Les fichiers `pg_hba.conf` et du `postgresql.conf` ont été répliqués avec le reste de l’instance présente sur **p1**. Il faut cependant adapter la configuration du `pg_hba.conf` pour qu’elle autorise les connexions de réplication depuis **p1** et **p3** :


**Exemple 3-e077.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e078.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    replication     repli           10.0.0.21/32            scram-sha-256
host    replication     repli           10.0.0.23/32            scram-sha-256
```


> - Démarrer l’instance sur **p2** et s’assurer que la réplication fonctionne bien avec `ps`.
> - Tenter de se connecter au serveur secondaire.
> - Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.

Il ne reste désormais plus qu’à démarrer l’instance secondaire :


**Exemple 3-e079.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start  postgresql-17.service   # redémarrage
sudo systemctl status  postgresql-17.service  # contrôle
```


La commande `ps` suivante permet de voir que les deux serveurs sont lancés :


**Exemple 3-e080.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx
```


Voici le résultat de la commande sur le seveur **p2** :


**Exemple 3-e081.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  PID CMD
12895 /usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
12896  \_ postgres: logger
12897  \_ postgres: checkpointer
12898  \_ postgres: background writer
12899  \_ postgres: startup recovering 000000010000000000000003
12900  \_ postgres: walreceiver streaming 0/3000148
```


La même commande exécutée sur le serveur **p1** :


**Exemple 3-e082.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  PID CMD
12443 /usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
12444  \_ postgres: logger
12445  \_ postgres: checkpointer
12446  \_ postgres: background writer
12448  \_ postgres: walwriter
12449  \_ postgres: autovacuum launcher
12450  \_ postgres: logical replication launcher
12940  \_ postgres: walsender repli 10.0.0.22(50520) streaming 0/3000148
```


Nous avons bien les deux processus de réplication en flux `wal sender` et `wal receiver`.

Créons quelques données sur le principal et assurons-nous qu’elles soient transmises au secondaire :


**Exemple 3-e083.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createdb b1
```


**Exemple 3-e084.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e085.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e086.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t1(id integer);
```


**Exemple 3-e087.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CREATE TABLE
```


**Exemple 3-e088.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# INSERT INTO t1 SELECT generate_series(1, 1000000);
```


**Exemple 3-e089.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INSERT 0 1000000
```


En exécutant la commande suivante sur les serveurs **p1** et **p2**, on constate que le flux a été transmis :


**Exemple 3-e090.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** :


**Exemple 3-e091.** Extrait de code ou sortie de commande du chapitre 3\.

```text
12940  \_ postgres: walsender repli 10.0.0.22(50520) streaming 0/71AFD20
```


Sur **p2** :


**Exemple 3-e092.** Extrait de code ou sortie de commande du chapitre 3\.

```text
12899  \_ postgres: startup recovering 000000010000000000000007
12900  \_ postgres: walreceiver streaming 0/71AFD20
```


Essayons de nous connecter au secondaire et d’exécuter quelques requêtes :


**Exemple 3-e093.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql b1
```


**Exemple 3-e094.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e095.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# SELECT COUNT(*) FROM t1;
```


**Exemple 3-e096.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  count
---------
 1000000
```


**Exemple 3-e097.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t2(id integer);
```


**Exemple 3-e098.** Extrait de code ou sortie de commande du chapitre 3\.

```text
ERROR:  cannot execute CREATE TABLE in a read-only transaction
```


On peut se connecter, lire des données, mais pas écrire.

Le comportement est visible dans le log de l’instance secondaire dans le fichier pointé par `/var/lib/pgsql/17/data/current_logfiles` :


**Exemple 3-e099.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat /var/lib/pgsql/17/data/current_logfiles
```


**Exemple 3-e100.** Extrait de code ou sortie de commande du chapitre 3\.

```text
stderr log/postgresql-Thu.log
```


**Exemple 3-e101.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep "database system is ready" /var/lib/pgsql/17/data/log/postgresql-Thu.log
```


**Exemple 3-e102.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] LOG:  database system is ready to accept read only connections
```


PostgreSQL indique bien qu’il accepte des connexions en lecture seule.

##### Promotion de l’instance secondaire

> - En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.

Sur **p1**, arrêt de l’instance primaire et vérification de son état :


**Exemple 3-e103.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql-17.service
```


**Exemple 3-e104.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e105.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               shut down
Latest checkpoint's REDO location:    0/71AFE08
Latest checkpoint's REDO WAL file:    000000010000000000000007
```


Vérification de l’instance secondaire sur **p2** :


**Exemple 3-e106.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'CHECKPOINT;'
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e107.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               in archive recovery
Latest checkpoint's REDO location:    0/71AFE08
Latest checkpoint's REDO WAL file:    000000010000000000000007
```


L’instance principale est bien arrêtée, l’instance secondaire est bien en `archive recovery` et les deux sont bien synchronisées.

Promotion de l’instance secondaire :


**Exemple 3-e108.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_ctl promote -w -D /var/lib/pgsql/17/data
```


**Exemple 3-e109.** Extrait de code ou sortie de commande du chapitre 3\.

```text
waiting for server to promote.... done
server promoted
```


> - Tenter de se connecter au serveur secondaire fraîchement promu.
> - Les écritures y sont-elles possibles ?

Connectons-nous à ce nouveau primaire et tentons d’y insérer des données :


**Exemple 3-e110.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e111.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e112.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t2(id integer);
```


**Exemple 3-e113.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CREATE TABLE
```


**Exemple 3-e114.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# INSERT INTO t2 SELECT generate_series(1, 1000000);
```


**Exemple 3-e115.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INSERT 0 1000000
```


Les écritures sont désormais possibles sur cette instance.

##### Retour à la normale

> - Reconstruire l’instance initiale sur **p1** comme nouvelle instance secondaire en repartant d’une copie complète de **p2** en utilisant `pg_basebackup`.

Afin de rétablir la situation, nous pouvons réintégrer l’ancienne instance primaire en tant que nouveau secondaire (sur **p1**). Pour ce faire, nous devons re-synchroniser les données. Utilisons `pg_basebackup` comme précédemment après avoir mis de côté les fichiers de l’ancien primaire :


**Exemple 3-e116.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
mv /var/lib/pgsql/17/data /var/lib/pgsql/17/data.old
```


**Exemple 3-e117.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pg_basebackup --pgdata /var/lib/pgsql/17/data \
              --progress \
              --write-recovery-conf \
              --checkpoint fast \
              --host 10.0.0.22 \
              --username repli
```


**Exemple 3-e118.** Extrait de code ou sortie de commande du chapitre 3\.

```text
101603/101603 kB (100%), 1/1 tablespace
```


Vérifier la présence du fichier `standby.signal`.


**Exemple 3-e119.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file /var/lib/pgsql/17/data/standby.signal
```


**Exemple 3-e120.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/pgsql/17/data/standby.signal: empty
```


Contrôler `postgresql.auto.conf` (qui contient potentiellement deux lignes `primary_conninfo` !).


**Exemple 3-e121.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat /var/lib/pgsql/17/data/postgresql.auto.conf
```


**Exemple 3-e122.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# paramètres remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/pgsql/.pgpass''
                    channel_binding=prefer host=10.0.0.22 port=5432
                    sslmode=prefer sslnegotiation=postgres sslcompression=0
                    sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2
                    gssencmode=prefer krbsrvname=postgres gssdelegation=0
                    target_session_attrs=any load_balance_hosts=disable'
```


Supprimer les traces :


**Exemple 3-e123.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
rm /var/lib/pgsql/17/data/log/*
```


Les fichiers de configuration de l’instance ayant été copiés depuis **p2** par le `pg_basebackup`, il faut les recopier depuis notre sauvegarde.


**Exemple 3-e124.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cp /var/lib/pgsql/17/data.old/postgresql.conf /var/lib/pgsql/17/data
cp /var/lib/pgsql/17/data.old/pg_hba.conf /var/lib/pgsql/17/data
```


> - Démarrer cette nouvelle instance.


**Exemple 3-e125.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17.service   # démarrage
sudo systemctl status postgresql-17.service  # contrôle
```


> - Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.

Les processus adéquats sont bien présents :


**Exemple 3-e126.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** :


**Exemple 3-e127.** Extrait de code ou sortie de commande du chapitre 3\.

```text
13666  \_ postgres: startup recovering 00000002000000000000000C
13667  \_ postgres: walreceiver streaming 0/C000060
```


Sur **p2** :


**Exemple 3-e128.** Extrait de code ou sortie de commande du chapitre 3\.

```text
13676  \_ postgres: walsender repli 10.0.0.21(46610) streaming 0/C000060
```


En nous connectant à la nouvelle instance secondaire (**p1**), vérifions que les données précédemment insérées dans la table `t2` sont bien présentes :


**Exemple 3-e129.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e130.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e131.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# SELECT COUNT(*) FROM t2;
```


**Exemple 3-e132.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  count
---------
 1000000
```


> - Inverser à nouveau les rôles des deux instances afin que **p2** redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.

Afin que l’instance **p1** redevienne primaire et celle sur **p2** secondaire, on peut ré-appliquer la procédure de promotion vue précédemment dans l’autre sens.

Arrêt de l’instance primaire (**p2**) et vérification de son état :


**Exemple 3-e133.** Extrait de code ou sortie de commande du chapitre 3\.

```text
sudo systemctl stop postgresql-17.service
```


**Exemple 3-e134.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e135.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               shut down
Latest checkpoint's REDO location:    0/C000148
Latest checkpoint's REDO WAL file:    00000002000000000000000C
```


Vérification de l’instance secondaire **p1** :


**Exemple 3-e136.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'CHECKPOINT;'
/usr/pgsql-17/bin/pg_controldata -D /var/lib/pgsql/17/data/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e137.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               in archive recovery
Latest checkpoint's REDO location:    0/C000148
Latest checkpoint's REDO WAL file:    00000002000000000000000C
```


L’instance principale est bien arrêtée, l’instance secondaire est bien en `archive recovery` et les deux sont bien synchronisées.

Promotion de l’instance secondaire :


**Exemple 3-e138.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_ctl promote -w -D /var/lib/pgsql/17/data
```


**Exemple 3-e139.** Extrait de code ou sortie de commande du chapitre 3\.

```text
waiting for server to promote.... done
server promoted
```


La configuration du fichier `pg_hba.conf` doit être adaptée pour autoriser les connexions depuis `10.0.0.22`. Comme nous l’avons restauré précédemment, elle devrait convenir :


**Exemple 3-e140.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep "^host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e141.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    replication     repli           10.0.0.22/32            scram-sha-256
host    replication     repli           10.0.0.23/32            scram-sha-256
```


Afin que **p2** redevienne l’instance secondaire, créer le fichier `standby.signal`, démarrer le service et vérifier que les processus adéquats sont bien présents :


**Exemple 3-e142.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
touch /var/lib/pgsql/17/data/standby.signal
```


**Exemple 3-e143.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17.service
```


**Exemple 3-e144.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** :


**Exemple 3-e145.** Extrait de code ou sortie de commande du chapitre 3\.

```text
14254  \_ postgres: walsender repli 10.0.0.22(53776) streaming 0/C0002D8
```


Sur **p2** :


**Exemple 3-e146.** Extrait de code ou sortie de commande du chapitre 3\.

```text
13996  \_ postgres: startup recovering 00000003000000000000000C
14172  \_ postgres: walreceiver streaming 0/C0002D8
```


##### Optionnel: Types de réplication synchrone

> - Configurer la réplication synchrone de **p1** vers **p2**.

Sur l’instance primaire éditer le paramétrage de l’instance :


**Exemple 3-e147.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
```


**Exemple 3-e148.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'p2'
```


Sur l’instance secondaire vérifier que l’`application_name` fourni dans le paramètre `primary_conninfo` est bien `p2`.


**Exemple 3-e149.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^primary_conninfo" /var/lib/pgsql/17/data/postgresql.auto.conf
```


**Exemple 3-e150.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p2'
```


Recharger la configuration sur les deux serveurs :


**Exemple 3-e151.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Lorsque l’on liste les connexions de réplication depuis **p1**, on voit la connexion synchrone :


**Exemple 3-e152.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication;
```


**Exemple 3-e153.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 pid  | application_name | sync_priority | sync_state
------+------------------+---------------+------------
 1623 | p2               |             1 | sync
(1 row)
```


> - Stopper l’instance secondaire **p2**, créer une table `t1`, que se passe-t-il ?

Arrêter l’instance sur **p2** :


**Exemple 3-e154.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql-17
```


Créer une table `t1` sur **p1** :


**Exemple 3-e155.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
CREATE TABLE t1();
```


La transaction implicite qui englobe la création de `t1` est en attente. La vue `pg_stat_activity` nous apprend que l’instance est attente de la réplication synchrone.


**Exemple 3-e156.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, usename, datname, wait_event_type, wait_event, state, query
  FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t1%' \gx
```


**Exemple 3-e157.** Extrait de code ou sortie de commande du chapitre 3\.

```text
-[ RECORD 1 ]---+-------------------
pid             | 1882
usename         | postgres
datname         | postgres
wait_event_type | IPC
wait_event      | SyncRep
state           | active
query           | CREATE TABLE t1();
```


À ce moment, la table n’est pas visible sur **p1**.


**Exemple 3-e158.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
\dt t1
```


**Exemple 3-e159.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Did not find any relation named "t1".
```


Lorsque l’on redémarre le service PostgreSQL sur **p2**, la session en attente se termine et la table est visible.


**Exemple 3-e160.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17
```


> - Stopper l’instance **p2**, créer une table **t2**. Annuler la requête bloquée sur **p1** avec la commande `pg_cancel_backend()`, qu’observez-vous ?
> - Redémarrer **p2**.

Si l’on reproduit les mêmes étapes en créant une table `t2` et que l’on annule la session avec `pg_cancel_backend()`, la réplication synchrone est débloquée et la table est visible :


**Exemple 3-e161.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, usename, datname, wait_event_type, wait_event, state, query,
       pg_cancel_backend(pid) as cancelled
  FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t2%' \gx
\dt t2
```


**Exemple 3-e162.** Extrait de code ou sortie de commande du chapitre 3\.

```text
-[ RECORD 1 ]---+-------------------
pid             | 1882
usename         | postgres
datname         | postgres
wait_event_type | IPC
wait_event      | SyncRep
state           | active
query           | CREATE TABLE t2();
cancelled       | t

        List of relations
 Schema | Name | Type  |  Owner
--------+------+-------+----------
 public | t2   | table | postgres
(1 row)
```


Sur **p1**, on observe :


**Exemple 3-e163.** Extrait de code ou sortie de commande du chapitre 3\.

```text
WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.
CREATE TABLE
```


À ce stade, si un incident provoquant la perte du serveur et de son stockage survient sur **p1** et que les WAL ne sont pas archivées, **p2** n’aura jamais reçus les modifications et elles seront perdues. Si l’on redémarre **p2**, elle rattrape son retard et la table `t2` y est visible.

> - Suivre la méthode utilisée dans les TP précédant pour mettre en place une seconde instance standby sur p3.

Petit rappel des opérations :

- sauvegarder la configuration (ici c’est inutile) et traces ;
- nettoyer le répertoire de données existant et ses traces ;
- effectuer le `pg_basebackup` ;
- vérifier la configuration ;
  - fichier flag (`standby.signal`) ;
  - accès extérieur (`pg_hba.conf`) ;
  - configuration générale (`postgresql.conf`) ;
  - configuration de la connexion (`postgresql.auto.conf`) ;
  - mot de passe (`.pgpass`) ;
- démarrer l’instance.

> - Configurer la réplication synchrone de **p1** vers **p2** et **p3** en utilisant la syntaxe `p2, p3`. Observer les champs `sync_priority` et `sync_state` de `pg_stat_replication`.

La configuration a été mise à jour et prise en compte :


**Exemple 3-e164.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
```


**Exemple 3-e165.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'p2, p3'
```


Note: une syntaxe équivalente est `FIRST 1 (p2, p3)` ou `FIRST (p2, p3)`, elle ne sera pas utilisée parce que Patroni ne l’utilise pas.

La vue `pg_stat_replication` contient désormais les informations suivantes :


**Exemple 3-e166.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication
ORDER BY application_name;
```


**Exemple 3-e167.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2025 | p2               |             1 | sync
  1664 | p3               |             2 | potential
(2 rows)
```


On voit que l’instance **p3** est marquée comme *potential*.

> - Stopper **p2** puis **p3**, redémarrer **p3** puis **p2**. À chaque étape regarder l’état de la vue `pg_stat_replication`.

Voici le contenu de `pg_stat_replication` en fonction de l’état des instances :

- **p2** arrêtée : On note que `sync priority` est toujours à 2 sur **p3**, en effet cette valeur dépend de la position de l’instance dans le paramètre `synchronous_standby_name`.


**Exemple 3-e168.** Extrait de code ou sortie de commande du chapitre 3\.

```text
    pid  | application_name | sync_priority | sync_state
  -------+------------------+---------------+------------
    1664 | p3               |             2 | sync
  (1 row)
  ```


- **p3** stoppée : la vue est vide.

- **p3** redémarrée : La vue est revenue dans son état précédent, seul le `pid` à changer.


**Exemple 3-e169.** Extrait de code ou sortie de commande du chapitre 3\.

```text
    pid  | application_name | sync_priority | sync_state
  -------+------------------+---------------+------------
    2035 | p3               |             2 | sync
  (1 row)
  ```


- **p2** redémarrée : La vue est revenue dans son état initial. **p2** est synchrone.


**Exemple 3-e170.** Extrait de code ou sortie de commande du chapitre 3\.

```text
     pid  | application_name | sync_priority | sync_state
   -------+------------------+---------------+------------
     2036 | p2               |             1 | sync
     2035 | p3               |             2 | potential
   (2 rows)
  ```


D’un point de vue infrastructure, on observe que tant que **p2** est disponible, c’est elle qui sera synchrone. Cela correspond bien à la syntaxe équivalente `FRIST 1 (p2, p3)` qui indique bien que **p2** à la plus haute priorité.

D’un point de vue expérience utilisateur, tant qu’il y a au moins une instance synchrone disponible, les transactions ne sont pas bloquées en attente de commit.

> - Modifier la configuration de **p1** pour demander deux standbys synchrones.

La configuration suivante a été mise à jour et prise en compte :


**Exemple 3-e171.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
```


**Exemple 3-e172.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = '2 (p2, p3)'
```


Note: une syntaxe équivalente est `FIRST 2 (p2, p3)`.

La vue `pg_stat_replication` contient désormais les informations suivantes :


**Exemple 3-e173.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication
ORDER BY application_name;
```


**Exemple 3-e174.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2036 | p2               |             1 | sync
  2035 | p3               |             2 | sync
(2 rows)
```


On observe que les deux standbys sont synchrones et que la priorité n’a pas changé. Si l’un des serveurs s’arrête toute transaction validée sur **p1** restera en attente car la configuration exige la présence de deux instances.

> - Configurer `synchronous_standby_names` à `*`. Répéter les mêmes tests que précédemment. Qu’observez-vous ?

On observe que le réplica synchrone est **p3** et que la priorité des deux instances est 1.


**Exemple 3-e175.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication ORDER BY application_name;
```


**Exemple 3-e176.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2036 | p2               |             1 | potential
  2035 | p3               |             1 | sync
(2 rows)
```


Si l’on arrête **p2** et **p3** et que l’on redémarre **p2** avant **p3**, on obtient :


**Exemple 3-e177.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2104 | p2               |             1 | sync
  2105 | p3               |             1 | potential
(2 rows)
```


> - Mettre en place la réplication synchrone par *quorum* avec un serveur synchrone minimum.

La configuration a été mise à jour et prise en compte par un `pg_reload_conf()` :


**Exemple 3-e178.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
```


**Exemple 3-e179.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'ANY 1 (p2, p3)'
```


Dans `pg_stat_replication`, on observe que le statut des deux instances est `quorum` et que leur priorité est identique :


**Exemple 3-e180.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2104 | p2               |             1 | quorum
  2105 | p3               |             1 | quorum
(2 rows)
```


Dans cet état, après un commit, la réplication synchrone rend la main dès que les données sont répliquées sur n’importe lequel des deux serveurs.

> - Désactiver la réplication synchrone.

Modifier le paramètre `synchronous_standby_names` sur **p1** :


**Exemple 3-e181.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /var/lib/pgsql/17/data/postgresql.conf
```


**Exemple 3-e182.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = ''
```


Recharger la configuration.


**Exemple 3-e183.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Contrôler le contenu de la vue `pg_stat_replication` :


**Exemple 3-e184.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
  2104 | p2               |             0 | async
  2105 | p3               |             0 | async
(2 rows)
```


##### Optionnel: Log shipping et initialisation d’instance avec pgBackRest

> - Installer pgBackRest sur tous les serveurs (**p1**, **p2**, **p3**, **b1**).

Il est préférable d’installer les paquets provenant de pgdg. Il faut donc configurer le dépôt associé sur le serveur. Dans notre cas, c’est déjà fait.

L’installation se fait ensuite simplement.


**Exemple 3-e185.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo dnf install -y epel-release
sudo dnf install -y pgbackrest
```


Il faut ensuite échanger les clés SSH entre les serveurs ou mettre en place le serveur TLS de pgBackRest. Pour ce TP, nous utiliserons ssh. Les clés ont déjà été déjà échangées.

> - Configurer pgBackRest sur le serveur de sauvegarde.

Le dépôt de sauvegarde sera créé par l’utilisateur **postgres**.


**Exemple 3-e186.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo install -o postgres -g postgres -m 0755 -d /var/lib/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf

cat  << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-level-console=info
log-level-file=detail

[main]
pg1-host=p1
pg1-path=/var/lib/pgsql/17/data
pg2-host=p2
pg2-path=/var/lib/pgsql/17/data
pg3-host=p3
pg3-path=/var/lib/pgsql/17/data
_EOF_
```


Note: toutes les instances doivent exister et être accessible pour que la commande `stanza-create` fonctionne.

> - Configurer pgBackRest sur les serveurs de base de données.

Sur les serveurs **p1**, **p2** et **p3** :


**Exemple 3-e187.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf

cat  << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-host=b1
repo1-host-user=postgres
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-path=/var/log/pgbackrest
log-level-console=info
log-level-file=detail

[main]
pg1-path=/var/lib/pgsql/17/data
_EOF_
```


Tester la connexion entre **b1** et **p1**, **p2**, **p3** et inversement.

Depuis *p1*, *p2*, *p3* :


**Exemple 3-e188.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ssh -o "StrictHostKeyChecking=no" b1 hostname
```


Depuis *b1* :


**Exemple 3-e189.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
for i in $(seq 1 3); do ssh -o "StrictHostKeyChecking=no" p$i hostname; done
```


La configuration de PostgreSQL doit aussi être mise à jour :


**Exemple 3-e190.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat  << _EOF_ >> /var/lib/pgsql/17/data/postgresql.conf
archive_mode = 'on'
archive_command = 'pgbackrest --stanza=main archive-push %p'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
_EOF_
```


L’activation de l’archivage nécessitant encore un redémarrage en version 17 :


**Exemple 3-e191.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql-17
```


> - Créer la stanza, tester l’archivage.

Sur le serveur de sauvegarde *b1* :


**Exemple 3-e192.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main stanza-create
```


**Exemple 3-e193.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: stanza-create command begin 2.54.2: --exec-id=1624-fb78780f --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: stanza-create for stanza 'main' on repo1
[…] INFO: stanza-create command end: completed successfully (7730ms)
```


**Exemple 3-e194.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main check
```


**Exemple 3-e195.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: check command begin 2.54.2: --exec-id=2135-d604f794 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: check repo1 (standby)
[…] INFO: switch wal not performed because this is a standby
[…] INFO: check repo1 configuration (primary)
[…] INFO: check repo1 archive for WAL (primary)
[…] INFO: WAL segment 00000003000000000000000C successfully archived to '/var/lib/pgbackrest/archive/main/17-1/0000000300000000/00000003000000000000000C-92677832c9edd5ea66f0c498f66274c82d1868b8.gz' on repo1
[…] INFO: check command end: completed successfully (3007ms)
```


> - Lancer une sauvegarde.


**Exemple 3-e196.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main backup --type=full --start-fast
```


**Exemple 3-e197.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: backup command begin 2.54.2: --exec-id=2171-3198a97f --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/pgsql/17/data --pg2-path=/var/lib/pgsql/17/data --pg3-path=/var/lib/pgsql/17/data --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main --start-fast --type=full
[…] INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
[…] INFO: backup start archive = 00000003000000000000000E, lsn = 0/E000028
[…] INFO: check archive for prior segment 00000003000000000000000D
[…] INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
[…] INFO: backup stop archive = 00000003000000000000000E, lsn = 0/E000158
[…] INFO: check archive for segment(s) 00000003000000000000000E:00000003000000000000000E
[…] INFO: new backup label = 20250311-134434F
[…] INFO: full backup size = 64MB, file total = 1271
[…] INFO: backup command end: completed successfully (48106ms)
[…] INFO: expire command begin 2.54.2: --exec-id=2171-3198a97f --log-level-console=info --log-level-file=detail --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main
[…] INFO: expire command end: completed successfully (204ms)
```


**Exemple 3-e198.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main info
```


**Exemple 3-e199.** Extrait de code ou sortie de commande du chapitre 3\.

```text
stanza: main
    status: ok
    cipher: none

    db (current)
        wal archive min/max (17): 00000003000000000000000C/00000003000000000000000E

        full backup: 20250311-134434F
            timestamp start/stop: 2025-03-11 13:44:34+00 / 2025-03-11 13:45:18+00
            wal start/stop: 00000003000000000000000E / 00000003000000000000000E
            database size: 64MB, database backup size: 64MB
            repo1: backup set size: 7.8MB, backup size: 7.8MB
```


> - Si **p3** existe, le stopper et détruire le répertoire de données.


**Exemple 3-e200.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql-17
rm -Rf /var/lib/pgsql/17/data
```


> - Créer l’instance à partir de la sauvegarde et s’assure qu’elle est bien en réplication.


**Exemple 3-e201.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main restore \
           --delta --type=standby \
           --recovery-option=primary_conninfo="user=repli host=10.0.0.21 port=5432 application_name=p3"
```


**Exemple 3-e202.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: restore command begin 2.54.2: --delta --exec-id=2446-9c0d5025 --log-level-console=info --log-level-file=detail --log-path=/var/log/pgbackrest --pg1-path=/var/lib/pgsql/17/data --recovery-option="primary_conninfo=user=repli host=10.0.0.21 port=5432 application_name=p3" --repo1-host=b1 --repo1-host-user=postgres --repo1-path=/var/lib/pgbackrest --stanza=main --type=standby
[…] WARN: --delta or --force specified but unable to find 'PG_VERSION' or 'backup.manifest' in '/var/lib/pgsql/17/data' to confirm that this is a valid $PGDATA directory. --delta and --force have been disabled and if any files exist in the destination directories the restore will be aborted.
[…] INFO: repo1: restore backup set 20250311-134434F, recovery will start at 2025-03-11 13:44:34
[…] INFO: write updated /var/lib/pgsql/17/data/postgresql.auto.conf
[…] INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
[…] INFO: restore size = 64MB, file total = 1271
[…] INFO: restore command end: completed successfully (42960ms)
```


Dans le cas présent, on note que pgBackRest a détecté qu’il n’y avait pas de données dans le répertoire de données de l’instance. Il a donc désactivé l’option `--delta` de lui-même. Si un répertoire de données existe seul les fichiers qui ont changé sont restaurés.

Le fichier `standby.signal` a bien été créé :


**Exemple 3-e203.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file ~/17/data/standby.signal
```


**Exemple 3-e204.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/pgsql/17/data/standby.signal: empty
```


La configuration a bien été mise à jour :


**Exemple 3-e205.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat ~/17/data/postgresql.auto.conf
```


**Exemple 3-e206.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.

# Recovery settings generated by pgBackRest restore on 2025-03-11 14:07:49
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p3'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
```


Adapter la configuration pour autoriser les connexions depuis **p1** et **p2** en cas de promotion :


**Exemple 3-e207.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "host.*replication" /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e208.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    replication     repli           10.0.0.21/32            scram-sha-256
host    replication     repli           10.0.0.22/32            scram-sha-256
```


Démarrer l’instance :


**Exemple 3-e209.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17
```


L’instance est bien démarrée et la réplication fonctionne :


**Exemple 3-e210.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e211.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CMD
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup waiting for 000000030000000000000010
```


> - Interdire la connexion à **p1** depuis **p3** dans le `pg_hba.conf`, générer de l’activité, que se passe-t-il sur **p3** ?

Ajouter une ligne `reject` au `pg_hba.conf` avant les lignes existantes (ici ligne 118) sur **p1** :


**Exemple 3-e212.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -nE "10\.0\.0\.23" /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e213.** Extrait de code ou sortie de commande du chapitre 3\.

```text
121:host    replication     repli           10.0.0.23/32            reject
122:host    replication     repli           10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p1**.


**Exemple 3-e214.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Redémarrer **p3**, cela permet de ré-initialiser la connexion de réplication.


**Exemple 3-e215.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql-17
```


On voit que la réplication a décroché.


**Exemple 3-e216.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e217.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000010000000000000009
```


Générer de l’activité sur **p1** :


**Exemple 3-e218.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createdb pgbench
/usr/pgsql-17/bin/pgbench -i -s10 pgbench
```


On observe que l’instance a continué de consommer les WAL en provenance de **p1** via le dépôt de sauvegarde de pgBackRest.


**Exemple 3-e219.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e220.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup waiting for 000000010000000000000019
```


> - Faire marche arrière sur la modification, que se passe-t-il sur **p3** ?

Retire la ligne suivante au `pg_hba.conf` avant les lignes existantes :


**Exemple 3-e221.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -nE "10\.0\.0\.23" /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e222.** Extrait de code ou sortie de commande du chapitre 3\.

```text
134:host    replication     repli           10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p1**.


**Exemple 3-e223.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


La connexion de réplication est de nouveau en place.


**Exemple 3-e224.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e225.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000010000000000000010
postgres: walreceiver streaming 0/10F3E2C0
```


Dans les traces, on voit que le log shipping a été abandonné au profil de la réplication :


**Exemple 3-e226.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -A20 -B1 -E "started streaming WAL" /var/lib/pgsql/17/data/log/postgresql-Wed.log
```


**Exemple 3-e227.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INFO: archive-get command end: completed successfully (412ms)
LOG:  started streaming WAL from primary at 0/10000000 on timeline 1
```


##### Optionnel: utilisation de pg_rewind

> - Promouvoir l’instance **p2**. Générer de l’activité sur **p1**.

Promouvoir l’instance **p2** :


**Exemple 3-e228.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "SELECT pg_promote();"
```


Sur l’instance **p1**, exécuter `pgbench` pour simuler que des écritures ont continué à arriver après la promotion :


**Exemple 3-e229.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
dropdb pgbench
createdb pgbench
/usr/pgsql-17/bin/pgbench -i -s10 pgbench
```


> - Stopper **p1** et configurer la réplication pour raccrocher **p1** à **p2**.

Stopper **p1** :


**Exemple 3-e230.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql-17
```


Créer un fichier `standby.signal` :


**Exemple 3-e231.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
touch /var/lib/pgsql/17/data/standby.signal
```


Vérifier que le paramètre `primary_conninfo` est correctement configuré :


**Exemple 3-e232.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^primary_conninfo" /var/lib/pgsql/17/data/postgresql.auto.conf
```


**Exemple 3-e233.** Extrait de code ou sortie de commande du chapitre 3\.

```text
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
```


Démarrer l’instance :


**Exemple 3-e234.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17
```


> Qu’observez-vous ?

L’instance refuse de démarrer.

Les traces indiquent que les deux instances ont divergé au lsn `0/12FFF6D8` qui se trouve après le dernier checkpoint de la primaire : `0/19000028` sur la timeline 1.


**Exemple 3-e235.** Extrait de code ou sortie de commande du chapitre 3\.

```text
LOG:  database system is shut down
LOG:  starting PostgreSQL 17.4 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5), 64-bit
LOG:  listening on IPv4 address "0.0.0.0", port 5432
LOG:  listening on IPv6 address "::", port 5432
LOG:  listening on Unix socket "/run/postgresql/.s.PGSQL.5432"
LOG:  listening on Unix socket "/tmp/.s.PGSQL.5432"
LOG:  database system was shut down at 2025-03-11 15:45:42 UTC
LOG:  restored log file "00000002.history" from archive
LOG:  entering standby mode
FATAL:  requested timeline 2 is not a child of this server's history
DETAIL:  Latest checkpoint is at 0/19000028 on timeline 1, but in the history of the requested timeline, the server forked off from that timeline at 0/12FFF6D8.
LOG:  startup process (PID 8973) exited with exit code 1
LOG:  aborting startup due to startup process failure
LOG:  database system is shut down
```


À ce stade, il faut reconstruire la primaire complètement ou partiellement.

> - Utiliser `pg_rewind` pour remettre l’instance **p1** d’aplomb.

Les instances que nous avons créées utilisent les sommes de contrôle (*checksums*), ce qui inclut des *hint* nécessaires à l’utilisation de pg_rewind :


**Exemple 3-e236.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_controldata /var/lib/pgsql/17/data \
  | grep "Data page checksum version"
```


**Exemple 3-e237.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Data page checksum version:           1
```


Un autre pré-requis est que l’instance a été arrêtée proprement, chose que nous avons fait précédemment.

Créer un utilisateur dédié à pg_rewind sur **p2** :


**Exemple 3-e238.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
CREATE USER rewind_user LOGIN;
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean)
  TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint,
  boolean) TO rewind_user;

\password rewind_user
```


Ajouter le mot de passe du nouvel utilisateur aux fichiers `.pgpass` de tous les serveurs :


**Exemple 3-e239.** Extrait de code ou sortie de commande du chapitre 3\.

```text
echo "*:*:*:rewind_user:rewind_password" >> ~/.pgpass
```


Mettre a jour le `pg_hba` des serveurs (ici pour le serveur **p2**) :


**Exemple 3-e240.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep rewind_user /var/lib/pgsql/17/data/pg_hba.conf
```


**Exemple 3-e241.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    postgres        rewind_user     10.0.0.21/32            scram-sha-256
host    postgres        rewind_user     10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p2** :


**Exemple 3-e242.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "SELECT pg_reload_conf();"
```


**Exemple 3-e243.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 pg_reload_conf
----------------
 t
(1 row)
```


Nous pouvons désormais lancer `pg_rewind` (ici en mode à blanc) :


**Exemple 3-e244.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/pgsql-17/bin/pg_rewind \
  --source-server="host=10.0.0.22 port=5432 user=rewind_user dbname=postgres" \
  --target-pgdata=/var/lib/pgsql/17/data \
  --progress \
  --write-recovery-conf \
  --restore-target-wal \
  --dry-run
```


Les traces nous montrent les deux serveurs ont divergé au lsn `0/21BE14E8`.


**Exemple 3-e245.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: connected to server
pg_rewind: servers diverged at WAL location 0/12FFF6D8 on timeline 1
```


`pg_rewind` lit les WAL présent sur **p1** et liste tous les blocs modifiés entre le checkpoint précédant le point de divergence et le moment ou l’instance a été arrêtée. Cette liste permettra de ne récupérer que ce qui n’a pas bougé sur la nouvelle instance. Certains fichiers, qui ne sont pas protégés par les WAL devront être copiés entièrement. C’est notamment le cas de la configuration présente dans le répertoire de données de l’instance.


**Exemple 3-e246.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: rewinding from last common checkpoint at 0/10F3E788 on timeline 1
pg_rewind: reading source file list
pg_rewind: reading target file list
pg_rewind: reading WAL in target
```


Une fois la liste de ce qui doit être fait établie : la copie est lancée.


**Exemple 3-e247.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: need to copy 350 MB (total source directory size is 518 MB)
359043/359043 kB (100%) copied
```


Le control file est ensuite modifié et un fichier backup label généré.


**Exemple 3-e248.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: creating backup label and updating control file
pg_rewind: syncing target data directory
pg_rewind: Done!
```


Le test a fonctionné nous pouvons le relancer sans l’option `--dry-run`.

L’option `--write-recovery-conf` nous permet de :

- créer le fichier `standby.signal` :


**Exemple 3-e249.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
  file /var/lib/pgsql/17/data/standby.signal
  ```


**Exemple 3-e250.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  /var/lib/pgsql/17/data/standby.signal: empty
  ```


- mettre à jour la configuration de la réplication :


**Exemple 3-e251.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
  cat /var/lib/pgsql/17/data/postgresql.auto.conf
  ```


**Exemple 3-e252.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  # Do not edit this file manually!
  # It will be overwritten by the ALTER SYSTEM command.
  primary_conninfo = 'user=repli passfile=''/var/lib/pgsql/.pgpass'' channel_binding=prefer host=10.0.0.21 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
  primary_conninfo = 'user=rewind_user passfile=''/var/lib/pgsql/.pgpass'' channel_binding=prefer host=10.0.0.22 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
  ```


  Cette configuration est malheureusement incorrecte pour notre besoin. La première correspond au contenu du fichier **p2**, la seconde utilise l’utilisateur `rewind_user`. Nous allons donc la remplacer par ce qui nous intéresse :


**Exemple 3-e253.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
  primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
  ```


L’instance peut ensuite être démarrée :


**Exemple 3-e254.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql-17
```


L’instance est bien démarrée en tant que secondaire et réplique :


**Exemple 3-e255.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e256.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/pgsql-17/bin/postgres -D /var/lib/pgsql/17/data/
postgres: logger
postgres: checkpointer
postgres: background writer
postgres: startup recovering 000000020000000000000013
postgres: walreceiver
```


La seconde instance secondaire a elle aussi décroché, à vous de jouer !


#### Sur Debian 12


**Note :**


**Conseil :**


Cette solution se base sur un système Debian 12 (ou supérieur), installé à minima depuis les paquets du PGDG, et en anglais.


Au préalable, nettoyer les instances précédemment créées sur le serveur.


**Exemple 3-e257.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
# pg_dropcluster --stop 17 main
```


**Note :**


**Conseil :**


Dans ce TP, les ordres sont à entrer soit comme utilisateur **root** ou avec `sudo`, soit comme utilisateur **postgres** (qui n’a pas droit au `sudo`).


##### Réplication asynchrone en flux avec un seul secondaire

> Ce TP suppose que les binaires de PostgreSQL sont présents mais qu’aucune instance n’est en place. - Vérifier avec `pg_lsclusters` que les binaires de PostgreSQL sont installés et qu’il n’existe pas d’autre instance. - Créer l’instance principale en utilisant `pg_createcluster` sur le serveur **p1**. Les sommes de contrôles devront être activées.

`pg_lsclusters` ne doit rien envoyer. Si la commande n’existe pas, installer les binaires par PostgreSQL depuis le PGDG, si possible sans créer d’instance (voir [référence](https://kb.dalibo.com/installation/plateformes/linux/debian/installation_debian/#installation-via-le-gestionnaire-de-paquets)). Si elle existe, la supprimer avec `pg_dropcluster`.

Création de l’instance :


**Exemple 3-e258.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo pg_createcluster 17 main -- -k
```


**Exemple 3-e259.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Creating new PostgreSQL cluster 17/main ...

Ver Cluster Port Status Owner    Data directory              Log file
17  main    5432 down   postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
```


Le répertoire des données se trouvera sous `/var/lib/postgresql/17/main`.

Démarrer l’instance, soit avec :


**Exemple 3-e260.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo pg_ctlcluster start 17 main
```


soit explicitement via systemd :


**Exemple 3-e261.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


> - Vérifier la configuration de la réplication par *streaming*.
> - L’utilisateur dédié sera nommé **repli**.

Au sein du fichier `/etc/postgresql/17/main/pg_hba.conf`, ajouter les entrées ci-dessous pour que l’utilisateur **repli** (avec l’attribut *REPLICATION*) ait accès en réplication à l’instance depuis **p2** et **p3** :


**Exemple 3-e262.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
# Allow replication
host    replication     repli           10.0.0.22/32            scram-sha-256
host    replication     repli           10.0.0.23/32            scram-sha-256
```


Les entrées configurées par défaut qui permettent de faire de la réplication en local peuvent être supprimées :


**Exemple 3-e263.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            scram-sha-256
host    replication     all             ::1/128                 scram-sha-256
```


Créer le rôle **repli**, qui sera dédié à la réplication, en lui affectant le mot de passe `confidentiel` :


**Exemple 3-e264.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createuser --no-superuser --no-createrole --no-createdb --replication -P repli
```


**Exemple 3-e265.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Enter password for new role:
Enter it again:
```


Configurer ensuite le fichier `.pgpass` de l’utilisateur système `postgres` :


**Exemple 3-e266.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
```


Il faut adapter la configuration pour que PostgreSQL écoute sur l’interface réseau qui sera utilisé pour la réplication (`listen_addresses = '*'`). Afin d’éviter que la mise en réplication n’échoue car les WAL ont été recyclés par PostgreSQL, il faut dire à PostgreSQL de conserver des WAL (`wal_keep_size = '256MB'`). Cette modification est facultative mais elle permet d’avoir plus de confort dans les manipulations. Un effet similaire peut être obtenu en utilisant des slots de réplication. La mise à jour de la configuration doit être réalisée dans `/etc/postgresql/17/main`.


**Exemple 3-e267.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
```


**Exemple 3-e268.** Extrait de code ou sortie de commande du chapitre 3\.

```text
                    List of configuration parameters
    Parameter     |   Value   |  Type   |  Context   | Access privileges
------------------+-----------+---------+------------+-------------------
 listen_addresses | localhost | string  | postmaster |
 wal_keep_size    | 0         | integer | sighup     |
(2 rows)
```


Comme vous pouvez le voir dans la colonne `context`, `listen_addresses` nécessite un redémarrage.


**Exemple 3-e269.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql@17-main  # redémarage
sudo systemctl status postgresql@17-main   # contrôle
```


**Exemple 3-e270.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "\dconfig+ (listen_addresses|wal_keep_size)"
```


**Exemple 3-e271.** Extrait de code ou sortie de commande du chapitre 3\.

```text
                  List of configuration parameters
    Parameter     | Value |  Type   |  Context   | Access privileges
------------------+-------+---------+------------+-------------------
 listen_addresses | *     | string  | postmaster |
 wal_keep_size    | 256MB | integer | sighup     |
(2 rows)
```


> - Créer la première instance secondaire sur le serveur **p2**, par copie **à chaud** du répertoire de données avec `pg_basebackup`.
> - Penser à copier les fichiers de configuration

Nous allons réutiliser le squelette de l’instance `main` pour créer l’instance secondaire.


**Exemple 3-e272.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo pg_createcluster 17 main -- -k
pg_lsclusters 17 main
```


**Exemple 3-e273.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Ver Cluster Port Status Owner    Data directory              Log file
17  main    5432 down   postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
```


Supprimer le répertoire de données :


**Exemple 3-e274.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
rm -Rf /var/lib/postgresql/17/main
```


Créer le fichier `~/.pgpass` :


**Exemple 3-e275.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
echo '*:*:*:repli:confidentiel' >> ~/.pgpass
chmod 600 ~/.pgpass
```


Utiliser `pg_basebackup` pour créer l’instance secondaire :


**Exemple 3-e276.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pg_basebackup --pgdata /var/lib/postgresql/17/main \
              --progress \
              --write-recovery-conf \
              --checkpoint fast \
              --host 10.0.0.21 \
              --username repli
```


**Exemple 3-e277.** Extrait de code ou sortie de commande du chapitre 3\.

```text
23110/23110 kB (100%), 1/1 tablespace
```


L’option `-R` ou `--write-recovery-conf` de `pg_basebackup` a préparé la configuration de la mise en réplication en créant le fichier `standby.signal` ainsi qu’en configurant `primary_conninfo` dans le fichier `postgresql.auto.conf` (dans les versions antérieures à la 11, il renseignerait `recovery.conf`) :


**Exemple 3-e278.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file /var/lib/postgresql/17/main/standby.signal
```


**Exemple 3-e279.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/postgresql/17/main/standby.signal: empty
```


**Exemple 3-e280.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat /var/lib/postgresql/17/main/postgresql.auto.conf
```


**Exemple 3-e281.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass''
                    channel_binding=prefer host=10.0.0.21 port=5432
                    sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
                    ssl_min_protocol_version=TLSv1.2
                    gssencmode=prefer krbsrvname=postgres gssdelegation=0
                    target_session_attrs=any load_balance_hosts=disable'
```


Il faut répercuter les modifications du `pg_hba.conf` et du `postgresql.conf` en les adaptant si nécessaire sur la nouvelle instance :


**Exemple 3-e282.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "host.*replication" /etc/postgresql/17/main/pg_hba.conf
```


**Exemple 3-e283.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    replication     repli           10.0.0.21/32            scram-sha-256
host    replication     repli           10.0.0.23/32            scram-sha-256
```


**Exemple 3-e284.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^(listen_addresses|wal_keep_size)" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e285.** Extrait de code ou sortie de commande du chapitre 3\.

```text
listen_addresses = '*'
wal_keep_size = '256MB'
```


> - Démarrer l’instance sur **p2** et s’assurer que la réplication fonctionne bien avec `ps`.
> - Tenter de se connecter au serveur secondaire.
> - Créer quelques tables pour vérifier que les écritures se propagent du primaire au secondaire.

Il ne reste désormais plus qu’à démarrer l’instance secondaire :


**Exemple 3-e286.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start  postgresql@17-main   # redémarrage
sudo systemctl status  postgresql@17-main  # contrôle
```


La commande `ps` suivante permet de voir que les deux serveurs sont lancés :


**Exemple 3-e287.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx
```


Voici le résultat de la commande sur le seveur **p2** :


**Exemple 3-e288.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 PID CMD
4247 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
4248  \_ postgres: 17/main: checkpointer
4249  \_ postgres: 17/main: background writer
4250  \_ postgres: 17/main: startup recovering 000000010000000000000003
4251  \_ postgres: 17/main: walreceiver streaming 0/3000148
```


La même commande exécutée sur le serveur **p1** :


**Exemple 3-e289.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 PID CMD
4084 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
4085  \_ postgres: 17/main: checkpointer
4086  \_ postgres: 17/main: background writer
4088  \_ postgres: 17/main: walwriter
4089  \_ postgres: 17/main: autovacuum launcher
4090  \_ postgres: 17/main: logical replication launcher
4252  \_ postgres: 17/main: walsender repli 10.0.0.22(48570) streaming 0/3000148
```


Nous avons bien les deux processus de réplication en flux `wal sender` et `wal receiver`.

Créons quelques données sur le principal et assurons-nous qu’elles soient transmises au secondaire :


**Exemple 3-e290.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createdb b1
```


**Exemple 3-e291.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e292.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e293.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t1(id integer);
```


**Exemple 3-e294.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CREATE TABLE
```


**Exemple 3-e295.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# INSERT INTO t1 SELECT generate_series(1, 1000000);
```


**Exemple 3-e296.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INSERT 0 1000000
```


En exécutant la commande suivante sur les serveurs **p1** et **p2**, on constate que le flux a été transmis :


**Exemple 3-e297.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** :


**Exemple 3-e298.** Extrait de code ou sortie de commande du chapitre 3\.

```text
4252  \_ postgres: 17/main: walsender repli 10.0.0.22(48570) streaming 0/71A9E50
```


Sur **p2** :


**Exemple 3-e299.** Extrait de code ou sortie de commande du chapitre 3\.

```text
4250  \_ postgres: 17/main: startup recovering 000000010000000000000007
4251  \_ postgres: 17/main: walreceiver streaming 0/71A9E50
```


Essayons de nous connecter au secondaire et d’exécuter quelques requêtes :


**Exemple 3-e300.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e301.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e302.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# SELECT COUNT(*) FROM t1;
```


**Exemple 3-e303.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  count
---------
 1000000
```


**Exemple 3-e304.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t2(id integer);
```


**Exemple 3-e305.** Extrait de code ou sortie de commande du chapitre 3\.

```text
ERROR:  cannot execute CREATE TABLE in a read-only transaction
```


On peut se connecter, lire des données, mais pas écrire.

Le comportement est visible dans le log de l’instance secondaire dans le fichier `/var/log/postgresql/postgresql-17-main.log` :


**Exemple 3-e306.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep "database system is ready" /var/log/postgresql/postgresql-17-main.log
```


**Exemple 3-e307.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] LOG:  database system is ready to accept read only connections
```


PostgreSQL indique bien qu’il accepte des connexions en lecture seule.

##### Promotion de l’instance secondaire

> - En respectant les étapes de vérification de l’état des instances, effectuer une promotion contrôlée de l’instance secondaire.

Arrêt de l’instance primaire et vérification de son état sur **p1** :


**Exemple 3-e308.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql@17-main
```


**Exemple 3-e309.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e310.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               shut down
Latest checkpoint's REDO location:    0/71A9F38
Latest checkpoint's REDO WAL file:    000000010000000000000007
```


Vérification de l’instance secondaire sur **p2** :


**Exemple 3-e311.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'CHECKPOINT'
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e312.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               in archive recovery
Latest checkpoint's REDO location:    0/71A9F38
Latest checkpoint's REDO WAL file:    000000010000000000000007
```


L’instance principale est bien arrêtée, l’instance secondaire est bien en `archive recovery` et les deux sont bien synchronisées.

Promotion de l’instance secondaire sur **p2** :


**Exemple 3-e313.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'SELECT pg_promote()'
```


**Exemple 3-e314.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 pg_promote
------------
 t
(1 row)
```


> - Tenter de se connecter au serveur secondaire fraîchement promu.
> - Les écritures y sont-elles possibles ?

Connectons-nous à ce nouveau primaire et tentons d’y insérer des données :


**Exemple 3-e315.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e316.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e317.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# CREATE TABLE t2(id integer);
```


**Exemple 3-e318.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CREATE TABLE
```


**Exemple 3-e319.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# INSERT INTO t2 SELECT generate_series(1, 1000000);
```


**Exemple 3-e320.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INSERT 0 1000000
```


Les écritures sont désormais possibles sur cette instance.

##### Retour à la normale

> - Reconstruire l’instance initiale sur **p1** comme nouvelle instance secondaire en repartant d’une copie complète de **p2** en utilisant `pg_basebackup`.

Afin de rétablir la situation, nous pouvons réintégrer l’ancienne instance primaire en tant que nouveau secondaire (sur **p1**). Pour ce faire, nous devons re-synchroniser les données. Utilisons `pg_basebackup` comme précédemment après avoir mis de côté les fichiers de l’ancien primaire :


**Exemple 3-e321.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
mv /var/lib/postgresql/17/main /var/lib/postgresql/17/main.old
```


**Exemple 3-e322.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pg_basebackup --pgdata /var/lib/postgresql/17/main \
              --progress \
              --write-recovery-conf \
              --checkpoint fast \
              --host 10.0.0.22 \
              --username repli
```


**Exemple 3-e323.** Extrait de code ou sortie de commande du chapitre 3\.

```text
101537/101537 kB (100%), 1/1 tablespace
```


Vérifier la présence du fichier `standby.signal`.


**Exemple 3-e324.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file /var/lib/postgresql/17/main/standby.signal
```


**Exemple 3-e325.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/postgresql/17/main/standby.signal: empty
```


Contrôler `postgresql.auto.conf` (qui contient potentiellement deux lignes `primary_conninfo` !).


**Exemple 3-e326.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat /var/lib/postgresql/17/main/postgresql.auto.conf
```


**Exemple 3-e327.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# paramètre remis en forme pour le manuel
primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass''
                    channel_binding=prefer host=10.0.0.22 port=5432
                    sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1
                    ssl_min_protocol_version=TLSv1.2
                    gssencmode=prefer krbsrvname=postgres gssdelegation=0
                    target_session_attrs=any load_balance_hosts=disable'
```


Les fichiers de configuration de l’instance n’ayant quant à eux pas été modifiés, il n’est pas nécessaire de remodifier la configuration.

Supprimer les traces :


**Exemple 3-e328.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
rm /var/log/postgresql/postgresql-17-main.log
```


> - Démarrer cette nouvelle instance.

Démarrer le service :


**Exemple 3-e329.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main     # Démarrage
sudo systemctl status postgresql@17-main    # Contrôle
```


> - Vérifier que les processus adéquats sont bien présents, et que les données précédemment insérées dans les tables créées plus haut sont bien présentes dans l’instance reconstruite.

Les processus adéquats sont bien présents :


**Exemple 3-e330.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** (secondaire) :


**Exemple 3-e331.** Extrait de code ou sortie de commande du chapitre 3\.

```text
27488  \_ postgres: 17/main: startup recovering 00000002000000000000000C
27489  \_ postgres: 17/main: walreceiver streaming 0/C000060
```


Sur **p2** (primaire) :


**Exemple 3-e332.** Extrait de code ou sortie de commande du chapitre 3\.

```text
19319  \_ postgres: 17/main: walsender repli 10.0.0.21(45332) streaming 0/C000060
```


En nous connectant à la nouvelle instance secondaire (**p1**), vérifions que les données précédemment insérées dans la table `t2` sont bien présentes :


**Exemple 3-e333.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql b1
```


**Exemple 3-e334.** Extrait de code ou sortie de commande du chapitre 3\.

```text
psql (17.1)
Type "help" for help.
```


**Exemple 3-e335.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
b1=# SELECT COUNT(*) FROM t2;
```


**Exemple 3-e336.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  count
---------
 1000000
```


> - Inverser à nouveau les rôles des deux instances afin que **p2** redevienne l’instance secondaire, cette fois-ci effectuer la remise en service de l’ancienne instance primaire sans reconstruction.

Afin que l’instance **p1** redevienne primaire et celle sur **p2** secondaire, on peut ré-appliquer la procédure de promotion vue précédemment dans l’autre sens.

Arrêt de l’instance primaire (**p2**) et vérification de son état :


**Exemple 3-e337.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql@17-main
```


**Exemple 3-e338.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e339.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               shut down
Latest checkpoint's REDO location:    0/C000148
Latest checkpoint's REDO WAL file:    00000002000000000000000C
```


Vérification de l’instance secondaire sur **p1** :


**Exemple 3-e340.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'CHECKPOINT;'
/usr/lib/postgresql/17/bin/pg_controldata -D /var/lib/postgresql/17/main/ \
  | grep -E '(cluster)|(REDO)'
```


**Exemple 3-e341.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Database cluster state:               in archive recovery
Latest checkpoint's REDO location:    0/C000148
Latest checkpoint's REDO WAL file:    00000002000000000000000C
```


L’instance principale est bien arrêtée, l’instance secondaire est bien en `archive recovery` et les deux sont bien synchronisées.

Promotion de l’instance secondaire :


**Exemple 3-e342.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c 'SELECT pg_promote()'
```


**Exemple 3-e343.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 pg_promote
------------
 t
(1 row)
```


Afin que **p2** redevienne l’instance secondaire, créer le fichier `standby.signal`, démarrer le service et vérifier que les processus adéquats sont bien présents :


**Exemple 3-e344.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
touch /var/lib/postgresql/17/main/standby.signal
```


**Exemple 3-e345.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


**Exemple 3-e346.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -o pid,cmd fx | egrep "(startup|walsender|walreceiver)"
```


Sur **p1** :


**Exemple 3-e347.** Extrait de code ou sortie de commande du chapitre 3\.

```text
27562  \_ postgres: 17/main: walsender repli 10.0.0.22(34562) streaming 0/C0002D8
```


Sur **p2** :


**Exemple 3-e348.** Extrait de code ou sortie de commande du chapitre 3\.

```text
19371  \_ postgres: 17/main: startup recovering 00000003000000000000000C
19373  \_ postgres: 17/main: walreceiver streaming 0/C0002D8
```


##### Optionnel: Types de réplication synchrone

> - Configurer la réplication synchrone de **p1** vers **p2**.

Sur l’instance primaire éditer le paramétrage de l’instance :


**Exemple 3-e349.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e350.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'p2'
```


Sur l’instance secondaire vérifier que l’`application_name` fourni dans le paramètre `synchronous_standby_names` est bien `p2`.


**Exemple 3-e351.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^primary_conninfo" /var/lib/postgresql/17/main/postgresql.auto.conf
```


**Exemple 3-e352.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p2'
```


Recharger la configuration sur les deux serveurs :


**Exemple 3-e353.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Lorsque l’on liste les connexions de réplication depuis **p1**, on voit la connexion synchrone :


**Exemple 3-e354.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication;
```


**Exemple 3-e355.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35258 | p2               |             1 | sync
(1 row)
```


> - Stopper l’instance secondaire **p2**, créer une table `t1`, que se passe-t-il ?

Arrêter l’instance sur **p2** :


**Exemple 3-e356.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql@17-main
```


Créer une table `t1` sur **p1** :


**Exemple 3-e357.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
CREATE TABLE t1();
```


La transaction implicite qui englobe la création de `t1` est en attente. La vue `pg_stat_activity` nous apprend que l’instance est attente de la réplication synchrone.


**Exemple 3-e358.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, usename, datname, wait_event_type, wait_event, state, query
  FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t1%' \gx
```


**Exemple 3-e359.** Extrait de code ou sortie de commande du chapitre 3\.

```text
-[ RECORD 1 ]---+-------------------
pid             | 35409
usename         | postgres
datname         | postgres
wait_event_type | IPC
wait_event      | SyncRep
state           | active
query           | CREATE TABLE t1();
```


À ce moment, la table n’est pas visible sur **p1**.


**Exemple 3-e360.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
\dt t1
```


**Exemple 3-e361.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Did not find any relation named "t1".
```


Lorsque l’on redémarre le service PostgreSQL sur **p2**, la session en attente se termine et la table est visible.


**Exemple 3-e362.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


> - Stopper l’instance **p2**, créer une table **t2**. Annuler la requête bloquée sur **p1** avec la commande `pg_cancel_backend()`, qu’observez-vous ?
> - Redémarrer **p2**.

Si l’on reproduit les mêmes étapes en créant une table `t2` et que l’on annule la session avec `pg_cancel_backend()`, la réplication synchrone est débloquée et la table est visible :


**Exemple 3-e363.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, usename, datname, wait_event_type, wait_event, state, query,
       pg_cancel_backend(pid) as cancelled
  FROM pg_stat_activity WHERE query LIKE 'CREATE TABLE t2%' \gx
\dt t2
```


**Exemple 3-e364.** Extrait de code ou sortie de commande du chapitre 3\.

```text
-[ RECORD 1 ]---+-------------------
pid             | 35409
usename         | postgres
datname         | postgres
wait_event_type | IPC
wait_event      | SyncRep
state           | active
query           | CREATE TABLE t2();
cancelled       | t

        List of relations
 Schema | Name | Type  |  Owner
--------+------+-------+----------
 public | t2   | table | postgres
(1 row)
```


Sur **p1**, on observe :


**Exemple 3-e365.** Extrait de code ou sortie de commande du chapitre 3\.

```text
WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.
CREATE TABLE
```


À ce stade, si un incident provoquant la perte du serveur et de son stockage survient sur **p1** et que les WAL ne sont pas archivées, **p2** n’aura jamais reçus les modifications et elles seront perdues. Si l’on redémarre **p2**, elle rattrape son retard et la table `t2` y est visible.

> - Suivre la méthode utilisée dans les TP précédant pour mettre en place une seconde instance standby sur p3.

Petit rappel des opérations :

- sauvegarder la configuration (ici c’est inutile) et traces ;
- nettoyer le répertoire de données existant et ses traces ;
- effectuer le `pg_basebackup` ;
- vérifier la configuration ;
  - fichier flag (`standby.signal`) ;
  - accès extérieur (`pg_hba.conf`) ;
  - configuration générale (`postgresql.conf`) ;
  - configuration de la connexion (`postgresql.auto.conf`) ;
  - mot de passe (`.pgpass`) ;
- démarrer l’instance.

> - Configurer la réplication synchrone de **p1** vers **p2** et **p3** en utilisant la syntaxe `p2, p3`. Observer les champs `sync_priority` et `sync_state` de `pg_stat_replication`.

La configuration a été mise à jour et prise en compte :


**Exemple 3-e366.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e367.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'p2, p3'
```


Note: deux syntaxes équivalentes existent : `FIRST 1 (p2, p3)` ou `FIRST (p2, p3)`. Elles ne seront pas utilisées dans ce TP, parce que Patroni ne les utilise pas.

La vue `pg_stat_replication` contient désormais les informations suivantes :


**Exemple 3-e368.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication
ORDER BY application_name;
```


**Exemple 3-e369.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35517 | p2               |             1 | sync
 35260 | p3               |             2 | potential
(2 rows)
```


On voit que l’instance **p3** est marquée comme *potential*.

> - Stopper **p2** puis **p3**, redémarrer **p3** puis **p2**. À chaque étape regarder l’état de la vue `pg_stat_replication`.

Voici le contenu de `pg_stat_replication` en fonction de l’état des instances :

- **p2** arrêtée : On note que `sync priority` est toujours à 2 sur **p3**, en effet cette valeur dépend de la position de l’instance dans le paramètre `synchronous_standby_name`.


**Exemple 3-e370.** Extrait de code ou sortie de commande du chapitre 3\.

```text
    pid  | application_name | sync_priority | sync_state
  -------+------------------+---------------+------------
   35260 | p3               |             2 | sync
  (1 row)
  ```


- **p3** stoppée : la vue est vide.

- **p3** redémarrée : La vue est revenue dans son état précédent, seul le `pid` à changer.


**Exemple 3-e371.** Extrait de code ou sortie de commande du chapitre 3\.

```text
    pid  | application_name | sync_priority | sync_state
  -------+------------------+---------------+------------
   35533 | p3               |             2 | sync
  (1 row)
  ```


- **p2** redémarrée : La vue est revenue dans son état initial. **p2** est synchrone.


**Exemple 3-e372.** Extrait de code ou sortie de commande du chapitre 3\.

```text
     pid  | application_name | sync_priority | sync_state
   -------+------------------+---------------+------------
    35534 | p2               |             1 | sync
    35533 | p3               |             2 | potential
   (2 rows)
  ```


D’un point de vue infrastructure, on observe que tant que **p2** est disponible, c’est elle qui sera synchrone. Cela correspond bien à la syntaxe équivalente `FRIST 1 (p2, p3)` qui indique bien que **p2** à la plus haute priorité.

D’un point de vue expérience utilisateur, tant qu’il y a au moins une instance synchrone disponible, les transactions ne sont pas bloquées en attente de commit.

> - Modifier la configuration de **p1** pour demander deux standbys synchrones.

La configuration suivante a été mise à jour et prise en compte :


**Exemple 3-e373.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e374.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = '2 (p2, p3)'
```


Note: une syntaxe équivalente est `FIRST 2 (p2, p3)`.

La vue `pg_stat_replication` contient désormais les informations suivantes :


**Exemple 3-e375.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication
ORDER BY application_name;
```


**Exemple 3-e376.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35534 | p2               |             1 | sync
 35533 | p3               |             2 | sync
(2 rows)
```


On observe que les deux standbys sont synchrones et que la priorité n’a pas changé. Si l’un des serveurs s’arrête toute transaction validée sur **p1** restera en attente car la configuration exige la présence de deux instances.

> - Configurer `synchronous_standby_names` à `*`. Répéter les mêmes tests que précédemment. Qu’observez-vous ?

On observe que le réplica synchrone est **p3** et que la priorité des deux instances est 1.


**Exemple 3-e377.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pid, application_name, sync_priority, sync_state
  FROM pg_stat_replication ORDER BY application_name;
```


**Exemple 3-e378.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35534 | p2               |             1 | potential
 35533 | p3               |             1 | sync
(2 rows)
```


Si l’on arrête **p2** et **p3** et que l’on redémarre **p2** avant **p3**, on obtient :


**Exemple 3-e379.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35571 | p2               |             1 | sync
 35572 | p3               |             1 | potential
(2 rows)
```


> - Mettre en place la réplication synchrone par *quorum* avec un serveur synchrone minimum.

La configuration a été mise à jour et prise en compte par un `pg_reload_conf()` :


**Exemple 3-e380.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e381.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = 'ANY 1 (p2, p3)'
```


Dans `pg_stat_replication`, on observe que le statut des deux instances est `quorum` et que leur priorité est identique :


**Exemple 3-e382.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 35571 | p2               |             1 | quorum
 35572 | p3               |             1 | quorum
```


Dans cet état, après un commit, la réplication synchrone rend la main dès que les données sont répliquées sur n’importe lequel des deux serveurs.

> - Désactiver la réplication synchrone.

Positionner le paramètre `synchronous_standby_names` à **chaîne vide** :


**Exemple 3-e383.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^synchronous_standby_names" /etc/postgresql/17/main/postgresql.conf
```


**Exemple 3-e384.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
synchronous_standby_names = ''
```


Recharger la configuration.


**Exemple 3-e385.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Contrôler le contenu de la vue `pg_stat_replication` :


**Exemple 3-e386.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  pid  | application_name | sync_priority | sync_state
-------+------------------+---------------+------------
 17411 | p2               |             0 | async
 17412 | p3               |             0 | async
(2 rows)
```


##### Optionnel: Log shipping et initialisation d’instance avec pgBackRest

> - Installer pgBackRest sur tous les serveurs (**p1**, **p2**, **p3**, **b1**).

Il est préférable d’installer les paquets provenant de pgdg. Il faut donc configurer le dépôt associé sur le serveur. Dans notre cas, c’est déjà fait.

L’installation se fait ensuite simplement.


**Exemple 3-e387.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo apt-get install -y pgbackrest
```


Il faut ensuite échanger les clés SSH entre les serveurs ou mettre en place le serveur TLS de pgBackRest. Pour ce TP, nous utiliserons ssh. Les clés ont déjà été déjà échangées.

> - Configurer pgBackRest sur le serveur de sauvegarde.

Le dépôt de sauvegarde sera créé par l’utilisateur **postgres**.


**Exemple 3-e388.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo install -o postgres -g postgres -m 0755 -d /var/lib/pgbackrest
sudo chown postgres:postgres /etc/pgbackrest.conf

cat  << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-level-console=info
log-level-file=detail

[main]
pg1-host=p1
pg1-path=/var/lib/postgresql/17/main
pg2-host=p2
pg2-path=/var/lib/postgresql/17/main
pg3-host=p3
pg3-path=/var/lib/postgresql/17/main
_EOF_
```


Note: toutes les instances doivent exister et être accessible pour que la commande `stanza-create` fonctionne.

> - Configurer pgBackRest sur les serveurs de base de données.

Sur chaque instance :


**Exemple 3-e389.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo install -o postgres -g postgres -m 0755 -d /var/log/pgbackrest
sudo chown postgres /etc/pgbackrest.conf

cat  << _EOF_ > /etc/pgbackrest.conf
[global]
repo1-host=b1
repo1-host-user=postgres
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
log-path=/var/log/pgbackrest
log-level-console=info
log-level-file=detail

[main]
pg1-path=/var/lib/postgresql/17/main
_EOF_
```


Tester la connexion entre **b1** et **p1**, **p2**, **p3** et inversement.

Depuis *p1*, *p2*, *p3* :


**Exemple 3-e390.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ssh -o "StrictHostKeyChecking=no" b1 hostname
```


Depuis *b1* :


**Exemple 3-e391.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
for i in $(seq 1 3); do ssh -o "StrictHostKeyChecking=no" p$i hostname; done
```


La configuration de PostgreSQL doit aussi être mise à jour :


**Exemple 3-e392.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat  << _EOF_ >> /etc/postgresql/17/main/postgresql.conf
archive_mode = 'on'
archive_command = 'pgbackrest --stanza=main archive-push %p'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
_EOF_
```


L’activation de l’archivage nécessitant encore un redémarrage en version 17 :


**Exemple 3-e393.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql@17-main
```


> - Créer la stanza, tester l’archivage.


**Exemple 3-e394.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main stanza-create
```


**Exemple 3-e395.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] 2025-01-14 17:47:18.459 P00   INFO: stanza-create command begin 2.54.1: --exec-id=129738-030db425 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --stanza=main
[…] 2025-01-14 17:47:19.785 P00   INFO: stanza-create for stanza 'main' on repo1
[…] 2025-01-14 17:47:20.154 P00   INFO: stanza-create command end: completed successfully (1697ms)
```


**Exemple 3-e396.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main check
```


**Exemple 3-e397.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: check command begin 2.54.1: --exec-id=129755-e8809f38 --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --stanza=main
[…] INFO: check repo1 (standby)
[…] INFO: switch wal not performed because this is a standby
[…] INFO: check repo1 configuration (primary)
[…] INFO: check repo1 archive for WAL (primary)
[…] INFO: WAL segment 000000030000000000000012 successfully archived to '/var/lib/pgbackrest/archive/main/17-1/0000000300000000/000000030000000000084b5808df7b2f49aa.gz' on repo1
[…] INFO: check command end: completed successfully (2793ms)
```


> - Lancer une sauvegarde.


**Exemple 3-e398.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main backup --type=full --start-fast
```


**Exemple 3-e399.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: backup command begin 2.54.1: --exec-id=129861-e77d17ba --log-level-console=info --log-level-file=detail --pg1-host=p1 --pg2-host=p2 --pg3-host=p3 --pg1-path=/var/lib/postgresql/17/main --pg2-path=/var/lib/postgresql/17/main --pg3-path=/var/lib/postgresql/17/main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main --start-fast --type=full
[…] INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
[…] INFO: backup start archive = 000000030000000000000014, lsn = 0/14000028
[…] INFO: check archive for prior segment 000000030000000000000013
[…] INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
[…] INFO: backup stop archive = 000000030000000000000014, lsn = 0/14000120
[…] INFO: check archive for segment(s) 000000030000000000000014:000000030000000000000014
[…] INFO: new backup label = 20250114-174941F
[…] INFO: full backup size = 22MB, file total = 966
[…] INFO: backup command end: completed successfully (16698ms)
[…] INFO: expire command begin 2.54.1: --exec-id=129861-e77d17ba --log-level-console=info --log-level-file=detail --repo1-path=/var/lib/pgbackrest --repo1-retention-full=2 --stanza=main
 INFO: expire command end: completed successfully (202ms)
```


**Exemple 3-e400.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main info
```


**Exemple 3-e401.** Extrait de code ou sortie de commande du chapitre 3\.

```text
stanza: main
    status: ok
    cipher: none

    db (current)
        wal archive min/max (17): 000000010000000000000002/000000030000000000000014

        full backup: 20250114-174941F
            timestamp start/stop: 2025-01-14 17:49:41+01 / 2025-01-14 17:49:55+01
            wal start/stop: 000000030000000000000014 / 000000030000000000000014
            database size: 22MB, database backup size: 22MB
            repo1: backup set size: 2.9MB, backup size: 2.9MB
```


> - Si **p3** existe, le stopper et détruire le répertoire de données.


**Exemple 3-e402.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql@17-main
rm -Rf /var/lib/postgresql/17/main
rm -Rf /var/log/postgresql/postgresql-17-main.log
```


> - Créer l’instance à partir de la sauvegarde et s’assure qu’elle est bien en réplication.


**Exemple 3-e403.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
pgbackrest --stanza=main restore \
           --delta --type=standby \
           --recovery-option=primary_conninfo="user=repli host=10.0.0.21 port=5432 application_name=p3"
```


**Exemple 3-e404.** Extrait de code ou sortie de commande du chapitre 3\.

```text
[…] INFO: restore command begin 2.54.1: --delta --exec-id=88667-6cd35e15 --log-level-console=info --log-level-file=detail --pg1-path=/var/lib/postgresql/17/main --recovery-option="primary_conninfo=user=repli host=10.0.0.21 port=5432 application_name=p3" --repo1-host=10.0.0.1 --repo1-host-user=dalibo --repo1-path=/var/lib/pgbackrest --stanza=main --type=standby
[…] WARN: --delta or --force specified but unable to find 'PG_VERSION' or 'backup.manifest' in '/var/lib/postgresql/17/main' to confirm that this is a valid $PGDATA directory. --delta and --force have been disabled and if any files exist in the destination directories the restore will be aborted.
[…] INFO: repo1: restore backup set 20250114-174941F, recovery will start at 2025-01-14 16:49:41
[…] INFO: write updated /var/lib/postgresql/17/main/postgresql.auto.conf
[…] INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
[…] INFO: restore size = 22MB, file total = 966
[…] INFO: restore command end: completed successfully (20187ms)
```


Dans le cas présent, on note que pgBackRest a détecté qu’il n’y avait pas de données dans le répertoire de données de l’instance. Il a donc désactivé l’option `--delta` de lui-même. Si un répertoire de données existe seul les fichiers qui ont changé sont restaurés.

Le fichier `standby.signal` a bien été créé :


**Exemple 3-e405.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
file ~/17/main/standby.signal
```


**Exemple 3-e406.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/var/lib/postgresql/17/main/standby.signal: empty
```


La configuration a bien été mise à jour :


**Exemple 3-e407.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
cat ~/17/main/postgresql.auto.conf
```


**Exemple 3-e408.** Extrait de code ou sortie de commande du chapitre 3\.

```text
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'

# Recovery settings generated by pgBackRest restore on 2025-01-14 17:05:40
primary_conninfo = 'user=repli host=10.0.0.21 port=5432 application_name=p3'
restore_command = 'pgbackrest --stanza=main archive-get %f "%p"'
```


Démarrer l’instance :


**Exemple 3-e409.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


L’instance est bien démarrée et la réplication fonctionne :


**Exemple 3-e410.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd
```


**Exemple 3-e411.** Extrait de code ou sortie de commande du chapitre 3\.

```text
CMD
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 000000030000000000000015
postgres: 17/main p3: walreceiver streaming 0/15000168
```


> - Interdire la connexion à **p1** depuis **p3** dans le `pg_hba.conf`, générer de l’activité, que se passe-t’il sur **p3** ?

Ajouter une ligne `reject` au `pg_hba.conf` avant les lignes existantes (ici ligne 118) sur **p1** :


**Exemple 3-e412.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -nE "10\.0\.0\.23" /etc/postgresql/17/main/pg_hba.conf
```


**Exemple 3-e413.** Extrait de code ou sortie de commande du chapitre 3\.

```text
118:host    replication     repli           10.0.0.23/32            reject
135:host    replication     repli           10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p1**.


**Exemple 3-e414.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


Redémarrer **p3**, cela permet de ré-initialiser la connexion de réplication.


**Exemple 3-e415.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl restart postgresql@17-main
```


On voit que la réplication a décroché.


**Exemple 3-e416.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd | grep "17/main"
```


**Exemple 3-e417.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 000000030000000000000015
```


Générer de l’activité sur **p1** :


**Exemple 3-e418.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
createdb pgbench
pgbench -i -s10 pgbench
```


On observe que l’instance a continué de consommer les WAL en provenance de **p1** via le dépôt de sauvegarde de pgBackRest.


**Exemple 3-e419.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd | grep "17/main"
```


**Exemple 3-e420.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup waiting for 00000003000000000000001A
```


> - Faire marche arrière sur la modification, que se passe-t-il sur **p3** ?

Retire la ligne suivante au `pg_hba.conf` avant les lignes existantes :


**Exemple 3-e421.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -nE "10\.0\.0\.23" /etc/postgresql/17/main/pg_hba.conf
```


**Exemple 3-e422.** Extrait de code ou sortie de commande du chapitre 3\.

```text
134:host    replication     repli           10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p1**.


**Exemple 3-e423.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
SELECT pg_reload_conf();
```


La connexion de réplication est de nouveau en place.


**Exemple 3-e424.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd | grep "17/main"
```


**Exemple 3-e425.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p3: checkpointer
postgres: 17/main p3: background writer
postgres: 17/main p3: startup recovering 00000003000000000000001B
postgres: 17/main p3: walreceiver streaming 0/1B002430
```


Dans les traces, on voit que le log shipping a été abandonné au profil de la réplication :


**Exemple 3-e426.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -A20 -B1 -E "started streaming WAL" /var/log/postgresql/postgresql-17-main.log
```


**Exemple 3-e427.** Extrait de code ou sortie de commande du chapitre 3\.

```text
INFO: archive-get command end: completed successfully (436ms)
LOG:  started streaming WAL from primary at 0/1A000000 on timeline 3
```


##### Optionnel: utilisation de pg_rewind

> - Promouvoir l’instance **p2**. Générer de l’activité sur **p1** (*split brain*).

Promouvoir l’instance **p2** :


**Exemple 3-e428.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "SELECT pg_promote();"
```


Sur l’instance **p1**, exécuter `pgbench` pour simuler que des écritures ont continué à arriver sur **p1** après la promotion de **p2** :


**Exemple 3-e429.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
dropdb pgbench
createdb pgbench
pgbench -i -s10 pgbench
```


> - Stopper **p1** et configurer la réplication pour raccrocher **p1** à **p2**.

Stopper **p1** :


**Exemple 3-e430.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl stop postgresql@17-main
```


Créer un fichier `standby.signal` :


**Exemple 3-e431.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
touch /var/lib/postgresql/17/main/standby.signal
```


Vérifier que le paramètre `primary_conninfo` est correctement configuré :


**Exemple 3-e432.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep -E "^primary_conninfo" /var/lib/postgresql/17/main/postgresql.auto.conf
```


**Exemple 3-e433.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
```


Démarrer l’instance :


**Exemple 3-e434.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


> Qu’observez-vous ?

L’instance refuse de démarrer.

Les traces indiquent que les deux instances ont divergé au lsn `0/21BE14E8` qui se trouve après le dernier checkpoint de la primaire : `0/23000028` sur la timeline 3.


**Exemple 3-e435.** Extrait de code ou sortie de commande du chapitre 3\.

```text
LOG:  database system was shut down at 2025-01-17 15:12:50 UTC
LOG:  restored log file "00000004.history" from archive
FATAL:  the database system is starting up
LOG:  restored log file "00000004.history" from archive
LOG:  entering standby mode
FATAL:  requested timeline 4 is not a child of this server's history
DETAIL:  Latest checkpoint is at 0/23000028 on timeline 3, but in the history of the requested timeline, the server forked off from that timeline at 0/21BE14E8.
LOG:  startup process (PID 8971) exited with exit code 1
LOG:  aborting startup due to startup process failure
LOG:  database system is shut down
```


À ce stade, il faut reconstruire le secondaire **p1** complètement ou partiellement.

> - Utiliser `pg_rewind` pour remettre l’instance **p1** d’aplomb.

Les instances que nous avons créées utilisent les sommes de contrôle (*checksums*), ce qui inclut des *hint* nécessaires à l’utilisation de `pg_rewind` :


**Exemple 3-e436.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/lib/postgresql/17/bin/pg_controldata /var/lib/postgresql/17/main \
  | grep "Data page checksum version"
```


**Exemple 3-e437.** Extrait de code ou sortie de commande du chapitre 3\.

```text
Data page checksum version:           1
```


Un autre pré-requis est que l’instance a été arrêtée proprement, chose que nous avons faite précédemment.

Créer un utilisateur dédié à pg_rewind sur **p2** :


**Exemple 3-e438.** Extrait de code ou sortie de commande du chapitre 3\.

```sql
CREATE USER rewind_user LOGIN;
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text, boolean, boolean)
  TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_stat_file(text, boolean) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text) TO rewind_user;
GRANT EXECUTE ON function pg_catalog.pg_read_binary_file(text, bigint, bigint,
  boolean) TO rewind_user;

\password rewind_user
```


**Note :**


Ces droits sont appliqués uniquement dans la base de données courante. `pg_rewind` devra donc se connecter à cette même base pour disposer des autorisations nécessaires.


Ajouter le mot de passe du nouvel utilisateur aux fichiers `.pgpass` de tous les serveurs :


**Exemple 3-e439.** Extrait de code ou sortie de commande du chapitre 3\.

```text
echo "*:*:*:rewind_user:rewind_password" >> ~/.pgpass
```


Mettre a jour le `pg_hba` des serveurs (ici pour le serveur **p2**) :


**Exemple 3-e440.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
grep rewind_user /etc/postgresql/17/main/pg_hba.conf
```


**Exemple 3-e441.** Extrait de code ou sortie de commande du chapitre 3\.

```text
host    postgres        rewind_user     10.0.0.21/32            scram-sha-256
host    postgres        rewind_user     10.0.0.23/32            scram-sha-256
```


Recharger la configuration sur **p2** :


**Exemple 3-e442.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
psql -c "SELECT pg_reload_conf();"
```


**Exemple 3-e443.** Extrait de code ou sortie de commande du chapitre 3\.

```text
 pg_reload_conf
----------------
 t
(1 row)
```


Nous pouvons désormais lancer `pg_rewind` (ici en mode à blanc) :


**Exemple 3-e444.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
/usr/lib/postgresql/17/bin/pg_rewind \
  --source-server="host=10.0.0.22 port=5432 user=rewind_user dbname=postgres" \
  --target-pgdata=/var/lib/postgresql/17/main \
  --progress \
  --write-recovery-conf \
  --restore-target-wal \
  --config-file=/etc/postgresql/17/main/postgresql.conf \
  --dry-run
```


Les traces nous montrent les deux serveurs ont divergé au lsn `0/21BE14E8`.


**Exemple 3-e445.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: connected to server
pg_rewind: servers diverged at WAL location 0/21BE14E8 on timeline 3
```


`pg_rewind` lit les WAL présents sur **p1** et liste tous les blocs modifiés entre le checkpoint précédent, le point de divergence et le moment où l’instance a été arrêtée. Cette liste permettra de ne récupérer que ce qui n’a pas bougé sur la nouvelle instance. Certains fichiers, qui ne sont pas protégés par les WAL, devront être copiés entièrement. C’est notamment le cas de la configuration présente dans le répertoire de données de l’instance.


**Exemple 3-e446.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: rewinding from last common checkpoint at 0/1B0027D8 on timeline 3
pg_rewind: reading source file list
pg_rewind: reading target file list
pg_rewind: reading WAL in target
```


Une fois la liste de ce qui doit être fait établie : la copie est lancée.


**Exemple 3-e447.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: need to copy 507 MB (total source directory size is 526 MB)
519619/519619 kB (100%) copied
```


Le *control file* est ensuite modifié et un fichier *backup label* est généré.


**Exemple 3-e448.** Extrait de code ou sortie de commande du chapitre 3\.

```text
pg_rewind: creating backup label and updating control file
pg_rewind: syncing target data directory
pg_rewind: Done!
```


Le test a fonctionné nous pouvons le relancer sans l’option `--dry-run`.

L’option `--write-recovery-conf` nous permet de :

- créer le fichier `standby.signal` :


**Exemple 3-e449.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
  file /var/lib/postgresql/17/main/standby.signal
  ```


**Exemple 3-e450.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  /var/lib/postgresql/17/main/standby.signal: empty
  ```


- mettre à jour la configuration de la réplication :


**Exemple 3-e451.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
  cat /var/lib/postgresql/17/main/postgresql.auto.conf
  ```


**Exemple 3-e452.** Extrait de code ou sortie de commande du chapitre 3\.

```text
  # Do not edit this file manually!
  # It will be overwritten by the ALTER SYSTEM command.
  primary_conninfo = 'user=repli passfile=''/var/lib/postgresql/.pgpass'' host=10.0.0.21 port=5432 application_name=p2'
  primary_conninfo = 'user=rewind_user passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer host=10.0.0.22 port=5432 sslmode=prefer sslnegotiation=postgres sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 target_session_attrs=any load_balance_hosts=disable'
  ```


  Cette configuration est malheureusement incorrecte pour notre besoin. La première correspond au contenu du fichier **p2**, la seconde utilise l’utilisateur `rewind_user`. Nous allons donc la remplacer par ce qui nous intéresse :


**Exemple 3-e453.** Extrait de code ou sortie de commande du chapitre 3\.

```ini
  primary_conninfo = 'user=repli host=10.0.0.22 port=5432 application_name=p1'
  ```


L’instance peut ensuite être démarrée :


**Exemple 3-e454.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
sudo systemctl start postgresql@17-main
```


L’instance est bien démarrée en tant que secondaire et réplique :


**Exemple 3-e455.** Extrait de code ou sortie de commande du chapitre 3\.

```bash
ps -u postgres -o cmd | grep "17/main"
```


**Exemple 3-e456.** Extrait de code ou sortie de commande du chapitre 3\.

```text
/usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql/17/main -c config_file=/etc/postgresql/17/main/postgresql.conf
postgres: 17/main p1: checkpointer
postgres: 17/main p1: background writer
postgres: 17/main p1: startup recovering 000000040000000000000021
postgres: 17/main p1: walreceiver streaming 0/21BF0EE8
```


La seconde instance secondaire a elle aussi décroché, à vous de jouer !

Pour poursuivre, consultez le [chapitre 4](/fr/hapat/04-etcd-architecture-fonctionnement/#hapat-ch4).

Pour revenir au chapitre précédent, consultez le [chapitre 2](/fr/hapat/02-patroni-architecture/#hapat-ch2).
