0

PostgreSQL 17 : des sauvegardes incrémentales avec pg_basebackup

twitterlinkedinmail

 

Bonjour

Les 11 et 12 juin derniers, nous étions aux journées PGDAY à Lille pour découvrir les nouveautés autour de PostgreSQL.
Cette conférence regroupe différents professionnels, de la communauté francophone, qui agissent en contribuant sur des sujets techniques mais aussi sur les bonnes pratiques afin d’utiliser PostgreSQL dans les meilleurs conditions.

Un article m’a particulièrement intéressé cette année, c’est celui de Stefan Fercot Senior DBA PostgreSQL qui vit en Belgique, et travaille pour une société allemande experte dans les solutions PostgreSQL. Sa présentation portait sur le sujet “démystifier les sauvegardes incrémentales sous PostgreSQL”.

J’ai écouté sa conférence tout en ayant hâte de tester sa mise en place dès mon retour de Lille.

Je tiens à remercier Stefan pour son travail sur ce sujet sauvegardes PostgreSQL.

 

Tout d’abord, il faut savoir que les sujets sauvegardes incrémentales ont été déjà abordés avec des outils comme Barman ou Pg_BackRest, et que certaines instances PostgreSQL de production sont sauvegardées via ces mécanismes depuis quelques années maintenant.

Ici, nous parlons de la solution “backup incremental” inclu nativement dans le moteur PostgreSQL, et disponible avec l’outil “pg_basebackup“. C’est d’ailleurs ce point que Stefan a souligné durant la journée PGDAY du 11 juin dernier.

Cette nouvelle fonctionnalité fait partie de la version PostgreSQL 17 qui est pour le moment, en version Beta 2.
Celle ci devrait sortir, comme à l’accoutumé, au cour de l’automne prochain.

Preuve que PostgreSQL est en perpétuel évolution, et rejoint la liste des SGBD étant capable, comme peuvent le faire Oracle et SQL Server, de proposer nativement des sauvegardes incrémentales.

 

Installation de PostgreSQL 17

 

Pour tester cette fonctionnalité, nous devons installer la toute dernière version de PostgreSQL , la 17 beta 2. Attention, celle ci n’étant pas disponible dans les dépôts PGDG, nous devons nous charger d’installer cette version via le site postgresql.org

https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-8-x86_64/

Nous disposons d’un serveur Linux fork Red Hat 8 (Rocky Linux). Il nous faut donc télécharger les “rpm” liés à cette version.

Les packages dont nous avons besoin sont les suivants

 

# ls -lrt postgresql1* | awk '{print$9}'
postgresql17-contrib-17-beta2_1PGDG.rhel8.x86_64.rpm
postgresql17-17-beta2_1PGDG.rhel8.x86_64.rpm
postgresql17-libs-17-beta2_1PGDG.rhel8.x86_64.rpm
postgresql17-server-17-beta2_1PGDG.rhel8.x86_64.rpm

 

Nous les installons avec le compte root de notre serveur.

 

[root@ tmp]# rpm -i postgresql17-libs-17-beta2_1PGDG.rhel8.x86_64.rpm
[root@ tmp]# rpm -i postgresql17-17-beta2_1PGDG.rhel8.x86_64.rpm
[root@ tmp]# rpm -i postgresql17-server-17-beta2_1PGDG.rhel8.x86_64.rpm
[root@ tmp]# rpm -i postgresql17-contrib-17-beta2_1PGDG.rhel8.x86_64.rpm

 

Comme nous sommes sur un environnement “Red Hat like”, la création d’une première instance via “initdb” est nécessaire.
Surtout, ne pas oublier d’activer les “data checksums” (option -k), nous verrons pourquoi dans la suite de cet article. La suite est à faire avec le compte postgres.

 

[postgres ~]$ initdb -D /data/postgres/17/pg_data -k
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are enabled.

creating directory /data/postgres/17/pg_data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

pg_ctl -D /data/postgres/17/pg_data -l logfile start

 

Démarrer cette instance pour s’assurer que tout fonctionne

 

[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data -l logfile start
waiting for server to start.... done
server started

 

Notre version enregistrée est bien une Beta 2. Version qui ne doit pas être mise sur un environnement de production comme le rappelle le site de la communauté PostgreSQL.

 

[postgres ~]$ psql
(postgres@[local]:5437) [postgres] > select * from version();
version
------------------------------------------------------------------------------------------------------------
PostgreSQL 17beta2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-22), 64-bit
(1 row)

 

Upgrade de version

 

Comme nous disposions deja d’une version PostgreSQL15 sur ce serveur, nous passons par un upgrade via l’outil “pg_upgrade” toujours disponible dans cette nouvelle version.

Lancer pg_upgrade en mode check

[postgres ~]$ pg_upgrade -b /usr/pgsql-15/bin/ -B /usr/pgsql-17/bin/ -c -d /data/postgres/15/pg_data/ -D /data/postgres/17/pg_data/ -p 5434 -P 5437
.....
.....

*Clusters are compatible*
"/usr/pgsql-17/bin/pg_ctl" -w -D "/data/postgres/17/pg_data" -o "" -m smart stop  "/data/postgres/17/pg_data/pg_upgrade_output.d/20240708T085906.955/log/pg_upgrade_server.log" 

la log est générée dans le $PGDATA de la version 17.

Puis lancer l’exécution de pg_upgrade

[postgres ~]$ pg_upgrade -b /usr/pgsql-15/bin/ -B /usr/pgsql-17/bin/ -d /data/postgres/15/pg_data/ -D /data/postgres/17/pg_data/ -p 5434 -P 5437

 

Effectuer une sauvegarde

 

Prérequis

Avant de pouvoir effectuer une première sauvegarde avec l’outil “pg_basebackup” natif, il est primordial de respecter certains prérequis important.

  • L’instance PostgreSQL doit être créée avec les ‘data checksums’ activés. Si ce n’est pas le cas, utiliser l’outil “pg_checksums” avec l’option “-e“.

 

  • Si vous lancez une sauvegarde full puis une incrémentale immédiatement, vous avez toutes les chances de tomber sur cette erreur
pg_basebackup: error: could not initiate base backup: ERROR: incremental backups cannot be taken unless WAL summarization is enabled

En effet, pour avoir toutes les informations concernant les blocks modifiés, PostgreSQL a besoin de tracer dans les WALs toutes les modifications sur les objets en base.
Pour les DBA Oracle, le “block change tracking” de la version Enterprise Edition vous parlera très certainement….
Il s’agit ici de la même fonctionnalité, c’est à dire, tracer les modifications effectuées dans les blocks de données.
Cette option est le “summarize_wal“.

Pour activer l’option, nous aurons 2 paramètres à modifier, soit via un ALTER SYSTEM directement sous psql, ou bien dans le fichier “postgresql.conf”.

 

[postgres backup]$ vi $PGDATA/postgresql.conf
...

# - WAL Summarization -

#summarize_wal = off # run WAL summarizer process?
#wal_summary_keep_time = '10d' # when to remove old summary files, 0 = never

Le premier paramètre permet d’activer cette option.
Le second définit un temps de conservation des informations concernant les blocks modifiés entre une sauvegarde FULL et un incrémentale.

Nous activons donc l’option “summarize_wal” et la passons à ON et laissons à 10 jours le “wal_summary_keep_time“.

Attention, activez ces deux paramètres avant votre première sauvegarde FULL. Si vous le faites après, vous risquez de rencontrer l’erreur suivante

pg_basebackup: error: could not initiate base backup: ERROR: WAL summaries are required on timeline 1 from 1/AA000028 to 1/AC000060, but the summaries for that timeline and LSN range are incomplete
DETAIL: The first unsummarized LSN in this range is 1/AA000028.

Le LSN pris lors de la première sauvegarde FULL n’est pas reconnu, et donc la sauvegarde incrémentale ne peut s’appuyer dessus.

 

Redémarrer l’instance une fois les modifications effectuées

[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data/ restart

 

Lancer une sauvegarde FULL

 

Voici la nouvelle option présente pour l’outil “pg_basebackup

 

[postgres -]$ pg_basebackup --help
pg_basebackup takes a base backup of a running PostgreSQL server.

Usage:
pg_basebackup [OPTION]...

Options controlling the output:
-D, --pgdata=DIRECTORY receive base backup into directory
-F, --format=p|t output format (plain (default), tar)
-i, --incremental=OLDMANIFEST
take incremental backup
-r, --max-rate=RATE maximum transfer rate to transfer data directory
(in kB/s, or use suffix "k" or "M")

.... 

 

Depuis la version 13 de PostgreSQL, nous disposons pour chaque sauvegarde, d’un fichier nommé “backup_manifest”. Il s’agit d’un fichier json qui recense entièrement les objets bases de données sauvegardés avec leur emplacement, leur taille, leur date de modification et leur “checksum”.

Celui ci est essentiel pour vérifier l’intégrité de notre sauvegarde avec “pg_verifybackup“.

Nous pouvons à présent faire une première sauvegarde FULL de notre instance PG17.

 

[postgres -]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17 -F p -l "Full Backup PG17" -P -v
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 1/AD000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_8048"
3097788/3097788 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 1/AD000158
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: syncing data to disk ...
pg_basebackup: renaming backup_manifest.tmp to backup_manifest
pg_basebackup: base backup completed

 

Puis on effectue quelques transactions : création d’une table et insertions de données sur cette table de test

(postgres@[local]:5437) [manu] $ > create table backup (nom varchar(20), type varchar(20), date_backup date);
CREATE TABLE
Time: 3.344 ms

(postgres@[local]:5437) [manu] $ > insert into backup values ('sauvegarde','FULL','2024-07-08 12:00:00');
INSERT 0 1
Time: 3.612 ms
(postgres@[local]:5437) [manu] $ > insert into backup values ('sauvegarde','incremental','2024-07-08 13:00:00');
INSERT 0 1
Time: 1.461 ms

(postgres@[local]:5437) [manu] $ > select * from backup;
nom | type | date_backup
------------+-------------+-------------
sauvegarde | FULL | 2024-07-08
sauvegarde | incremental | 2024-07-08
(2 rows)

 

Repérer le fichier “backup_manifest” de la sauvegarde FULL réalisée dans le dossier “/data/postgres/backup/pg_basebackup/PG17

[postgres PG17]$ ls -lrt backup*
-rw-------. 1 postgres postgres 218 Jul 8 09:19 backup_label
-rw-------. 1 postgres postgres 433295 Jul 8 09:20 backup_manifest

 

Effectuer une sauvegarde incrémentale

 

A partir de là, lancer une sauvegarde incrémentale. Nous utilisons l’option “-i” pour indiquer à pg_basebackup ou est situé le “backup_manifest” de la dernière sauvegarde FULL.

 

[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr -l "Incremental Backup PG17" -P -v -i /data/postgres/backup/pg_basebackup/PG17/backup_manifest
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 1/AF000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_8139"
12485/3097787 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 1/AF000120
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: syncing data to disk ...
pg_basebackup: renaming backup_manifest.tmp to backup_manifest
pg_basebackup: base backup completed

 

S’il l’on compare les deux répertoires de sauvegardes “/data/postgres/backup/pg_basebackup/PG17” et “/data/postgres/backup/pg_basebackup/PG17_incr“, nous voyons que les tailles sont bien différentes

[postgres - ]$ du -h /data/postgres/backup/pg_basebackup/PG17
......
3.0G /data/postgres/backup/pg_basebackup/PG17

[postgres - ]$ du -h /data/postgres/backup/pg_basebackup/PG17_incr
......
35M /data/postgres/backup/pg_basebackup/PG17_incr

 

Un volume de 3Go pour la sauvegarde FULL de l’instance contre 35Mo pour l’incrémentale.
La taille occupée par les objets dans chacune des bases est bien plus faible dans la sauvegarde incrémentale.

Nous continuons à insérer des données :

 [postgres - ]$ psql -d manu

(postgres@[local]:5437) [manu] $ > select * from backup;
nom | type | date_backup
------------+-------------+-------------
sauvegarde | FULL | 2024-07-08
sauvegarde | incremental | 2024-07-08
(2 rows)

Time: 0.614 ms
(postgres@[local]:5437) [manu] $ > insert into backup values ('sauvegarde','incremental 2','2024-07-08 14:00:00');
INSERT 0 1
Time: 1.436 ms
(postgres@[local]:5437) [manu] $ > select * from backup;
nom | type | date_backup
------------+---------------+-------------
sauvegarde | FULL | 2024-07-08
sauvegarde | incremental | 2024-07-08
sauvegarde | incremental 2 | 2024-07-08
(3 rows)

 

Puis on lance une seconde sauvegarde incrémentale :

 

[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr_2 -l "Incremental 2 Backup PG17" -P -v -i /data/postgres/backup/pg_basebackup/PG17_incr/backup_manifest
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 1/B1000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot "pg_basebackup_8313"
12260/3097787 kB (100%), 1/1 tablespace
pg_basebackup: write-ahead log end point: 1/B1000120
pg_basebackup: waiting for background process to finish streaming ...
pg_basebackup: syncing data to disk ...
pg_basebackup: renaming backup_manifest.tmp to backup_manifest
pg_basebackup: base backup completed

 

Nous remarquons l’appel au “backup manifest” de la dernière sauvegarde incrémentale présente dans le répertoire “/data/postgres/backup/pg_basebackup/PG17_incr

Si l’on regarde la taille de ce nouveau backup

 

[postgres pg_basebackup]$ du -h PG17_incr_2
.......
35M PG17_incr_2

 

A nouveau 35 Mo, mais vu le peu de modifications effectuées, la taille n’est pas très représentative.

Ce qu’il faut retenir, c’est qu’en fonction du fichier “backup manifest” pris lors de l’appel à pg_basebackup, vous pourrez faire soit
– une sauvegarde incrémentale qui prendra les dernières modifications depuis la dernière sauvegarde incrémentale effectuée.
– une sauvegarde différentielle qui prendra les modifications faites depuis la dernière sauvegarde FULL si vous vous appuyez toujours sur le “backup manifest” de votre sauvegarde FULL.

C’est donc ce fichier json “backup manifest” qui a un rôle essentiel dans l’élaboration de votre stratégie de sauvegarde au fur et à mesure du temps.

 

Et la restauration , comment ca se passe ?

 

Si l’on souhaite restaurer tous ces jeux de sauvegardes, nous utilisons un nouvel outil qui est “pg_combinebackup“.
Cet outil permet de “merger” les différentes sauvegardes dans un et un seul dossier que l’on restaurera par la suite.

Dans notre exemple, nous avons fait 1 sauvegarde FULL puis 2 incrémentales.
Nous allons donc restaurer ces 3 jeux de sauvegardes afin de retrouver les données. A noter qu’il existe une option “–dry-run” pour tester la commande

Exécuter la commande en prenant en paramètre les dossiers de sauvegardes dans l’ordre chronologique.

[postgres - ]$ pg_combinebackup -n -o /data/postgres/backup/pg_basebackup/PG17_ALL /data/postgres/backup/pg_basebackup/PG17 /data/postgres/backup/pg_basebackup/PG17_incr /data/postgres/backup/pg_basebackup/PG17_incr_2 

Si aucune erreur en sortie, on exécute sans l’option “dry run”.

 [postgres - ]$ pg_combinebackup -o /data/postgres/backup/pg_basebackup/PG17_ALL /data/postgres/backup/pg_basebackup/PG17 /data/postgres/backup/pg_basebackup/PG17_incr /data/postgres/backup/pg_basebackup/PG17_incr_2 

 

Le répertoire “/data/postgres/backup/pg_basebackup/PG17_ALL” ainsi généré, doit avoir une taille très légèrement supérieure au dossier de la sauvegarde FULL.

[postgres - ]$ du -h PG17_ALL
....
3.0G PG17_ALL

 

Dernière étape, nous passons à la restauration des données.

Nous arrêtons l’instance PG17

[postgres - ]$ pg_ctl -D /data/postgres/17/pg_data/ stop
waiting for server to shut down.... done
server stopped

Nous supprimons les données dans $PGDATA

[postgres - ]$ rm -rf /data/postgres/17/pg_data/* 

Puis nous restaurons ce jeu complet de données avec une simple copie.

[postgres - ]$ cp -r /data/postgres/backup/pg_basebackup/PG17_ALL/* /data/postgres/17/pg_data/ 

Enfin redémarrons l’instance

 

[postgres - ]$ pg_ctl -D /data/postgres/17/pg_data/ start
waiting for server to start....2024-07-08 10:51:45.671 UTC [8909] LOG: redirecting log output to logging collector process
2024-07-08 10:51:45.671 UTC [8909] HINT: Future log output will appear in directory "log".
done
server started

 

Puis contrôler que nous récupérons bien toutes les lignes de notre table “backup”.

 

[postgres@ip-172-44-2-96 pg_basebackup]$ psql -d manu
(postgres@[local]:5437) [manu] primaire $ > select * from backup;
nom | type | date_backup
------------+---------------+-------------
sauvegarde | FULL | 2024-07-08
sauvegarde | incremental | 2024-07-08
sauvegarde | incremental 2 | 2024-07-08

Remarques

  • Attention, toujours vérifier les sauvegardes à chaque étape avec l’outil pg_verifybackup car rien ne garantit qu’au moment de l’appel à pg_combinebackup les différents jeux de sauvegardes FULL et/ou incrémentales ne soient pas corrompus.

 

  • Assurez vous d’être en mode “data_checksum” activé et ne pas changer de mode entre les jeux de backup. Le “backup manifest” s’appuie sur ce paramétrage pour valider les checksums de chaque fichier.

 

  • Le mode TAR pour pg_basebackup n’est pas compatible pour les sauvegardes full et incrémentales même si celui ci est possible. Mais c’est à vous de détarer les fichiers “base.tar.gz” Et au moment de la restauration  avec “pg_combinebackup“, une possible corruption est rencontrée.
[postgres - ]$ pg_combinebackup -o /data/postgres/backup/pg_basebackup/PG17_all_tar /data/postgres/backup/pg_basebackup/PG17_TAR /data/postgres/backup/pg_basebackup/PG17_incr_TAR
pg_combinebackup: error: could not write to file "/data/postgres/backup/pg_basebackup/PG17_all_tar/base/25284/25332", offset 122470400: wrote 380928 of 409600
pg_combinebackup: removing output directory "/data/postgres/backup/pg_basebackup/PG17_all_tar" 

La compression a potentiellement ajoutée une corruption ne rendant pas possible l’opération de “merge” des données.

 

  • La restauration PITR est possible bien entendu. N’oubliez pas de créer le “recovery.signal” dans $PGDATA et de définir dans le fichier “postgresql.conf” les quelques paramètres suivants
    • recovery_target_name 
    • recovery_target_time
    • recovery_target_xid
    • recovery_target_lsn
    • recovery_target_inclusive = off ou on
    • recovery_target_timeline = ‘latest’
    • recovery_target_action = ‘pause’ 

 

🙂

 

Continuez votre lecture sur le blog :

twitterlinkedinmail

Emmanuel RAMI

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.