From 97b465eeece1687046f569732f5dad5141ecb733 Mon Sep 17 00:00:00 2001 From: daamien Date: Wed, 15 Jan 2025 16:38:02 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20postgres?= =?UTF-8?q?qlfr/planete@57e8d8d8914637e47d3f2d29afda210c91e9c379=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feed.atom | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/feed.atom b/feed.atom index 3fa00a4..ae9f134 100644 --- a/feed.atom +++ b/feed.atom @@ -1,7 +1,7 @@ Planète PostgreSQL https://planete.postgresql.fr/ - 2025-01-15T12:53:53Z + 2025-01-15T16:38:01Z L'actualité de PostgreSQL de français @@ -72,7 +72,7 @@ La montée de version en zero-downtime : merci la réplication ! 2024-12-19T10:28:41Z tag:blog.capdata.fr,2024-12-19:/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;title=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><h1>Introduction :</h1> <p>Dans le monde des bases de données, garantir une disponibilité continue est une exigence incontournable, surtout pour les systèmes critiques où chaque minute d&#8217;arrêt peut entraîner des pertes significatives. Lorsqu’il s’agit de migrer une base de données vers une nouvelle version, ce défi prend une toute autre dimension. Comment mettre à jour votre système sans interrompre les services, tout en préservant l’intégrité des données ?</p> <p>PostgreSQL offre une solution élégante : la réplication logique. Cet outil permet de transférer des données de manière fluide entre différentes versions de PostgreSQL, tout en maintenant la base de données source opérationnelle. Dans cet article, nous allons explorer étape par étape comment utiliser cette fonctionnalité pour réaliser une montée de version sans temps d&#8217;arrêt, du déploiement initial à la bascule finale vers la nouvelle version.</p> <p>Que vous soyez en train de planifier une migration ou simplement curieux de découvrir les possibilités offertes par PostgreSQL, suivez ce guide pratique qui vous permettra de transformer un défi complexe en une opération maîtrisée et efficace.</p> <h1>Le test :</h1> <ol> <li> <h3>Préparation</h3> </li> </ol> <p>Pour tester cette nouvelle méthode, nous aurons besoin de deux instances PostgreSQL. Pour cet article j&#8217;ai choisit de démontrer la technique en migrant d&#8217;une version 14 à une version 17 de PostgreSQL.</p> <p>Je commence donc par installer les versions sur deux machines différentes pouvant communiquer entre elles (c&#8217;est important) :</p> <p>Sur les deux machines nous pouvons exécuter les commandes suivantes :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# sudo apt update sudo apt upgrade -y ... root@ip-192-1-1-246:~# sudo apt -y install gnupg2 wget vim ... root@ip-192-1-1-246:~# sudo sh -c 'echo &quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&quot; &amp;gt; /etc/apt/sources.list.d/pgdg.list' root@ip-192-1-1-246:~# curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg root@ip-192-1-1-246:~# sudo apt -y update Get:1 file:/etc/apt/mirrors/debian.list Mirrorlist [38 B] Get:2 file:/etc/apt/mirrors/debian-security.list Mirrorlist [47 B] Hit:3 https://cdn-aws.deb.debian.org/debian bookworm InRelease Hit:4 https://cdn-aws.deb.debian.org/debian bookworm-updates InRelease Hit:5 https://cdn-aws.deb.debian.org/debian bookworm-backports InRelease Hit:6 https://cdn-aws.deb.debian.org/debian-security bookworm-security InRelease Get:7 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg InRelease [129 kB] Get:8 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg/main amd64 Packages [359 kB] Fetched 489 kB in 1s (348 kB/s) Reading package lists... Done Building dependency tree... Done Reading state information... Done All packages are up to date. </pre> <p>Puis sur notre première machine :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# sudo apt install postgresql-14 Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libgdbm-compat4 libio-pty-perl libipc-run-perl libjson-perl libjson-xs-perl libllvm16 libperl5.36 libpq5 libsensors-config libsensors5 libtypes-serialiser-perl libxslt1.1 libz3-4 logrotate perl perl-modules-5.36 postgresql-client-14 postgresql-client-common postgresql-common ssl-cert sysstat ... root@ip-192-1-1-246:~# systemctl status postgresql@14-main.service ● postgresql@14-main.service - PostgreSQL Cluster 14-main Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled-runtime;&amp;gt; Active: active (running) since Wed 2024-12-04 09:43:55 UTC; 2min 55s ago Process: 15248 ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect &amp;gt; Main PID: 15253 (postgres) Tasks: 7 (limit: 4633) Memory: 17.3M CPU: 239ms CGroup: /system.slice/system-postgresql.slice/postgresql@14-main.service ├─15253 /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresq&amp;gt; ├─15255 &quot;postgres: 14/main: checkpointer &quot; ├─15256 &quot;postgres: 14/main: background writer &quot; ├─15257 &quot;postgres: 14/main: walwriter &quot; ├─15258 &quot;postgres: 14/main: autovacuum launcher &quot; ├─15259 &quot;postgres: 14/main: stats collector &quot; └─15260 &quot;postgres: 14/main: logical replication launcher &quot; Dec 04 09:43:53 ip-192-1-1-246 systemd[1]: Starting postgresql@14-main.service&amp;gt; Dec 04 09:43:55 ip-192-1-1-246 systemd[1]: Started postgresql@14-main.service &amp;gt; </pre> <p>Puis sur la deuxième machine :</p> <pre class="brush: bash; title: ; notranslate"> admin@ip-192-1-1-89:~$ sudo apt install postgresql-17 Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libgdbm-compat4 libio-pty-perl libipc-run-perl libjson-perl libjson-xs-perl libllvm16 libperl5.36 libpq5 libsensors-config libsensors5 libtypes-serialiser-perl libxslt1.1 libz3-4 logrotate perl perl-modules-5.36 postgresql-client-17 postgresql-client-common postgresql-common ssl-cert sysstat admin@ip-192-1-1-89:~$ systemctl status postgresql@17-main.service ● postgresql@17-main.service - PostgreSQL Cluster 17-main Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled-runtime; &amp;gt; Active: active (running) since Wed 2024-12-04 09:52:33 UTC; 2min 13s ago Process: 15235 ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect 1&amp;gt; Main PID: 15240 (postgres) Tasks: 6 (limit: 4633) Memory: 20.5M CPU: 332ms CGroup: /system.slice/system-postgresql.slice/postgresql@17-main.service ├─15240 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql&amp;gt; ├─15241 &quot;postgres: 17/main: checkpointer &quot; ├─15242 &quot;postgres: 17/main: background writer &quot; ├─15244 &quot;postgres: 17/main: walwriter &quot; ├─15245 &quot;postgres: 17/main: autovacuum launcher &quot; └─15246 &quot;postgres: 17/main: logical replication launcher &quot; Dec 04 09:52:31 ip-192-1-1-89 systemd[1]: Starting postgresql@17-main.service -&amp;gt; Dec 04 09:52:33 ip-192-1-1-89 systemd[1]: Started postgresql@17-main.service -&amp;gt; </pre> <p>Nos deux instances sont maintenant installées. Sur notre première base de données, nous allons créer une base, avec deux tables, et quelques lignes.</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# CREATE DATABASE mydb; CREATE DATABASE postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# CREATE TABLE customers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE mydb=# CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id), amount NUMERIC(10,2) NOT NULL, order_date TIMESTAMP DEFAULT NOW() ); CREATE TABLE mydb=# INSERT INTO customers (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'); INSERT 0 3 mydb=# INSERT INTO orders (customer_id, amount) VALUES (1, 50.75), (2, 20.00), (1, 75.00); INSERT 0 3 </pre> <h3>2. Configurer la base de données source</h3> <p>Sur notre première machine, nous allons modifier les paramètres du fichier de configuration de PostgreSQL pour permettre de pouvoir créer la réplication :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# su - postgres postgres@ip-192-1-1-246:~$ cd /etc/postgresql/14/main postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi postgresql.conf </pre> <p>Il s&#8217;agit de modifier les paramètres suivants :</p> <blockquote><p>wal_level = logical<br /> max_replication_slots = 4<br /> max_wal_senders = 4</p></blockquote> <p>Nous modifierons ensuite le pg_hba pour rajouter l&#8217;autorisation de connexion entre les deux machines :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi pg_hba.conf </pre> <p>Il suffira de rajouter une ligne :</p> <blockquote><p>host replication <span class="hljs-attribute">all</span> &lt;destination_ip&gt; scram-sha-256</p> <p>host replication all &lt;source_ip&gt; scram-sha-256</p> <p>host all replication &lt;destination_ip&gt; scram-sha-256</p> <p>host all replication &lt;source-ip&gt; scram-sha-256</p></blockquote> <p>Il ne faut pas oublier de redémarrer le serveur PostgreSQL une fois ces modifications effectuées :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# systemctl stop postgresql@14-main.service root@ip-192-1-1-246:~# systemctl start postgresql@14-main.service </pre> <h3>3. Configurer la base de donnée de destination</h3> <p>Après avoir configuré notre base de donnée depuis laquelle nous allons faire notre migration, il nous faut a présent configurer celle qui va recevoir la nouvelle base de donnée migrée.</p> <p>Pour cela, nous allons répéter les étapes de configuration de la base de donnée source, en les adaptant sur notre base de donnée de destination : modifier le postgresql.conf, puis le pg_hba.conf, redémarrer ensuite la base de données</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-89:~$ cd /etc/postgresql/17/main/ postgres@ip-192-1-1-89:/etc/postgresql/17/main$ vi postgresql.conf </pre> <blockquote><p>wal_level = logical<br /> max_replication_slots = 4<br /> max_wal_senders = 4</p></blockquote> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi pg_hba.conf </pre> <blockquote><p>host replication <span class="hljs-attribute">all</span> &lt;destination_ip&gt; scram-sha-256</p> <p>host replication all &lt;source_ip&gt; scram-sha-256</p> <p>host all replication &lt;destination_ip&gt; scram-sha-256</p> <p>host all replication &lt;source-ip&gt; scram-sha-256</p></blockquote> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# systemctl stop postgresql@14-main.service root@ip-192-1-1-246:~# systemctl start postgresql@14-main.service </pre> <p>Il ne faudra pas oublier de créer la base de donnée ainsi que toutes les structures de tables et autres objets dans notre base cible pour qu&#8217;elle puisse recevoir les données. Pour avoir les scripts de création de la base de données, vous pouvez faire un pg_dump avec l&#8217;option</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-89:~$ psql psql (17.2 (Debian 17.2-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate"> postgres=# CREATE DATABASE mydb; CREATE DATABASE </pre> <p>N&#8217;oubliez pas de donner tout les droits à votre utilisateur de replication pour qu&#8217;il puisse lire, écrire&#8230; Sur votre base de données repliquée, sur la source, comme sur la destination :</p> <pre class="brush: sql; title: ; notranslate"> postgres=# GRANT ALL PRIVILEGES ON DATABASE &quot;mydb&quot; to replication; GRANT mydb=# GRANT ALL PRIVILEGES ON all tables in schema public to replication; GRANT </pre> <h3>4. Mise en place de la réplication logique</h3> <p>Maintenant que nos deux environnement sont bien en place, nous sommes prêts à mettre en route le processus de réplication logique pour commencer à transférer les données. Les étapes du dessous ont demandé une première intervention hors horaire de prod, notamment pour redémarrer le service postgreSQL, mais le but d&#8217;une migration avec réplication logique, c&#8217;est de pouvoir ensuite n&#8217;avoir rien à toucher jusqu&#8217;au moment de basculer les applicatifs d&#8217;une ip a une autre.</p> <p>Sur notre machine source, on créé la publication qui va nous servir à transférer nos tables :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:~$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# CREATE PUBLICATION my_pub FOR ALL TABLES; CREATE PUBLICATION </pre> <p>On va ensuite créé la souscription sur la base de données cible de notre migration :</p> <pre class="brush: sql; title: ; notranslate"> mydb=# create subscription my_sub connection 'host=192.1.1.246 port=5432 dbname=mydb user=replication password=replication'publication my_pub; NOTICE: created replication slot &quot;my_sub&quot; on publisher CREATE SUBSCRIPTION </pre> <p>Maintenant que la subscription est en place, on peut vérifier qu&#8217;elle fonctionne. Pendant ce temps, la vrai production, sur la version 14, peut continuer à fonctionner, elle sera automatiquement repliquée sur la nouvelle version 17.</p> <p>On peut vérifier ou en est notre replication avec la commande <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> pg_stat_subscription;</p> <pre class="brush: sql; title: ; notranslate"> mydb=# SELECT * FROM pg_stat_subscription; -[ RECORD 1 ]---------+------------------------------ subid | 16422 subname | my_sub worker_type | apply pid | 16076 leader_pid | relid | received_lsn | 0/1733988 last_msg_send_time | 2024-12-04 14:23:59.873074+00 last_msg_receipt_time | 2024-12-04 14:23:59.872357+00 latest_end_lsn | 0/1733988 latest_end_time | 2024-12-04 14:23:59.873074+00 </pre> <h3>5. Test de replication, bascule, et nettoyage</h3> <p>Une fois que la synchronisation de votre replication logique est terminée, ce qui peut prendre un certain temps si vous avez beaucoup de données, vous pouvez constater de vous même sur les lignes que vous ajoutez, modifiez ou supprimez sur votre instance source sont repliquées sur l&#8217;instance de destination.</p> <p>Par exemple, ajoutons un nouveau customer sur notre base source :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:~$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# INSERT INTO customers (name, email) VALUES ('Diana', 'diana@example.com'); INSERT 0 1 </pre> <p>Si nous allons requêter sur notre instance de destination :</p> <pre class="brush: sql; title: ; notranslate"> mydb=# select * from customers where name='Diana'; id | name | email | created_at ----+-------+-------------------+---------------------------- 4 | Diana | diana@example.com | 2024-12-04 14:31:05.708031 (1 row) </pre> <p>Quand vous vous êtes bien assuré que tout fonctionne, vous pouvez alors rediriger les drivers odbc de vos applications vers le nouveau serveur et non plus l&#8217;ancien.</p> <p>Une fois que cela est fait, vous pouvez alors supprimer le lien de replication, puisque l&#8217;ancienne instance ne sera plus alimentée, et même supprimer l&#8217;ancienne version si vous n&#8217;en avez plus l&#8217;utilité.</p> <p>Sur la destination, notre nouveau serveur de prod :</p> <pre class="brush: sql; title: ; notranslate"> DROP SUBSCRIPTION my_sub; </pre> <p>Sur la source, ancien serveur qui va être supprimé :</p> <pre class="brush: sql; title: ; notranslate"> DROP PUBLICATION my_pub; </pre> <h1>Conclusion</h1> <p>La réplication logique se distingue comme l’une des meilleures solutions pour minimiser le temps d’arrêt lors d’une migration de version PostgreSQL. En permettant une synchronisation continue des données entre deux instances, elle garantit une transition en douceur sans jamais interrompre les services en cours. Cela en fait un choix idéal pour les environnements critiques où la disponibilité est primordiale.</p> <h3>Avantages :</h3> <p><strong>Zéro downtime :</strong> la source reste opérationnelle pendant toute la migration.<br /> <strong>Flexibilité :</strong> possibilité de migrer vers une infrastructure différente (nouveau matériel, cloud, etc.).<br /> <strong>Granularité :</strong> la réplication logique peut se limiter à certaines tables si nécessaire.</p> <h3>Inconvénients :</h3> <p><strong>Complexité initiale :</strong> la configuration et les tests nécessitent une bonne maîtrise des paramètres de PostgreSQL.<br /> <strong>Impact sur les performances :</strong> la charge de réplication peut légèrement affecter les performances de la base source, surtout avec un grand volume de données.<br /> <strong>Non pris en charge pour certains types de données :</strong> les types spécifiques ou les extensions non standards ne sont pas toujours compatibles avec la réplication logique.</p> <p>Si la réplication logique est souvent la méthode privilégiée pour des mises à jour critiques, elle n’est pas la seule option. Des alternatives comme les outils de sauvegarde et restauration ou la réplication physique peuvent répondre à d’autres besoins spécifiques, notamment pour des bases de données très volumineuses ou des scénarios nécessitant une réplication complète du système.</p> <p>Dans tous les cas, le choix de la méthode dépendra de votre contexte, de vos contraintes techniques et de vos objectifs métier. Prenez le temps d’évaluer les différentes options pour garantir une migration réussie et sans surprise.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/replication-logique-avec-postgresql/" rel="bookmark" title="23 janvier 2020">Réplication logique avec PostgreSQL</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/migration-postgresql-via-slony-i-ou-comment-reduire-le-temps-de-coupure/" rel="bookmark" title="27 janvier 2020">Migration PostgreSQL via SLONY-I ou comment réduire le temps de coupure</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/migrer-dun-cluster-galera-mariadb-10-3-vers-mariadb-10-5-avec-la-replication-logique/" rel="bookmark" title="25 février 2022">Migrer d&#8217;un cluster Galera MariaDB 10.3 vers MariaDB 10.5 avec la réplication logique</a> (David Baffaleuf) [ContainerMySQLNon classé]</li> <li><a href="https://blog.capdata.fr/index.php/comparatif-des-gestionnaires-de-vip-dans-un-cluster-patroni-episode-1-keepalived/" rel="bookmark" title="6 mars 2022">Comparatif des gestionnaires de VIP dans un cluster Patroni : épisode 1 (KEEPALIVED)</a> (David Baffaleuf) [ContainerPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 3.138 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;title=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/">La montée de version en zero-downtime : merci la réplication !</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;title=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><h1>Introduction :</h1> <p>Dans le monde des bases de données, garantir une disponibilité continue est une exigence incontournable, surtout pour les systèmes critiques où chaque minute d&#8217;arrêt peut entraîner des pertes significatives. Lorsqu’il s’agit de migrer une base de données vers une nouvelle version, ce défi prend une toute autre dimension. Comment mettre à jour votre système sans interrompre les services, tout en préservant l’intégrité des données ?</p> <p>PostgreSQL offre une solution élégante : la réplication logique. Cet outil permet de transférer des données de manière fluide entre différentes versions de PostgreSQL, tout en maintenant la base de données source opérationnelle. Dans cet article, nous allons explorer étape par étape comment utiliser cette fonctionnalité pour réaliser une montée de version sans temps d&#8217;arrêt, du déploiement initial à la bascule finale vers la nouvelle version.</p> <p>Que vous soyez en train de planifier une migration ou simplement curieux de découvrir les possibilités offertes par PostgreSQL, suivez ce guide pratique qui vous permettra de transformer un défi complexe en une opération maîtrisée et efficace.</p> <h1>Le test :</h1> <ol> <li> <h3>Préparation</h3> </li> </ol> <p>Pour tester cette nouvelle méthode, nous aurons besoin de deux instances PostgreSQL. Pour cet article j&#8217;ai choisit de démontrer la technique en migrant d&#8217;une version 14 à une version 17 de PostgreSQL.</p> <p>Je commence donc par installer les versions sur deux machines différentes pouvant communiquer entre elles (c&#8217;est important) :</p> <p>Sur les deux machines nous pouvons exécuter les commandes suivantes :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# sudo apt update sudo apt upgrade -y ... root@ip-192-1-1-246:~# sudo apt -y install gnupg2 wget vim ... root@ip-192-1-1-246:~# sudo sh -c 'echo &quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&quot; &amp;gt; /etc/apt/sources.list.d/pgdg.list' root@ip-192-1-1-246:~# curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg root@ip-192-1-1-246:~# sudo apt -y update Get:1 file:/etc/apt/mirrors/debian.list Mirrorlist [38 B] Get:2 file:/etc/apt/mirrors/debian-security.list Mirrorlist [47 B] Hit:3 https://cdn-aws.deb.debian.org/debian bookworm InRelease Hit:4 https://cdn-aws.deb.debian.org/debian bookworm-updates InRelease Hit:5 https://cdn-aws.deb.debian.org/debian bookworm-backports InRelease Hit:6 https://cdn-aws.deb.debian.org/debian-security bookworm-security InRelease Get:7 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg InRelease [129 kB] Get:8 http://apt.postgresql.org/pub/repos/apt bookworm-pgdg/main amd64 Packages [359 kB] Fetched 489 kB in 1s (348 kB/s) Reading package lists... Done Building dependency tree... Done Reading state information... Done All packages are up to date. </pre> <p>Puis sur notre première machine :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# sudo apt install postgresql-14 Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libgdbm-compat4 libio-pty-perl libipc-run-perl libjson-perl libjson-xs-perl libllvm16 libperl5.36 libpq5 libsensors-config libsensors5 libtypes-serialiser-perl libxslt1.1 libz3-4 logrotate perl perl-modules-5.36 postgresql-client-14 postgresql-client-common postgresql-common ssl-cert sysstat ... root@ip-192-1-1-246:~# systemctl status postgresql@14-main.service ● postgresql@14-main.service - PostgreSQL Cluster 14-main Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled-runtime;&amp;gt; Active: active (running) since Wed 2024-12-04 09:43:55 UTC; 2min 55s ago Process: 15248 ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect &amp;gt; Main PID: 15253 (postgres) Tasks: 7 (limit: 4633) Memory: 17.3M CPU: 239ms CGroup: /system.slice/system-postgresql.slice/postgresql@14-main.service ├─15253 /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresq&amp;gt; ├─15255 &quot;postgres: 14/main: checkpointer &quot; ├─15256 &quot;postgres: 14/main: background writer &quot; ├─15257 &quot;postgres: 14/main: walwriter &quot; ├─15258 &quot;postgres: 14/main: autovacuum launcher &quot; ├─15259 &quot;postgres: 14/main: stats collector &quot; └─15260 &quot;postgres: 14/main: logical replication launcher &quot; Dec 04 09:43:53 ip-192-1-1-246 systemd[1]: Starting postgresql@14-main.service&amp;gt; Dec 04 09:43:55 ip-192-1-1-246 systemd[1]: Started postgresql@14-main.service &amp;gt; </pre> <p>Puis sur la deuxième machine :</p> <pre class="brush: bash; title: ; notranslate"> admin@ip-192-1-1-89:~$ sudo apt install postgresql-17 Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libgdbm-compat4 libio-pty-perl libipc-run-perl libjson-perl libjson-xs-perl libllvm16 libperl5.36 libpq5 libsensors-config libsensors5 libtypes-serialiser-perl libxslt1.1 libz3-4 logrotate perl perl-modules-5.36 postgresql-client-17 postgresql-client-common postgresql-common ssl-cert sysstat admin@ip-192-1-1-89:~$ systemctl status postgresql@17-main.service ● postgresql@17-main.service - PostgreSQL Cluster 17-main Loaded: loaded (/lib/systemd/system/postgresql@.service; enabled-runtime; &amp;gt; Active: active (running) since Wed 2024-12-04 09:52:33 UTC; 2min 13s ago Process: 15235 ExecStart=/usr/bin/pg_ctlcluster --skip-systemctl-redirect 1&amp;gt; Main PID: 15240 (postgres) Tasks: 6 (limit: 4633) Memory: 20.5M CPU: 332ms CGroup: /system.slice/system-postgresql.slice/postgresql@17-main.service ├─15240 /usr/lib/postgresql/17/bin/postgres -D /var/lib/postgresql&amp;gt; ├─15241 &quot;postgres: 17/main: checkpointer &quot; ├─15242 &quot;postgres: 17/main: background writer &quot; ├─15244 &quot;postgres: 17/main: walwriter &quot; ├─15245 &quot;postgres: 17/main: autovacuum launcher &quot; └─15246 &quot;postgres: 17/main: logical replication launcher &quot; Dec 04 09:52:31 ip-192-1-1-89 systemd[1]: Starting postgresql@17-main.service -&amp;gt; Dec 04 09:52:33 ip-192-1-1-89 systemd[1]: Started postgresql@17-main.service -&amp;gt; </pre> <p>Nos deux instances sont maintenant installées. Sur notre première base de données, nous allons créer une base, avec deux tables, et quelques lignes.</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# CREATE DATABASE mydb; CREATE DATABASE postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# CREATE TABLE customers ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE mydb=# CREATE TABLE orders ( id SERIAL PRIMARY KEY, customer_id INT REFERENCES customers(id), amount NUMERIC(10,2) NOT NULL, order_date TIMESTAMP DEFAULT NOW() ); CREATE TABLE mydb=# INSERT INTO customers (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'); INSERT 0 3 mydb=# INSERT INTO orders (customer_id, amount) VALUES (1, 50.75), (2, 20.00), (1, 75.00); INSERT 0 3 </pre> <h3>2. Configurer la base de données source</h3> <p>Sur notre première machine, nous allons modifier les paramètres du fichier de configuration de PostgreSQL pour permettre de pouvoir créer la réplication :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# su - postgres postgres@ip-192-1-1-246:~$ cd /etc/postgresql/14/main postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi postgresql.conf </pre> <p>Il s&#8217;agit de modifier les paramètres suivants :</p> <blockquote><p>wal_level = logical<br /> max_replication_slots = 4<br /> max_wal_senders = 4</p></blockquote> <p>Nous modifierons ensuite le pg_hba pour rajouter l&#8217;autorisation de connexion entre les deux machines :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi pg_hba.conf </pre> <p>Il suffira de rajouter une ligne :</p> <blockquote><p>host replication <span class="hljs-attribute">all</span> &lt;destination_ip&gt; scram-sha-256</p> <p>host replication all &lt;source_ip&gt; scram-sha-256</p> <p>host all replication &lt;destination_ip&gt; scram-sha-256</p> <p>host all replication &lt;source-ip&gt; scram-sha-256</p></blockquote> <p>Il ne faut pas oublier de redémarrer le serveur PostgreSQL une fois ces modifications effectuées :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# systemctl stop postgresql@14-main.service root@ip-192-1-1-246:~# systemctl start postgresql@14-main.service </pre> <h3>3. Configurer la base de donnée de destination</h3> <p>Après avoir configuré notre base de donnée depuis laquelle nous allons faire notre migration, il nous faut a présent configurer celle qui va recevoir la nouvelle base de donnée migrée.</p> <p>Pour cela, nous allons répéter les étapes de configuration de la base de donnée source, en les adaptant sur notre base de donnée de destination : modifier le postgresql.conf, puis le pg_hba.conf, redémarrer ensuite la base de données</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-89:~$ cd /etc/postgresql/17/main/ postgres@ip-192-1-1-89:/etc/postgresql/17/main$ vi postgresql.conf </pre> <blockquote><p>wal_level = logical<br /> max_replication_slots = 4<br /> max_wal_senders = 4</p></blockquote> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:/etc/postgresql/14/main$ vi pg_hba.conf </pre> <blockquote><p>host replication <span class="hljs-attribute">all</span> &lt;destination_ip&gt; scram-sha-256</p> <p>host replication all &lt;source_ip&gt; scram-sha-256</p> <p>host all replication &lt;destination_ip&gt; scram-sha-256</p> <p>host all replication &lt;source-ip&gt; scram-sha-256</p></blockquote> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-246:~# systemctl stop postgresql@14-main.service root@ip-192-1-1-246:~# systemctl start postgresql@14-main.service </pre> <p>Il ne faudra pas oublier de créer la base de donnée ainsi que toutes les structures de tables et autres objets dans notre base cible pour qu&#8217;elle puisse recevoir les données. Pour avoir les scripts de création de la base de données, vous pouvez faire un pg_dump avec l&#8217;option</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-89:~$ psql psql (17.2 (Debian 17.2-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate"> postgres=# CREATE DATABASE mydb; CREATE DATABASE </pre> <p>N&#8217;oubliez pas de donner tout les droits à votre utilisateur de replication pour qu&#8217;il puisse lire, écrire&#8230; Sur votre base de données repliquée, sur la source, comme sur la destination :</p> <pre class="brush: sql; title: ; notranslate"> postgres=# GRANT ALL PRIVILEGES ON DATABASE &quot;mydb&quot; to replication; GRANT mydb=# GRANT ALL PRIVILEGES ON all tables in schema public to replication; GRANT </pre> <h3>4. Mise en place de la réplication logique</h3> <p>Maintenant que nos deux environnement sont bien en place, nous sommes prêts à mettre en route le processus de réplication logique pour commencer à transférer les données. Les étapes du dessous ont demandé une première intervention hors horaire de prod, notamment pour redémarrer le service postgreSQL, mais le but d&#8217;une migration avec réplication logique, c&#8217;est de pouvoir ensuite n&#8217;avoir rien à toucher jusqu&#8217;au moment de basculer les applicatifs d&#8217;une ip a une autre.</p> <p>Sur notre machine source, on créé la publication qui va nous servir à transférer nos tables :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:~$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# CREATE PUBLICATION my_pub FOR ALL TABLES; CREATE PUBLICATION </pre> <p>On va ensuite créé la souscription sur la base de données cible de notre migration :</p> <pre class="brush: sql; title: ; notranslate"> mydb=# create subscription my_sub connection 'host=192.1.1.246 port=5432 dbname=mydb user=replication password=replication'publication my_pub; NOTICE: created replication slot &quot;my_sub&quot; on publisher CREATE SUBSCRIPTION </pre> <p>Maintenant que la subscription est en place, on peut vérifier qu&#8217;elle fonctionne. Pendant ce temps, la vrai production, sur la version 14, peut continuer à fonctionner, elle sera automatiquement repliquée sur la nouvelle version 17.</p> <p>On peut vérifier ou en est notre replication avec la commande <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> pg_stat_subscription;</p> <pre class="brush: sql; title: ; notranslate"> mydb=# SELECT * FROM pg_stat_subscription; -[ RECORD 1 ]---------+------------------------------ subid | 16422 subname | my_sub worker_type | apply pid | 16076 leader_pid | relid | received_lsn | 0/1733988 last_msg_send_time | 2024-12-04 14:23:59.873074+00 last_msg_receipt_time | 2024-12-04 14:23:59.872357+00 latest_end_lsn | 0/1733988 latest_end_time | 2024-12-04 14:23:59.873074+00 </pre> <h3>5. Test de replication, bascule, et nettoyage</h3> <p>Une fois que la synchronisation de votre replication logique est terminée, ce qui peut prendre un certain temps si vous avez beaucoup de données, vous pouvez constater de vous même sur les lignes que vous ajoutez, modifiez ou supprimez sur votre instance source sont repliquées sur l&#8217;instance de destination.</p> <p>Par exemple, ajoutons un nouveau customer sur notre base source :</p> <pre class="brush: bash; title: ; notranslate"> postgres@ip-192-1-1-246:~$ psql psql (14.15 (Debian 14.15-1.pgdg120+1)) Type &quot;help&quot; for help. </pre> <p>&nbsp;</p> <pre class="brush: sql; title: ; notranslate"> postgres=# \c mydb You are now connected to database &quot;mydb&quot; as user &quot;postgres&quot;. mydb=# INSERT INTO customers (name, email) VALUES ('Diana', 'diana@example.com'); INSERT 0 1 </pre> <p>Si nous allons requêter sur notre instance de destination :</p> <pre class="brush: sql; title: ; notranslate"> mydb=# select * from customers where name='Diana'; id | name | email | created_at ----+-------+-------------------+---------------------------- 4 | Diana | diana@example.com | 2024-12-04 14:31:05.708031 (1 row) </pre> <p>Quand vous vous êtes bien assuré que tout fonctionne, vous pouvez alors rediriger les drivers odbc de vos applications vers le nouveau serveur et non plus l&#8217;ancien.</p> <p>Une fois que cela est fait, vous pouvez alors supprimer le lien de replication, puisque l&#8217;ancienne instance ne sera plus alimentée, et même supprimer l&#8217;ancienne version si vous n&#8217;en avez plus l&#8217;utilité.</p> <p>Sur la destination, notre nouveau serveur de prod :</p> <pre class="brush: sql; title: ; notranslate"> DROP SUBSCRIPTION my_sub; </pre> <p>Sur la source, ancien serveur qui va être supprimé :</p> <pre class="brush: sql; title: ; notranslate"> DROP PUBLICATION my_pub; </pre> <h1>Conclusion</h1> <p>La réplication logique se distingue comme l’une des meilleures solutions pour minimiser le temps d’arrêt lors d’une migration de version PostgreSQL. En permettant une synchronisation continue des données entre deux instances, elle garantit une transition en douceur sans jamais interrompre les services en cours. Cela en fait un choix idéal pour les environnements critiques où la disponibilité est primordiale.</p> <h3>Avantages :</h3> <p><strong>Zéro downtime :</strong> la source reste opérationnelle pendant toute la migration.<br /> <strong>Flexibilité :</strong> possibilité de migrer vers une infrastructure différente (nouveau matériel, cloud, etc.).<br /> <strong>Granularité :</strong> la réplication logique peut se limiter à certaines tables si nécessaire.</p> <h3>Inconvénients :</h3> <p><strong>Complexité initiale :</strong> la configuration et les tests nécessitent une bonne maîtrise des paramètres de PostgreSQL.<br /> <strong>Impact sur les performances :</strong> la charge de réplication peut légèrement affecter les performances de la base source, surtout avec un grand volume de données.<br /> <strong>Non pris en charge pour certains types de données :</strong> les types spécifiques ou les extensions non standards ne sont pas toujours compatibles avec la réplication logique.</p> <p>Si la réplication logique est souvent la méthode privilégiée pour des mises à jour critiques, elle n’est pas la seule option. Des alternatives comme les outils de sauvegarde et restauration ou la réplication physique peuvent répondre à d’autres besoins spécifiques, notamment pour des bases de données très volumineuses ou des scénarios nécessitant une réplication complète du système.</p> <p>Dans tous les cas, le choix de la méthode dépendra de votre contexte, de vos contraintes techniques et de vos objectifs métier. Prenez le temps d’évaluer les différentes options pour garantir une migration réussie et sans surprise.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/replication-logique-avec-postgresql/" rel="bookmark" title="23 janvier 2020">Réplication logique avec PostgreSQL</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/migration-postgresql-via-slony-i-ou-comment-reduire-le-temps-de-coupure/" rel="bookmark" title="27 janvier 2020">Migration PostgreSQL via SLONY-I ou comment réduire le temps de coupure</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/migrer-dun-cluster-galera-mariadb-10-3-vers-mariadb-10-5-avec-la-replication-logique/" rel="bookmark" title="25 février 2022">Migrer d&#8217;un cluster Galera MariaDB 10.3 vers MariaDB 10.5 avec la réplication logique</a> (David Baffaleuf) [ContainerMySQLNon classé]</li> <li><a href="https://blog.capdata.fr/index.php/comparatif-des-gestionnaires-de-vip-dans-un-cluster-patroni-episode-1-keepalived/" rel="bookmark" title="6 mars 2022">Comparatif des gestionnaires de VIP dans un cluster Patroni : épisode 1 (KEEPALIVED)</a> (David Baffaleuf) [ContainerPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 7.440 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633&#038;title=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=La%20mont%C3%A9e%20de%20version%20en%20zero-downtime%20%3A%20merci%20la%20r%C3%A9plication%20%21&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10633" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/">La montée de version en zero-downtime : merci la réplication !</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>Introduction : Dans le monde des bases de données, garantir une disponibilité continue est une exigence incontournable, surtout pour les systèmes critiques où chaque minute d&#8217;arrêt peut entraîner des pertes significatives. Lorsqu’il s’agit de migrer une base de données vers&#8230; <a href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/">La montée de version en zero-downtime : merci la réplication !</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> @@ -83,7 +83,7 @@ pg_vector : l’IA et PostgreSQL 2024-12-03T07:22:34Z tag:blog.capdata.fr,2024-12-03:/index.php/pg_vector-lia-et-postgresql/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;title=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><h2>1. Introduction : L&#8217;intelligence artificielle et le rôle des bases de données</h2> <p>L&#8217;intelligence artificielle (IA) connaît une popularité croissante, des assistants virtuels aux voitures autonomes, en passant par les recommandations de films et de produits. Mais pour que ces technologies fonctionnent, elles ont besoin de données, souvent en grande quantité. C’est là qu’interviennent les bases de données : elles stockent, gèrent et permettent d&#8217;accéder à ces données de manière efficace.</p> <p>Les bases de données, comme PostgreSQL, ont donc un rôle clé dans l’IA. Mais l&#8217;IA ne traite pas toujours des informations simples comme des noms ou des chiffres ; souvent, elle doit manipuler des informations complexes, comme des représentations numériques d&#8217;images, de sons, ou de textes. Pour gérer ces données spécifiques, il faut des outils adaptés, et c&#8217;est là que l&#8217;extension pg_vector de PostgreSQL entre en jeu.</p> <h2>2. Les vecteurs en informatique et dans pg_vector</h2> <p>Dans le cadre de l’informatique, un vecteur est simplement une liste de nombres. Ces nombres peuvent représenter n’importe quoi : les caractéristiques d’un produit, les mots d’un texte ou même une image. Par exemple, pour un document texte, chaque mot peut être transformé en une série de nombres qui capture son sens dans un certain contexte.</p> <p>L’extension pg_vector permet à PostgreSQL de stocker et de manipuler ces vecteurs. Elle offre un moyen simple de les utiliser directement dans une base de données. Imaginons que nous avons des centaines de documents et que nous souhaitions rechercher les plus similaires à un texte donné : en stockant les représentations numériques (ou embeddings) de ces documents sous forme de vecteurs, nous pouvons facilement comparer leur similarité grâce à pg_vector.</p> <h2>3. Le lien entre l&#8217;IA et les vecteurs</h2> <p>L&#8217;intelligence artificielle repose sur la capacité à comprendre et traiter des informations complexes. Par exemple, quand une IA doit reconnaître une image, elle ne &#8220;voit&#8221; pas comme nous. Au lieu de cela, l&#8217;image est transformée en une série de nombres, un vecteur, qui représente ses caractéristiques (couleurs, formes, etc.).</p> <p>Le même principe s’applique au texte. Les modèles de traitement du langage, comme ceux utilisés par les moteurs de recherche ou les chatbots, transforment chaque mot ou phrase en vecteur. Ces vecteurs capturent le sens des mots et permettent à l&#8217;IA de manipuler des informations complexes sans &#8220;comprendre&#8221; le langage humain.</p> <p>C&#8217;est ici que les embeddings entrent en jeu. Un embedding est un vecteur qui représente des données sous une forme que l’IA peut utiliser. Par exemple, dans un système de recommandation, chaque produit est converti en un embedding, et les produits les plus proches de celui que nous venons de consulter (en termes de vecteur) nous seront recommandés. Grâce à pg_vector, ces embeddings peuvent être stockés et comparés directement dans une base de données.</p> <h2>4. Pourquoi est-ce utile ?</h2> <p>L&#8217;extension pg_vector est très utile pour des applications qui nécessitent la recherche par similarité. Par exemple, dans un moteur de recherche, si nous voulons trouver les documents les plus proches d&#8217;un texte donné, pg_vector permet de comparer les vecteurs (ou embeddings) de chaque document pour voir lesquels sont les plus similaires.</p> <p>Autre exemple, dans une plateforme de streaming musical, chaque chanson peut être convertie en vecteur qui représente ses caractéristiques (comme le tempo, la tonalité, etc.). Grâce à pg_vector, on peut facilement recommander des chansons similaires à celles que nous écoutons.</p> <p>L’avantage de pg_vector, c’est qu’il permet de gérer ces vecteurs directement dans la base de données, ce qui évite de passer par des systèmes externes plus complexes. Cela simplifie le développement et améliore la performance, car tout est géré au même endroit.</p> <h2>5. Le test</h2> <p>Pour démontrer le fonctionnement de l&#8217;extension, rien de tel qu&#8217;un petit test pour éprouver les fonctionnalités qu&#8217;elle propose. Le test sera plutôt simple et succinct pour être accessible. Le prérequi est d&#8217;avoir une version PostgreSQL 14 ou plus récente d&#8217;installée.</p> <h3>Etape 1 :</h3> <p>On commence par installer l&#8217;extension pg_vector. Pour cela, nous allons avoir besoin d&#8217;un certain nombre d&#8217;outils pour le faire fonctionner. Une partie de ces outils sont disponible dans la distribution dev de PostgreSQL</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# sudo apt install postgresql-server-dev-14 </pre> <p>Nous aurons également besoin de gcc et make :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# apt install make root@ip-192-1-1-201:~# apt-get install gcc </pre> <p>On effectue ensuite un git clone du projet :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# git clone https://github.com/pgvector/pgvector.git </pre> <p>Et une fois que c&#8217;est fait, on l&#8217;installe avec make :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# cd pgvector root@ip-192-1-1-201:~# make &amp;amp;&amp;amp; sudo make install </pre> <h3>Etape 2 :</h3> <p>On se connecte à PostgreSQL pour créer l&#8217;extension. Au passage, on créé aussi une base de données pour faire nos test.</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# su - postgres postgres@ip-192-1-1-201:~$ psql psql (14.13 (Ubuntu 14.13-0ubuntu0.22.04.1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate"> postgres=# create database test_vector; CREATE DATABASE postgres=# \c test_vector You are now connected to database &quot;test_vector&quot; as user &quot;postgres&quot;. test_vector=# CREATE EXTENSION vector; CREATE EXTENSION </pre> <p>Et dans la foulée, on crée une table qui contient les vecteurs sur lesquels nous allons faire les test</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# CREATE TABLE documents ( id SERIAL PRIMARY KEY, title TEXT, embedding vector(3) -- vecteur de dimension 3 pour cet exemple ); CREATE TABLE </pre> <p>Insertion des données d&#8217;exemple :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# INSERT INTO documents (title, embedding) VALUES ('Document 1', '[0.1, 0.2, 0.3]'), ('Document 2', '[0.4, 0.5, 0.6]'), ('Document 3', '[0.9, 0.8, 0.7]'); INSERT 0 3 </pre> <h3>Etape 3 :</h3> <p>Nous avons deux types de choses à tester pour montrer l&#8217;efficacité de notre extension. En effet, pour rechercher un vecteur, deux modes s&#8217;offrent à nous :</p> <ol> <li> <h4>La distance cosinus</h4> </li> </ol> <p>La distance cosinus mesure non pas combien deux vecteurs sont éloignés, mais l&#8217;angle entre eux. C’est un peu comme comparer la direction dans laquelle pointent deux vecteurs plutôt que la distance réelle entre eux.</p> <p>Imaginons que nous sommes en train de lancer deux flèches. La distance cosinus nous dira si les deux flèches pointent dans la même direction (sont similaires) ou si elles pointent dans des directions très différentes (sont moins similaires).</p> <p>Dans le cadre de l’IA, cette mesure est souvent utilisée pour comparer des embeddings (représentations numériques complexes), car elle se concentre sur la relation entre les éléments, indépendamment de leur taille exacte.</p> <p>Exemple simple :</p> <p>Prenons les deux films :</p> <ul> <li>Film A : <code>[1, 5, 50, 120]</code></li> <li>Film B : <code>[2, 4, 45, 110]</code></li> </ul> <p>La distance cosinus ne va pas se soucier de la différence de valeur entre chaque composant, mais va regarder si les deux films ont des proportions similaires. Autrement dit, est-ce que leur &#8220;profil&#8221; général est proche ou éloigné ?</p> <p>Pour tester cette distance, dans notre pg vector, on utilise la méthode suivante :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# SELECT title, embedding, embedding &amp;lt;=&amp;gt; '[0.2, 0.1, 0.3]' AS distance FROM documents ORDER BY embedding &amp;lt;=&amp;gt; '[0.2, 0.1, 0.3]' ASC LIMIT 3; title | embedding | distance ------------+---------------+--------------------- Document 2 | [0.4,0.5,0.6] | 0.05582537807240784 Document 1 | [0.1,0.2,0.3] | 0.07142855242198809 Document 3 | [0.9,0.8,0.7] | 0.09815280896106982 (3 rows) </pre> <p>Le symbole &lt;=&gt; représente une distance cosinus.</p> <h4>2. La distance Euclidienne</h4> <p>Imaginons que nous sommes sur une carte avec deux points : le point A et le point B. La distance euclidienne, c&#8217;est la façon la plus intuitive de mesurer la distance entre ces deux points, comme si nous tracions une ligne droite entre eux. Pour parler en terme simple, c’est la &#8220;distance à vol d&#8217;oiseau&#8221;.</p> <p>Dans le cadre des vecteurs, la distance euclidienne mesure la différence entre deux vecteurs, un peu comme si chaque vecteur était un point sur une carte en plusieurs dimensions. Plus cette distance est petite, plus les deux vecteurs (et donc les objets qu’ils représentent) sont similaires.</p> <p>Exemple simple :</p> <p>Imaginons deux films représentés par les vecteurs suivants :</p> <ul> <li><strong>Film A</strong> : <code>[1, 5, 50, 120]</code></li> <li><strong>Film B</strong> : <code>[2, 4, 45, 110]</code></li> </ul> <p>La distance euclidienne va calculer la différence entre chaque nombre des deux vecteurs et déterminer à quel point ces films sont proches en termes de caractéristiques (genre, nombre d’acteurs, budget, etc.).</p> <p>Dans pg_vector on peut le tester ainsi :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# SELECT title, embedding, embedding &amp;lt;-&amp;gt; '[0.2, 0.1, 0.3]' AS distance FROM documents ORDER BY embedding &amp;lt;-&amp;gt; '[0.2, 0.1, 0.3]' ASC LIMIT 3; title | embedding | distance ------------+---------------+-------------------- Document 1 | [0.1,0.2,0.3] | 0.1414213612422477 Document 2 | [0.4,0.5,0.6] | 0.5385165006363984 Document 3 | [0.9,0.8,0.7] | 1.0677078185041473 (3 rows) </pre> <p>Elle est représentée par le cigle &lt;-&gt; dans pg_vector.</p> <h4>3. Quand choisir l&#8217;une ou l&#8217;autre des distances ?</h4> <ul> <li>La distance euclidienne est utile quand tu veux mesurer la différence globale entre deux objets. Elle est facile à comprendre et à utiliser pour des comparaisons directes.</li> <li>La distance cosinus est utile quand tu veux savoir si deux objets sont globalement similaires dans leur profil, indépendamment de leur taille ou de leur échelle. Elle est souvent utilisée pour comparer des documents textuels ou des données complexes en IA</li> </ul> <h2>Conclusion</h2> <p>L&#8217;extension pg_vector apporte une fonctionnalité puissante à PostgreSQL, permettant de manipuler des données complexes sous forme de vecteurs. Que ce soit pour des systèmes de recommandation, des moteurs de recherche ou toute autre application liée à l’intelligence artificielle, elle offre un moyen simple et efficace d&#8217;intégrer l&#8217;IA dans les bases de données. Et tout cela, sans avoir besoin de comprendre des mathématiques avancées : il suffit de savoir que ces vecteurs permettent de traiter des informations complexes de manière très efficace.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/" rel="bookmark" title="3 avril 2024">pg_recursively_delete : Simplifier les suppressions récursives</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/transparent-data-encryption-pour-postgresql/" rel="bookmark" title="13 mai 2022">Transparent Data Encryption pour PostgreSQL</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David Baffaleuf) [SQL Server]</li> </ul> <p><!-- Similar Posts took 2.103 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;title=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_vector-lia-et-postgresql/">pg_vector : l&#8217;IA et PostgreSQL</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;title=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><h2>1. Introduction : L&#8217;intelligence artificielle et le rôle des bases de données</h2> <p>L&#8217;intelligence artificielle (IA) connaît une popularité croissante, des assistants virtuels aux voitures autonomes, en passant par les recommandations de films et de produits. Mais pour que ces technologies fonctionnent, elles ont besoin de données, souvent en grande quantité. C’est là qu’interviennent les bases de données : elles stockent, gèrent et permettent d&#8217;accéder à ces données de manière efficace.</p> <p>Les bases de données, comme PostgreSQL, ont donc un rôle clé dans l’IA. Mais l&#8217;IA ne traite pas toujours des informations simples comme des noms ou des chiffres ; souvent, elle doit manipuler des informations complexes, comme des représentations numériques d&#8217;images, de sons, ou de textes. Pour gérer ces données spécifiques, il faut des outils adaptés, et c&#8217;est là que l&#8217;extension pg_vector de PostgreSQL entre en jeu.</p> <h2>2. Les vecteurs en informatique et dans pg_vector</h2> <p>Dans le cadre de l’informatique, un vecteur est simplement une liste de nombres. Ces nombres peuvent représenter n’importe quoi : les caractéristiques d’un produit, les mots d’un texte ou même une image. Par exemple, pour un document texte, chaque mot peut être transformé en une série de nombres qui capture son sens dans un certain contexte.</p> <p>L’extension pg_vector permet à PostgreSQL de stocker et de manipuler ces vecteurs. Elle offre un moyen simple de les utiliser directement dans une base de données. Imaginons que nous avons des centaines de documents et que nous souhaitions rechercher les plus similaires à un texte donné : en stockant les représentations numériques (ou embeddings) de ces documents sous forme de vecteurs, nous pouvons facilement comparer leur similarité grâce à pg_vector.</p> <h2>3. Le lien entre l&#8217;IA et les vecteurs</h2> <p>L&#8217;intelligence artificielle repose sur la capacité à comprendre et traiter des informations complexes. Par exemple, quand une IA doit reconnaître une image, elle ne &#8220;voit&#8221; pas comme nous. Au lieu de cela, l&#8217;image est transformée en une série de nombres, un vecteur, qui représente ses caractéristiques (couleurs, formes, etc.).</p> <p>Le même principe s’applique au texte. Les modèles de traitement du langage, comme ceux utilisés par les moteurs de recherche ou les chatbots, transforment chaque mot ou phrase en vecteur. Ces vecteurs capturent le sens des mots et permettent à l&#8217;IA de manipuler des informations complexes sans &#8220;comprendre&#8221; le langage humain.</p> <p>C&#8217;est ici que les embeddings entrent en jeu. Un embedding est un vecteur qui représente des données sous une forme que l’IA peut utiliser. Par exemple, dans un système de recommandation, chaque produit est converti en un embedding, et les produits les plus proches de celui que nous venons de consulter (en termes de vecteur) nous seront recommandés. Grâce à pg_vector, ces embeddings peuvent être stockés et comparés directement dans une base de données.</p> <h2>4. Pourquoi est-ce utile ?</h2> <p>L&#8217;extension pg_vector est très utile pour des applications qui nécessitent la recherche par similarité. Par exemple, dans un moteur de recherche, si nous voulons trouver les documents les plus proches d&#8217;un texte donné, pg_vector permet de comparer les vecteurs (ou embeddings) de chaque document pour voir lesquels sont les plus similaires.</p> <p>Autre exemple, dans une plateforme de streaming musical, chaque chanson peut être convertie en vecteur qui représente ses caractéristiques (comme le tempo, la tonalité, etc.). Grâce à pg_vector, on peut facilement recommander des chansons similaires à celles que nous écoutons.</p> <p>L’avantage de pg_vector, c’est qu’il permet de gérer ces vecteurs directement dans la base de données, ce qui évite de passer par des systèmes externes plus complexes. Cela simplifie le développement et améliore la performance, car tout est géré au même endroit.</p> <h2>5. Le test</h2> <p>Pour démontrer le fonctionnement de l&#8217;extension, rien de tel qu&#8217;un petit test pour éprouver les fonctionnalités qu&#8217;elle propose. Le test sera plutôt simple et succinct pour être accessible. Le prérequi est d&#8217;avoir une version PostgreSQL 14 ou plus récente d&#8217;installée.</p> <h3>Etape 1 :</h3> <p>On commence par installer l&#8217;extension pg_vector. Pour cela, nous allons avoir besoin d&#8217;un certain nombre d&#8217;outils pour le faire fonctionner. Une partie de ces outils sont disponible dans la distribution dev de PostgreSQL</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# sudo apt install postgresql-server-dev-14 </pre> <p>Nous aurons également besoin de gcc et make :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# apt install make root@ip-192-1-1-201:~# apt-get install gcc </pre> <p>On effectue ensuite un git clone du projet :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# git clone https://github.com/pgvector/pgvector.git </pre> <p>Et une fois que c&#8217;est fait, on l&#8217;installe avec make :</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# cd pgvector root@ip-192-1-1-201:~# make &amp;amp;&amp;amp; sudo make install </pre> <h3>Etape 2 :</h3> <p>On se connecte à PostgreSQL pour créer l&#8217;extension. Au passage, on créé aussi une base de données pour faire nos test.</p> <pre class="brush: bash; title: ; notranslate"> root@ip-192-1-1-201:~# su - postgres postgres@ip-192-1-1-201:~$ psql psql (14.13 (Ubuntu 14.13-0ubuntu0.22.04.1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate"> postgres=# create database test_vector; CREATE DATABASE postgres=# \c test_vector You are now connected to database &quot;test_vector&quot; as user &quot;postgres&quot;. test_vector=# CREATE EXTENSION vector; CREATE EXTENSION </pre> <p>Et dans la foulée, on crée une table qui contient les vecteurs sur lesquels nous allons faire les test</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# CREATE TABLE documents ( id SERIAL PRIMARY KEY, title TEXT, embedding vector(3) -- vecteur de dimension 3 pour cet exemple ); CREATE TABLE </pre> <p>Insertion des données d&#8217;exemple :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# INSERT INTO documents (title, embedding) VALUES ('Document 1', '[0.1, 0.2, 0.3]'), ('Document 2', '[0.4, 0.5, 0.6]'), ('Document 3', '[0.9, 0.8, 0.7]'); INSERT 0 3 </pre> <h3>Etape 3 :</h3> <p>Nous avons deux types de choses à tester pour montrer l&#8217;efficacité de notre extension. En effet, pour rechercher un vecteur, deux modes s&#8217;offrent à nous :</p> <ol> <li> <h4>La distance cosinus</h4> </li> </ol> <p>La distance cosinus mesure non pas combien deux vecteurs sont éloignés, mais l&#8217;angle entre eux. C’est un peu comme comparer la direction dans laquelle pointent deux vecteurs plutôt que la distance réelle entre eux.</p> <p>Imaginons que nous sommes en train de lancer deux flèches. La distance cosinus nous dira si les deux flèches pointent dans la même direction (sont similaires) ou si elles pointent dans des directions très différentes (sont moins similaires).</p> <p>Dans le cadre de l’IA, cette mesure est souvent utilisée pour comparer des embeddings (représentations numériques complexes), car elle se concentre sur la relation entre les éléments, indépendamment de leur taille exacte.</p> <p>Exemple simple :</p> <p>Prenons les deux films :</p> <ul> <li>Film A : <code>[1, 5, 50, 120]</code></li> <li>Film B : <code>[2, 4, 45, 110]</code></li> </ul> <p>La distance cosinus ne va pas se soucier de la différence de valeur entre chaque composant, mais va regarder si les deux films ont des proportions similaires. Autrement dit, est-ce que leur &#8220;profil&#8221; général est proche ou éloigné ?</p> <p>Pour tester cette distance, dans notre pg vector, on utilise la méthode suivante :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# SELECT title, embedding, embedding &amp;lt;=&amp;gt; '[0.2, 0.1, 0.3]' AS distance FROM documents ORDER BY embedding &amp;lt;=&amp;gt; '[0.2, 0.1, 0.3]' ASC LIMIT 3; title | embedding | distance ------------+---------------+--------------------- Document 2 | [0.4,0.5,0.6] | 0.05582537807240784 Document 1 | [0.1,0.2,0.3] | 0.07142855242198809 Document 3 | [0.9,0.8,0.7] | 0.09815280896106982 (3 rows) </pre> <p>Le symbole &lt;=&gt; représente une distance cosinus.</p> <h4>2. La distance Euclidienne</h4> <p>Imaginons que nous sommes sur une carte avec deux points : le point A et le point B. La distance euclidienne, c&#8217;est la façon la plus intuitive de mesurer la distance entre ces deux points, comme si nous tracions une ligne droite entre eux. Pour parler en terme simple, c’est la &#8220;distance à vol d&#8217;oiseau&#8221;.</p> <p>Dans le cadre des vecteurs, la distance euclidienne mesure la différence entre deux vecteurs, un peu comme si chaque vecteur était un point sur une carte en plusieurs dimensions. Plus cette distance est petite, plus les deux vecteurs (et donc les objets qu’ils représentent) sont similaires.</p> <p>Exemple simple :</p> <p>Imaginons deux films représentés par les vecteurs suivants :</p> <ul> <li><strong>Film A</strong> : <code>[1, 5, 50, 120]</code></li> <li><strong>Film B</strong> : <code>[2, 4, 45, 110]</code></li> </ul> <p>La distance euclidienne va calculer la différence entre chaque nombre des deux vecteurs et déterminer à quel point ces films sont proches en termes de caractéristiques (genre, nombre d’acteurs, budget, etc.).</p> <p>Dans pg_vector on peut le tester ainsi :</p> <pre class="brush: sql; title: ; notranslate"> test_vector=# SELECT title, embedding, embedding &amp;lt;-&amp;gt; '[0.2, 0.1, 0.3]' AS distance FROM documents ORDER BY embedding &amp;lt;-&amp;gt; '[0.2, 0.1, 0.3]' ASC LIMIT 3; title | embedding | distance ------------+---------------+-------------------- Document 1 | [0.1,0.2,0.3] | 0.1414213612422477 Document 2 | [0.4,0.5,0.6] | 0.5385165006363984 Document 3 | [0.9,0.8,0.7] | 1.0677078185041473 (3 rows) </pre> <p>Elle est représentée par le cigle &lt;-&gt; dans pg_vector.</p> <h4>3. Quand choisir l&#8217;une ou l&#8217;autre des distances ?</h4> <ul> <li>La distance euclidienne est utile quand tu veux mesurer la différence globale entre deux objets. Elle est facile à comprendre et à utiliser pour des comparaisons directes.</li> <li>La distance cosinus est utile quand tu veux savoir si deux objets sont globalement similaires dans leur profil, indépendamment de leur taille ou de leur échelle. Elle est souvent utilisée pour comparer des documents textuels ou des données complexes en IA</li> </ul> <h2>Conclusion</h2> <p>L&#8217;extension pg_vector apporte une fonctionnalité puissante à PostgreSQL, permettant de manipuler des données complexes sous forme de vecteurs. Que ce soit pour des systèmes de recommandation, des moteurs de recherche ou toute autre application liée à l’intelligence artificielle, elle offre un moyen simple et efficace d&#8217;intégrer l&#8217;IA dans les bases de données. Et tout cela, sans avoir besoin de comprendre des mathématiques avancées : il suffit de savoir que ces vecteurs permettent de traiter des informations complexes de manière très efficace.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/" rel="bookmark" title="3 avril 2024">pg_recursively_delete : Simplifier les suppressions récursives</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/transparent-data-encryption-pour-postgresql/" rel="bookmark" title="13 mai 2022">Transparent Data Encryption pour PostgreSQL</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/openrowset-episode-1/" rel="bookmark" title="13 juillet 2011">OPENROWSET, épisode 1</a> (David Baffaleuf) [SQL Server]</li> </ul> <p><!-- Similar Posts took 6.311 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620&#038;title=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_vector%20%3A%20l%E2%80%99IA%20et%20PostgreSQL&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10620" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_vector-lia-et-postgresql/">pg_vector : l&#8217;IA et PostgreSQL</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>1. Introduction : L&#8217;intelligence artificielle et le rôle des bases de données L&#8217;intelligence artificielle (IA) connaît une popularité croissante, des assistants virtuels aux voitures autonomes, en passant par les recommandations de films et de produits. Mais pour que ces technologies&#8230; <a href="https://blog.capdata.fr/index.php/pg_vector-lia-et-postgresql/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_vector-lia-et-postgresql/">pg_vector : l&#8217;IA et PostgreSQL</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> @@ -94,7 +94,7 @@ PostgreSQL 17 : des sauvegardes incrémentales avec pg_basebackup 2024-07-16T11:24:05Z tag:blog.capdata.fr,2024-07-16:/index.php/postgresql-17-sauvegardes-incrementales/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;title=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-10592" src="https://blog.capdata.fr/wp-content/uploads/2024/07/SalesGrowth.jpg" alt="" width="279" height="180" /></p> <p>&nbsp;</p> <p>Bonjour</p> <p>Les 11 et 12 juin derniers, nous étions aux journées PGDAY à Lille pour découvrir les nouveautés autour de PostgreSQL.<br /> 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&#8217;utiliser PostgreSQL dans les meilleurs conditions.</p> <p>Un article m&#8217;a particulièrement intéressé cette année, c&#8217;est celui de <a href="https://www.linkedin.com/in/stefan-fercot/?originalSubdomain=be">Stefan Fercot</a> 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 &#8220;démystifier les sauvegardes incrémentales sous PostgreSQL&#8221;.</p> <p>J&#8217;ai écouté sa conférence tout en ayant hâte de tester sa mise en place dès mon retour de Lille.</p> <p>Je tiens à remercier Stefan pour son travail sur ce sujet sauvegardes PostgreSQL.</p> <p>&nbsp;</p> <p>Tout d&#8217;abord, il faut savoir que les sujets sauvegardes incrémentales ont été déjà abordés avec des outils comme <strong>Barman</strong> ou <strong>Pg_BackRest</strong>, et que certaines instances PostgreSQL de production sont sauvegardées via ces mécanismes depuis quelques années maintenant.</p> <p>Ici, nous parlons de la solution &#8220;backup incremental&#8221; inclu nativement dans le moteur PostgreSQL, et disponible avec l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8220;. C&#8217;est d&#8217;ailleurs ce point que Stefan a souligné durant la journée PGDAY du 11 juin dernier.</p> <p>Cette nouvelle fonctionnalité fait partie de la version <strong>PostgreSQL 17</strong> qui est pour le moment, en version<strong> Beta 2</strong>.<br /> Celle ci devrait sortir, comme à l&#8217;accoutumé, au cour de l&#8217;automne prochain.</p> <p>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.</p> <p>&nbsp;</p> <h2>Installation de PostgreSQL 17</h2> <p>&nbsp;</p> <p>Pour tester cette fonctionnalité, nous devons installer la toute dernière version de PostgreSQL , la 17 beta 2. Attention, celle ci n&#8217;étant pas disponible dans les dépôts PGDG, nous devons nous charger d&#8217;installer cette version via le site postgresql.org</p> <p><a href="https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-8-x86_64/">https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-8-x86_64/</a></p> <p>Nous disposons d&#8217;un serveur Linux fork Red Hat 8 (Rocky Linux). Il nous faut donc télécharger les &#8220;rpm&#8221; liés à cette version.</p> <p>Les packages dont nous avons besoin sont les suivants</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate"># 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</pre> <p>&nbsp;</p> <p>Nous les installons avec le compte <strong>root</strong> de notre serveur.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <p>Comme nous sommes sur un environnement &#8220;Red Hat like&#8221;, la création d&#8217;une première instance via &#8220;initdb&#8221; est nécessaire.<br /> Surtout, ne pas oublier d&#8217;activer les &#8220;data checksums&#8221; (option -k), nous verrons pourquoi dans la suite de cet article. La suite est à faire avec le compte <strong>postgres</strong>.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ initdb -D /data/postgres/17/pg_data -k The files belonging to this database system will be owned by user &quot;postgres&quot;. This user must also own the server process. The database cluster will be initialized with locale &quot;en_US.UTF-8&quot;. The default database encoding has accordingly been set to &quot;UTF8&quot;. The default text search configuration will be set to &quot;english&quot;. Data page checksums are enabled. creating directory /data/postgres/17/pg_data ... ok creating subdirectories ... ok selecting dynamic shared memory implementation ... posix selecting default &quot;max_connections&quot; ... 100 selecting default &quot;shared_buffers&quot; ... 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 &quot;trust&quot; 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</pre> <p>&nbsp;</p> <p>Démarrer cette instance pour s&#8217;assurer que tout fonctionne</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data -l logfile start waiting for server to start.... done server started</pre> <p>&nbsp;</p> <p>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.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ psql (postgres@[local]:5437) [postgres] &gt; 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)</pre> <p>&nbsp;</p> <h3>Upgrade de version</h3> <p>&nbsp;</p> <p>Comme nous disposions deja d&#8217;une version PostgreSQL15 sur ce serveur, nous passons par un upgrade via l&#8217;outil &#8220;pg_upgrade&#8221; toujours disponible dans cette nouvelle version.</p> <p>Lancer pg_upgrade en mode check</p> <pre class="brush: bash; title: ; notranslate">[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* &quot;/usr/pgsql-17/bin/pg_ctl&quot; -w -D &quot;/data/postgres/17/pg_data&quot; -o &quot;&quot; -m smart stop &quot;/data/postgres/17/pg_data/pg_upgrade_output.d/20240708T085906.955/log/pg_upgrade_server.log&quot; </pre> <p>la log est générée dans le $PGDATA de la version 17.</p> <p>Puis lancer l&#8217;exécution de pg_upgrade</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <h2>Effectuer une sauvegarde</h2> <p>&nbsp;</p> <h3>Prérequis</h3> <p>Avant de pouvoir effectuer une première sauvegarde avec l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8221; natif, il est primordial de respecter certains prérequis important.</p> <ul> <li>L&#8217;instance PostgreSQL doit être créée avec les &#8216;data checksums&#8217; activés. Si ce n&#8217;est pas le cas, utiliser l&#8217;outil &#8220;<strong>pg_checksums</strong>&#8221; avec l&#8217;option &#8220;<strong>-e</strong>&#8220;.</li> </ul> <p>&nbsp;</p> <ul> <li>Si vous lancez une sauvegarde full puis une incrémentale immédiatement, vous avez toutes les chances de tomber sur cette erreur</li> </ul> <pre class="brush: bash; title: ; notranslate">pg_basebackup: error: could not initiate base backup: ERROR: incremental backups cannot be taken unless WAL summarization is enabled</pre> <p>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.<br /> Pour les DBA Oracle, le &#8220;block change tracking&#8221; de la version Enterprise Edition vous parlera très certainement&#8230;.<br /> Il s&#8217;agit ici de la même fonctionnalité, c&#8217;est à dire, tracer les modifications effectuées dans les blocks de données.<br /> Cette option est le &#8220;<strong>summarize_wal</strong>&#8220;.</p> <p>Pour activer l&#8217;option, nous aurons 2 paramètres à modifier, soit via un ALTER SYSTEM directement sous psql, ou bien dans le fichier &#8220;postgresql.conf&#8221;.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>Le premier paramètre permet d&#8217;activer cette option.<br /> Le second définit un temps de conservation des informations concernant les blocks modifiés entre une sauvegarde FULL et un incrémentale.</p> <p>Nous activons donc l&#8217;option &#8220;<strong>summarize_wal</strong>&#8221; et la passons à <strong>ON</strong> et laissons à 10 jours le &#8220;<strong>wal_summary_keep_time</strong>&#8220;.</p> <p>Attention, activez ces deux paramètres avant votre première sauvegarde FULL. Si vous le faites après, vous risquez de rencontrer l&#8217;erreur suivante</p> <pre class="brush: bash; title: ; notranslate">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.</pre> <p>Le LSN pris lors de la première sauvegarde FULL n&#8217;est pas reconnu, et donc la sauvegarde incrémentale ne peut s&#8217;appuyer dessus.</p> <p>&nbsp;</p> <p>Redémarrer l&#8217;instance une fois les modifications effectuées</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data/ restart</pre> <p>&nbsp;</p> <h3>Lancer une sauvegarde FULL</h3> <p>&nbsp;</p> <p>Voici la nouvelle option présente pour l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8221;</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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 &quot;k&quot; or &quot;M&quot;) .... </pre> <p>&nbsp;</p> <p>Depuis la <a href="https://blog.capdata.fr/index.php/postgresql-13-les-nouveautes-interessantes/">version 13</a> de PostgreSQL, nous disposons pour chaque sauvegarde, d&#8217;un fichier nommé &#8220;backup_manifest&#8221;. Il s&#8217;agit d&#8217;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 &#8220;checksum&#8221;.</p> <p>Celui ci est essentiel pour vérifier l&#8217;intégrité de notre sauvegarde avec &#8220;<strong>pg_verifybackup</strong>&#8220;.</p> <p>Nous pouvons à présent faire une première sauvegarde FULL de notre instance PG17.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres -]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17 -F p -l &quot;Full Backup PG17&quot; -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 &quot;pg_basebackup_8048&quot; 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</pre> <p>&nbsp;</p> <p>Puis on effectue quelques transactions : création d&#8217;une table et insertions de données sur cette table de test</p> <pre class="brush: sql; title: ; notranslate">(postgres@[local]:5437) [manu] $ &gt; create table backup (nom varchar(20), type varchar(20), date_backup date); CREATE TABLE Time: 3.344 ms (postgres@[local]:5437) [manu] $ &gt; insert into backup values ('sauvegarde','FULL','2024-07-08 12:00:00'); INSERT 0 1 Time: 3.612 ms (postgres@[local]:5437) [manu] $ &gt; insert into backup values ('sauvegarde','incremental','2024-07-08 13:00:00'); INSERT 0 1 Time: 1.461 ms (postgres@[local]:5437) [manu] $ &gt; select * from backup; nom | type | date_backup ------------+-------------+------------- sauvegarde | FULL | 2024-07-08 sauvegarde | incremental | 2024-07-08 (2 rows)</pre> <p>&nbsp;</p> <p>Repérer le fichier &#8220;backup_manifest&#8221; de la sauvegarde FULL réalisée dans le dossier &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17</strong>&#8221;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <h3>Effectuer une sauvegarde incrémentale</h3> <p>&nbsp;</p> <p>A partir de là, lancer une sauvegarde incrémentale. Nous utilisons l&#8217;option &#8220;<strong>-i</strong>&#8221; pour indiquer à <strong>pg_basebackup</strong> ou est situé le &#8220;backup_manifest&#8221; de la dernière sauvegarde FULL.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr -l &quot;Incremental Backup PG17&quot; -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 &quot;pg_basebackup_8139&quot; 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</pre> <p>&nbsp;</p> <p>S&#8217;il l&#8217;on compare les deux répertoires de sauvegardes &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17</strong>&#8221; et &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_incr</strong>&#8220;, nous voyons que les tailles sont bien différentes</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <p>Un volume de 3Go pour la sauvegarde FULL de l&#8217;instance contre 35Mo pour l&#8217;incrémentale.<br /> La taille occupée par les objets dans chacune des bases est bien plus faible dans la sauvegarde incrémentale.</p> <p>Nous continuons à insérer des données :</p> <pre class="brush: sql; title: ; notranslate"> [postgres - ]$ psql -d manu (postgres@[local]:5437) [manu] $ &gt; 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] $ &gt; insert into backup values ('sauvegarde','incremental 2','2024-07-08 14:00:00'); INSERT 0 1 Time: 1.436 ms (postgres@[local]:5437) [manu] $ &gt; 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)</pre> <p>&nbsp;</p> <p>Puis on lance une seconde sauvegarde incrémentale :</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr_2 -l &quot;Incremental 2 Backup PG17&quot; -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 &quot;pg_basebackup_8313&quot; 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</pre> <p>&nbsp;</p> <p>Nous remarquons l&#8217;appel au &#8220;backup manifest&#8221; de la dernière sauvegarde incrémentale présente dans le répertoire &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_incr</strong>&#8221;</p> <p>Si l&#8217;on regarde la taille de ce nouveau backup</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres pg_basebackup]$ du -h PG17_incr_2 ....... 35M PG17_incr_2</pre> <p>&nbsp;</p> <p>A nouveau 35 Mo, mais vu le peu de modifications effectuées, la taille n&#8217;est pas très représentative.</p> <p>Ce qu&#8217;il faut retenir, c&#8217;est qu&#8217;en fonction du fichier &#8220;backup manifest&#8221; pris lors de l&#8217;appel à <strong>pg_basebackup</strong>, vous pourrez faire soit<br /> &#8211; une sauvegarde incrémentale qui prendra les dernières modifications depuis la dernière sauvegarde incrémentale effectuée.<br /> &#8211; une sauvegarde différentielle qui prendra les modifications faites depuis la dernière sauvegarde FULL si vous vous appuyez toujours sur le &#8220;backup manifest&#8221; de votre sauvegarde FULL.</p> <p>C&#8217;est donc ce fichier json &#8220;backup manifest&#8221; qui a un rôle essentiel dans l&#8217;élaboration de votre stratégie de sauvegarde au fur et à mesure du temps.</p> <p>&nbsp;</p> <h2>Et la restauration , comment ca se passe ?</h2> <p>&nbsp;</p> <p>Si l&#8217;on souhaite restaurer tous ces jeux de sauvegardes, nous utilisons un nouvel outil qui est &#8220;<strong>pg_combinebackup</strong>&#8220;.<br /> Cet outil permet de &#8220;merger&#8221; les différentes sauvegardes dans un et un seul dossier que l&#8217;on restaurera par la suite.</p> <p>Dans notre exemple, nous avons fait 1 sauvegarde FULL puis 2 incrémentales.<br /> Nous allons donc restaurer ces 3 jeux de sauvegardes afin de retrouver les données. A noter qu&#8217;il existe une option &#8220;&#8211;dry-run&#8221; pour tester la commande</p> <p>Exécuter la commande en prenant en paramètre les dossiers de sauvegardes dans l&#8217;ordre chronologique.</p> <pre class="brush: bash; title: ; notranslate">[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 </pre> <p>Si aucune erreur en sortie, on exécute sans l&#8217;option &#8220;dry run&#8221;.</p> <pre class="brush: bash; title: ; notranslate"> [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 </pre> <p>&nbsp;</p> <p>Le répertoire &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_ALL</strong>&#8221; ainsi généré, doit avoir une taille très légèrement supérieure au dossier de la sauvegarde FULL.</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ du -h PG17_ALL .... 3.0G PG17_ALL</pre> <p>&nbsp;</p> <p>Dernière étape, nous passons à la restauration des données.</p> <p>Nous arrêtons l&#8217;instance PG17</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_ctl -D /data/postgres/17/pg_data/ stop waiting for server to shut down.... done server stopped</pre> <p>Nous supprimons les données dans $PGDATA</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ rm -rf /data/postgres/17/pg_data/* </pre> <p>Puis nous restaurons ce jeu complet de données avec une simple copie.</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ cp -r /data/postgres/backup/pg_basebackup/PG17_ALL/* /data/postgres/17/pg_data/ </pre> <p>Enfin redémarrons l&#8217;instance</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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 &quot;log&quot;. done server started</pre> <p>&nbsp;</p> <p>Puis contrôler que nous récupérons bien toutes les lignes de notre table &#8220;backup&#8221;.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres@ip-172-44-2-96 pg_basebackup]$ psql -d manu (postgres@[local]:5437) [manu] primaire $ &gt; select * from backup; nom | type | date_backup ------------+---------------+------------- sauvegarde | FULL | 2024-07-08 sauvegarde | incremental | 2024-07-08 sauvegarde | incremental 2 | 2024-07-08 </pre> <h3></h3> <h3></h3> <h3>Remarques</h3> <ul> <li>Attention, toujours vérifier les sauvegardes à chaque étape avec l&#8217;outil <strong>pg_verifybackup </strong>car rien ne garantit qu&#8217;au moment de l&#8217;appel à <strong>pg_combinebackup</strong> les différents jeux de sauvegardes FULL et/ou incrémentales ne soient pas corrompus.</li> </ul> <p>&nbsp;</p> <ul> <li>Assurez vous d&#8217;être en mode &#8220;data_checksum&#8221; activé et ne pas changer de mode entre les jeux de backup. Le &#8220;backup manifest&#8221; s&#8217;appuie sur ce paramétrage pour valider les checksums de chaque fichier.</li> </ul> <p>&nbsp;</p> <ul> <li>Le mode TAR pour <strong>pg_basebackup</strong> n&#8217;est pas compatible pour les sauvegardes full et incrémentales même si celui ci est possible. Mais c&#8217;est à vous de détarer les fichiers &#8220;<strong>base.tar.gz</strong>&#8221; Et au moment de la restauration  avec &#8220;<strong>pg_combinebackup</strong>&#8220;, une possible corruption est rencontrée.</li> </ul> <pre class="brush: bash; title: ; notranslate">[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 &quot;/data/postgres/backup/pg_basebackup/PG17_all_tar/base/25284/25332&quot;, offset 122470400: wrote 380928 of 409600 pg_combinebackup: removing output directory &quot;/data/postgres/backup/pg_basebackup/PG17_all_tar&quot; </pre> <p>La compression a potentiellement ajoutée une corruption ne rendant pas possible l&#8217;opération de &#8220;merge&#8221; des données.</p> <p>&nbsp;</p> <ul> <li>La restauration PITR est possible bien entendu. N&#8217;oubliez pas de créer le &#8220;<strong>recovery.signal</strong>&#8221; dans $PGDATA et de définir dans le fichier &#8220;postgresql.conf&#8221; les quelques paramètres suivants <ul> <li><span style="color: #3366ff;">recovery_target_name </span></li> <li><span style="color: #3366ff;">recovery_target_time </span></li> <li><span style="color: #3366ff;">recovery_target_xid </span></li> <li><span style="color: #3366ff;">recovery_target_lsn </span></li> <li><span style="color: #808000;">recovery_target_inclusive = off ou on</span></li> <li><span style="color: #808000;">recovery_target_timeline = &#8216;latest&#8217; </span></li> <li><span style="color: #808000;">recovery_target_action = &#8216;pause&#8217; </span></li> </ul> </li> </ul> <p>&nbsp;</p> <p><img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p> <p>&nbsp;<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/postgresql-la-streaming-replication-en-12/" rel="bookmark" title="19 novembre 2019">PostgreSQL : la streaming replication en 12.</a> (Emmanuel RAMI) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-13-les-nouveautes-interessantes/" rel="bookmark" title="30 octobre 2020">PostgreSQL 13 : présentation</a> (Emmanuel RAMI) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/oracle-rds-effectuer-des-backup-rman-en-mode-paas/" rel="bookmark" title="25 juin 2019">Oracle RDS : effectuer des backup RMAN en mode PaaS.</a> (Emmanuel RAMI) [AWSNon classéOracle]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-comparatif-entre-barman-et-pgbackrest/" rel="bookmark" title="4 février 2020">PostgreSQL : Comparatif entre Barman et pgBackRest</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/haute-disponibilite-de-postgresql-avec-patroni/" rel="bookmark" title="2 février 2022">Haute disponibilité de PostgreSQL avec Patroni</a> (Ludovic AUGEREAU) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 2.558 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;title=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/postgresql-17-sauvegardes-incrementales/">PostgreSQL 17 : des sauvegardes incrémentales avec pg_basebackup</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;title=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-10592" src="https://blog.capdata.fr/wp-content/uploads/2024/07/SalesGrowth.jpg" alt="" width="279" height="180" /></p> <p>&nbsp;</p> <p>Bonjour</p> <p>Les 11 et 12 juin derniers, nous étions aux journées PGDAY à Lille pour découvrir les nouveautés autour de PostgreSQL.<br /> 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&#8217;utiliser PostgreSQL dans les meilleurs conditions.</p> <p>Un article m&#8217;a particulièrement intéressé cette année, c&#8217;est celui de <a href="https://www.linkedin.com/in/stefan-fercot/?originalSubdomain=be">Stefan Fercot</a> 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 &#8220;démystifier les sauvegardes incrémentales sous PostgreSQL&#8221;.</p> <p>J&#8217;ai écouté sa conférence tout en ayant hâte de tester sa mise en place dès mon retour de Lille.</p> <p>Je tiens à remercier Stefan pour son travail sur ce sujet sauvegardes PostgreSQL.</p> <p>&nbsp;</p> <p>Tout d&#8217;abord, il faut savoir que les sujets sauvegardes incrémentales ont été déjà abordés avec des outils comme <strong>Barman</strong> ou <strong>Pg_BackRest</strong>, et que certaines instances PostgreSQL de production sont sauvegardées via ces mécanismes depuis quelques années maintenant.</p> <p>Ici, nous parlons de la solution &#8220;backup incremental&#8221; inclu nativement dans le moteur PostgreSQL, et disponible avec l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8220;. C&#8217;est d&#8217;ailleurs ce point que Stefan a souligné durant la journée PGDAY du 11 juin dernier.</p> <p>Cette nouvelle fonctionnalité fait partie de la version <strong>PostgreSQL 17</strong> qui est pour le moment, en version<strong> Beta 2</strong>.<br /> Celle ci devrait sortir, comme à l&#8217;accoutumé, au cour de l&#8217;automne prochain.</p> <p>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.</p> <p>&nbsp;</p> <h2>Installation de PostgreSQL 17</h2> <p>&nbsp;</p> <p>Pour tester cette fonctionnalité, nous devons installer la toute dernière version de PostgreSQL , la 17 beta 2. Attention, celle ci n&#8217;étant pas disponible dans les dépôts PGDG, nous devons nous charger d&#8217;installer cette version via le site postgresql.org</p> <p><a href="https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-8-x86_64/">https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-8-x86_64/</a></p> <p>Nous disposons d&#8217;un serveur Linux fork Red Hat 8 (Rocky Linux). Il nous faut donc télécharger les &#8220;rpm&#8221; liés à cette version.</p> <p>Les packages dont nous avons besoin sont les suivants</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate"># 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</pre> <p>&nbsp;</p> <p>Nous les installons avec le compte <strong>root</strong> de notre serveur.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <p>Comme nous sommes sur un environnement &#8220;Red Hat like&#8221;, la création d&#8217;une première instance via &#8220;initdb&#8221; est nécessaire.<br /> Surtout, ne pas oublier d&#8217;activer les &#8220;data checksums&#8221; (option -k), nous verrons pourquoi dans la suite de cet article. La suite est à faire avec le compte <strong>postgres</strong>.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ initdb -D /data/postgres/17/pg_data -k The files belonging to this database system will be owned by user &quot;postgres&quot;. This user must also own the server process. The database cluster will be initialized with locale &quot;en_US.UTF-8&quot;. The default database encoding has accordingly been set to &quot;UTF8&quot;. The default text search configuration will be set to &quot;english&quot;. Data page checksums are enabled. creating directory /data/postgres/17/pg_data ... ok creating subdirectories ... ok selecting dynamic shared memory implementation ... posix selecting default &quot;max_connections&quot; ... 100 selecting default &quot;shared_buffers&quot; ... 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 &quot;trust&quot; 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</pre> <p>&nbsp;</p> <p>Démarrer cette instance pour s&#8217;assurer que tout fonctionne</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data -l logfile start waiting for server to start.... done server started</pre> <p>&nbsp;</p> <p>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.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ psql (postgres@[local]:5437) [postgres] &gt; 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)</pre> <p>&nbsp;</p> <h3>Upgrade de version</h3> <p>&nbsp;</p> <p>Comme nous disposions deja d&#8217;une version PostgreSQL15 sur ce serveur, nous passons par un upgrade via l&#8217;outil &#8220;pg_upgrade&#8221; toujours disponible dans cette nouvelle version.</p> <p>Lancer pg_upgrade en mode check</p> <pre class="brush: bash; title: ; notranslate">[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* &quot;/usr/pgsql-17/bin/pg_ctl&quot; -w -D &quot;/data/postgres/17/pg_data&quot; -o &quot;&quot; -m smart stop &quot;/data/postgres/17/pg_data/pg_upgrade_output.d/20240708T085906.955/log/pg_upgrade_server.log&quot; </pre> <p>la log est générée dans le $PGDATA de la version 17.</p> <p>Puis lancer l&#8217;exécution de pg_upgrade</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <h2>Effectuer une sauvegarde</h2> <p>&nbsp;</p> <h3>Prérequis</h3> <p>Avant de pouvoir effectuer une première sauvegarde avec l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8221; natif, il est primordial de respecter certains prérequis important.</p> <ul> <li>L&#8217;instance PostgreSQL doit être créée avec les &#8216;data checksums&#8217; activés. Si ce n&#8217;est pas le cas, utiliser l&#8217;outil &#8220;<strong>pg_checksums</strong>&#8221; avec l&#8217;option &#8220;<strong>-e</strong>&#8220;.</li> </ul> <p>&nbsp;</p> <ul> <li>Si vous lancez une sauvegarde full puis une incrémentale immédiatement, vous avez toutes les chances de tomber sur cette erreur</li> </ul> <pre class="brush: bash; title: ; notranslate">pg_basebackup: error: could not initiate base backup: ERROR: incremental backups cannot be taken unless WAL summarization is enabled</pre> <p>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.<br /> Pour les DBA Oracle, le &#8220;block change tracking&#8221; de la version Enterprise Edition vous parlera très certainement&#8230;.<br /> Il s&#8217;agit ici de la même fonctionnalité, c&#8217;est à dire, tracer les modifications effectuées dans les blocks de données.<br /> Cette option est le &#8220;<strong>summarize_wal</strong>&#8220;.</p> <p>Pour activer l&#8217;option, nous aurons 2 paramètres à modifier, soit via un ALTER SYSTEM directement sous psql, ou bien dans le fichier &#8220;postgresql.conf&#8221;.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>Le premier paramètre permet d&#8217;activer cette option.<br /> Le second définit un temps de conservation des informations concernant les blocks modifiés entre une sauvegarde FULL et un incrémentale.</p> <p>Nous activons donc l&#8217;option &#8220;<strong>summarize_wal</strong>&#8221; et la passons à <strong>ON</strong> et laissons à 10 jours le &#8220;<strong>wal_summary_keep_time</strong>&#8220;.</p> <p>Attention, activez ces deux paramètres avant votre première sauvegarde FULL. Si vous le faites après, vous risquez de rencontrer l&#8217;erreur suivante</p> <pre class="brush: bash; title: ; notranslate">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.</pre> <p>Le LSN pris lors de la première sauvegarde FULL n&#8217;est pas reconnu, et donc la sauvegarde incrémentale ne peut s&#8217;appuyer dessus.</p> <p>&nbsp;</p> <p>Redémarrer l&#8217;instance une fois les modifications effectuées</p> <pre class="brush: bash; title: ; notranslate">[postgres ~]$ pg_ctl -D /data/postgres/17/pg_data/ restart</pre> <p>&nbsp;</p> <h3>Lancer une sauvegarde FULL</h3> <p>&nbsp;</p> <p>Voici la nouvelle option présente pour l&#8217;outil &#8220;<strong>pg_basebackup</strong>&#8221;</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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 &quot;k&quot; or &quot;M&quot;) .... </pre> <p>&nbsp;</p> <p>Depuis la <a href="https://blog.capdata.fr/index.php/postgresql-13-les-nouveautes-interessantes/">version 13</a> de PostgreSQL, nous disposons pour chaque sauvegarde, d&#8217;un fichier nommé &#8220;backup_manifest&#8221;. Il s&#8217;agit d&#8217;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 &#8220;checksum&#8221;.</p> <p>Celui ci est essentiel pour vérifier l&#8217;intégrité de notre sauvegarde avec &#8220;<strong>pg_verifybackup</strong>&#8220;.</p> <p>Nous pouvons à présent faire une première sauvegarde FULL de notre instance PG17.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres -]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17 -F p -l &quot;Full Backup PG17&quot; -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 &quot;pg_basebackup_8048&quot; 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</pre> <p>&nbsp;</p> <p>Puis on effectue quelques transactions : création d&#8217;une table et insertions de données sur cette table de test</p> <pre class="brush: sql; title: ; notranslate">(postgres@[local]:5437) [manu] $ &gt; create table backup (nom varchar(20), type varchar(20), date_backup date); CREATE TABLE Time: 3.344 ms (postgres@[local]:5437) [manu] $ &gt; insert into backup values ('sauvegarde','FULL','2024-07-08 12:00:00'); INSERT 0 1 Time: 3.612 ms (postgres@[local]:5437) [manu] $ &gt; insert into backup values ('sauvegarde','incremental','2024-07-08 13:00:00'); INSERT 0 1 Time: 1.461 ms (postgres@[local]:5437) [manu] $ &gt; select * from backup; nom | type | date_backup ------------+-------------+------------- sauvegarde | FULL | 2024-07-08 sauvegarde | incremental | 2024-07-08 (2 rows)</pre> <p>&nbsp;</p> <p>Repérer le fichier &#8220;backup_manifest&#8221; de la sauvegarde FULL réalisée dans le dossier &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17</strong>&#8221;</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <h3>Effectuer une sauvegarde incrémentale</h3> <p>&nbsp;</p> <p>A partir de là, lancer une sauvegarde incrémentale. Nous utilisons l&#8217;option &#8220;<strong>-i</strong>&#8221; pour indiquer à <strong>pg_basebackup</strong> ou est situé le &#8220;backup_manifest&#8221; de la dernière sauvegarde FULL.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr -l &quot;Incremental Backup PG17&quot; -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 &quot;pg_basebackup_8139&quot; 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</pre> <p>&nbsp;</p> <p>S&#8217;il l&#8217;on compare les deux répertoires de sauvegardes &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17</strong>&#8221; et &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_incr</strong>&#8220;, nous voyons que les tailles sont bien différentes</p> <pre class="brush: bash; title: ; notranslate">[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</pre> <p>&nbsp;</p> <p>Un volume de 3Go pour la sauvegarde FULL de l&#8217;instance contre 35Mo pour l&#8217;incrémentale.<br /> La taille occupée par les objets dans chacune des bases est bien plus faible dans la sauvegarde incrémentale.</p> <p>Nous continuons à insérer des données :</p> <pre class="brush: sql; title: ; notranslate"> [postgres - ]$ psql -d manu (postgres@[local]:5437) [manu] $ &gt; 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] $ &gt; insert into backup values ('sauvegarde','incremental 2','2024-07-08 14:00:00'); INSERT 0 1 Time: 1.436 ms (postgres@[local]:5437) [manu] $ &gt; 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)</pre> <p>&nbsp;</p> <p>Puis on lance une seconde sauvegarde incrémentale :</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_basebackup -D /data/postgres/backup/pg_basebackup/PG17_incr_2 -l &quot;Incremental 2 Backup PG17&quot; -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 &quot;pg_basebackup_8313&quot; 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</pre> <p>&nbsp;</p> <p>Nous remarquons l&#8217;appel au &#8220;backup manifest&#8221; de la dernière sauvegarde incrémentale présente dans le répertoire &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_incr</strong>&#8221;</p> <p>Si l&#8217;on regarde la taille de ce nouveau backup</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres pg_basebackup]$ du -h PG17_incr_2 ....... 35M PG17_incr_2</pre> <p>&nbsp;</p> <p>A nouveau 35 Mo, mais vu le peu de modifications effectuées, la taille n&#8217;est pas très représentative.</p> <p>Ce qu&#8217;il faut retenir, c&#8217;est qu&#8217;en fonction du fichier &#8220;backup manifest&#8221; pris lors de l&#8217;appel à <strong>pg_basebackup</strong>, vous pourrez faire soit<br /> &#8211; une sauvegarde incrémentale qui prendra les dernières modifications depuis la dernière sauvegarde incrémentale effectuée.<br /> &#8211; une sauvegarde différentielle qui prendra les modifications faites depuis la dernière sauvegarde FULL si vous vous appuyez toujours sur le &#8220;backup manifest&#8221; de votre sauvegarde FULL.</p> <p>C&#8217;est donc ce fichier json &#8220;backup manifest&#8221; qui a un rôle essentiel dans l&#8217;élaboration de votre stratégie de sauvegarde au fur et à mesure du temps.</p> <p>&nbsp;</p> <h2>Et la restauration , comment ca se passe ?</h2> <p>&nbsp;</p> <p>Si l&#8217;on souhaite restaurer tous ces jeux de sauvegardes, nous utilisons un nouvel outil qui est &#8220;<strong>pg_combinebackup</strong>&#8220;.<br /> Cet outil permet de &#8220;merger&#8221; les différentes sauvegardes dans un et un seul dossier que l&#8217;on restaurera par la suite.</p> <p>Dans notre exemple, nous avons fait 1 sauvegarde FULL puis 2 incrémentales.<br /> Nous allons donc restaurer ces 3 jeux de sauvegardes afin de retrouver les données. A noter qu&#8217;il existe une option &#8220;&#8211;dry-run&#8221; pour tester la commande</p> <p>Exécuter la commande en prenant en paramètre les dossiers de sauvegardes dans l&#8217;ordre chronologique.</p> <pre class="brush: bash; title: ; notranslate">[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 </pre> <p>Si aucune erreur en sortie, on exécute sans l&#8217;option &#8220;dry run&#8221;.</p> <pre class="brush: bash; title: ; notranslate"> [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 </pre> <p>&nbsp;</p> <p>Le répertoire &#8220;<strong>/data/postgres/backup/pg_basebackup/PG17_ALL</strong>&#8221; ainsi généré, doit avoir une taille très légèrement supérieure au dossier de la sauvegarde FULL.</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ du -h PG17_ALL .... 3.0G PG17_ALL</pre> <p>&nbsp;</p> <p>Dernière étape, nous passons à la restauration des données.</p> <p>Nous arrêtons l&#8217;instance PG17</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ pg_ctl -D /data/postgres/17/pg_data/ stop waiting for server to shut down.... done server stopped</pre> <p>Nous supprimons les données dans $PGDATA</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ rm -rf /data/postgres/17/pg_data/* </pre> <p>Puis nous restaurons ce jeu complet de données avec une simple copie.</p> <pre class="brush: bash; title: ; notranslate">[postgres - ]$ cp -r /data/postgres/backup/pg_basebackup/PG17_ALL/* /data/postgres/17/pg_data/ </pre> <p>Enfin redémarrons l&#8217;instance</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[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 &quot;log&quot;. done server started</pre> <p>&nbsp;</p> <p>Puis contrôler que nous récupérons bien toutes les lignes de notre table &#8220;backup&#8221;.</p> <p>&nbsp;</p> <pre class="brush: bash; title: ; notranslate">[postgres@ip-172-44-2-96 pg_basebackup]$ psql -d manu (postgres@[local]:5437) [manu] primaire $ &gt; select * from backup; nom | type | date_backup ------------+---------------+------------- sauvegarde | FULL | 2024-07-08 sauvegarde | incremental | 2024-07-08 sauvegarde | incremental 2 | 2024-07-08 </pre> <h3></h3> <h3></h3> <h3>Remarques</h3> <ul> <li>Attention, toujours vérifier les sauvegardes à chaque étape avec l&#8217;outil <strong>pg_verifybackup </strong>car rien ne garantit qu&#8217;au moment de l&#8217;appel à <strong>pg_combinebackup</strong> les différents jeux de sauvegardes FULL et/ou incrémentales ne soient pas corrompus.</li> </ul> <p>&nbsp;</p> <ul> <li>Assurez vous d&#8217;être en mode &#8220;data_checksum&#8221; activé et ne pas changer de mode entre les jeux de backup. Le &#8220;backup manifest&#8221; s&#8217;appuie sur ce paramétrage pour valider les checksums de chaque fichier.</li> </ul> <p>&nbsp;</p> <ul> <li>Le mode TAR pour <strong>pg_basebackup</strong> n&#8217;est pas compatible pour les sauvegardes full et incrémentales même si celui ci est possible. Mais c&#8217;est à vous de détarer les fichiers &#8220;<strong>base.tar.gz</strong>&#8221; Et au moment de la restauration  avec &#8220;<strong>pg_combinebackup</strong>&#8220;, une possible corruption est rencontrée.</li> </ul> <pre class="brush: bash; title: ; notranslate">[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 &quot;/data/postgres/backup/pg_basebackup/PG17_all_tar/base/25284/25332&quot;, offset 122470400: wrote 380928 of 409600 pg_combinebackup: removing output directory &quot;/data/postgres/backup/pg_basebackup/PG17_all_tar&quot; </pre> <p>La compression a potentiellement ajoutée une corruption ne rendant pas possible l&#8217;opération de &#8220;merge&#8221; des données.</p> <p>&nbsp;</p> <ul> <li>La restauration PITR est possible bien entendu. N&#8217;oubliez pas de créer le &#8220;<strong>recovery.signal</strong>&#8221; dans $PGDATA et de définir dans le fichier &#8220;postgresql.conf&#8221; les quelques paramètres suivants <ul> <li><span style="color: #3366ff;">recovery_target_name </span></li> <li><span style="color: #3366ff;">recovery_target_time </span></li> <li><span style="color: #3366ff;">recovery_target_xid </span></li> <li><span style="color: #3366ff;">recovery_target_lsn </span></li> <li><span style="color: #808000;">recovery_target_inclusive = off ou on</span></li> <li><span style="color: #808000;">recovery_target_timeline = &#8216;latest&#8217; </span></li> <li><span style="color: #808000;">recovery_target_action = &#8216;pause&#8217; </span></li> </ul> </li> </ul> <p>&nbsp;</p> <p><img src="https://s.w.org/images/core/emoji/15.0.3/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p> <p>&nbsp;<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/postgresql-la-streaming-replication-en-12/" rel="bookmark" title="19 novembre 2019">PostgreSQL : la streaming replication en 12.</a> (Emmanuel RAMI) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-13-les-nouveautes-interessantes/" rel="bookmark" title="30 octobre 2020">PostgreSQL 13 : présentation</a> (Emmanuel RAMI) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/oracle-rds-effectuer-des-backup-rman-en-mode-paas/" rel="bookmark" title="25 juin 2019">Oracle RDS : effectuer des backup RMAN en mode PaaS.</a> (Emmanuel RAMI) [AWSNon classéOracle]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-comparatif-entre-barman-et-pgbackrest/" rel="bookmark" title="4 février 2020">PostgreSQL : Comparatif entre Barman et pgBackRest</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/haute-disponibilite-de-postgresql-avec-patroni/" rel="bookmark" title="2 février 2022">Haute disponibilité de PostgreSQL avec Patroni</a> (Ludovic AUGEREAU) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 3.574 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584&#038;title=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PostgreSQL%2017%20%3A%20des%20sauvegardes%20incr%C3%A9mentales%20avec%20pg_basebackup&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10584" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/postgresql-17-sauvegardes-incrementales/">PostgreSQL 17 : des sauvegardes incrémentales avec pg_basebackup</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>&#160; 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&#8230; <a href="https://blog.capdata.fr/index.php/postgresql-17-sauvegardes-incrementales/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/postgresql-17-sauvegardes-incrementales/">PostgreSQL 17 : des sauvegardes incrémentales avec pg_basebackup</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> @@ -105,7 +105,7 @@ PGO : la suite 2024-05-29T08:58:17Z tag:blog.capdata.fr,2024-05-29:/index.php/pgo-la-suite/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;title=PGO%20%3A%20la%20suite" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PGO%20%3A%20la%20suite&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>La gestion efficace des clusters PostgreSQL dans un environnement Kubernetes est un défi complexe auquel sont confrontées de nombreuses entreprises aujourd&#8217;hui. PGO offre une solution déclarative qui automatise la gestion des clusters PostgreSQL, simplifiant ainsi le déploiement, la mise à l&#8217;échelle et la gestion des bases de données PostgreSQL dans un environnement Kubernetes.</p> <p>Pour faire suite à l&#8217;article de David sur PGO et à la demande d&#8217;un de nos clients, j&#8217;ai réalisé une étude approfondie de plusieurs fonctionnalités de PGO.<br /> Cet article va faire un petit tour d&#8217;horizon des outils principaux inclus dans l&#8217;implémentation de PGO. Que ce soit pour la sauvegarde avec pgbackrest, pour la balance des connexion avec pgbouncer ou pour le monitoring avec prometheus, PGO ne manque pas d&#8217;utilitaire dont l&#8217;utilisation est facilitée par la solution tout embarqué.</p> <h3>Pgbackrest :</h3> <h4>Utilité :</h4> <p>PgBackRest est une solution de sauvegarde et de restauration pour les bases de données PostgreSQL qui propose plusieurs fonctionnalités, telles que la sauvegarde et la restauration parallèles, la compression, les sauvegardes complètes, différentielles et incrémentielles, la rotation des sauvegardes et l&#8217;expiration des archives, l&#8217;intégrité des sauvegardes, etc. Il prend en charge plusieurs référentiels, qui peuvent être situés localement ou à distance via TLS/SSH, ou être des stockages fournis par le cloud comme S3/GCS/Azure.<br /> L&#8217;architecture de pgbackrest pour PGO est la suivante :</p> <p><a href="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1.png"><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-10564" src="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1-300x168.png" alt="" width="300" height="168" srcset="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1-300x168.png 300w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image1.png 605w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p> <h4>Mise en place :</h4> <p>On peut imaginer plusieurs moyens de mettre en place le pgbackrest. Dans un premier temps, nous avons la sauvegarde classique en système de fichier, comme dans notre exemple sur le blog :</p> <h5>1) La sauvegarde sur volume persistant Kubernetes :</h5> <pre class="brush: yaml; title: ; notranslate"> - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>Ce type de sauvegarde utilise un volume persistant de Kubernetes pour recueillir nos sauvegardes et les garder.<br /> Une PersistentVolumeClaim (PVC) est une demande de stockage faite par un utilisateur. Elle est similaire à un Pod. Les Pods consomment des ressources de nœud et les PVC consomment des ressources de PV (PersistentVolume). Les Pods peuvent demander des niveaux spécifiques de ressources (CPU et mémoire). Les revendications peuvent demander une taille spécifique et des modes d&#8217;accès spécifiques (par exemple, elles peuvent être montées en ReadWriteOnce, ReadOnlyMany, ReadWriteMany, ou ReadWriteOncePod, voir AccessModes).</p> <h5>2) Le stockage pour S3 :</h5> <p>Pour pouvoir faire du stockage dans S3, il faut rajouter un fichier de configuration dans notre dossier de déploiement. Le fichier doit s’appeler s3.conf. Ce fichier contient les crédential de connexion à un AWS S3 :</p> <pre class="brush: bash; title: ; notranslate"> repo1-s3-key=$YOUR_AWS_S3_KEY repo1-s3-key-secret=$YOUR_AWS_S3_KEY_SECRET </pre> <p>Une fois que c’est configuré dans votre fichier, il ne reste plus qu’à modifier le postgresql.yaml, et configurer dans la partie backup :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-s3-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster1/repo1 repos: - name: repo1 s3: bucket: &quot;&lt;YOUR_AWS_S3_BUCKET_NAME&gt;&quot; endpoint: &quot;&lt;YOUR_AWS_S3_ENDPOINT&gt;&quot; region: &quot;&lt;YOUR_AWS_S3_REGION&gt;&quot; </pre> <p>Une fois configuré, et le job mis dans le cron, vous devriez voir apparaitre les sauvegardes sur le volume S3.</p> <h5>3) Le stockage GCS :</h5> <p>Comme pour Amazon S3 on peut sauvegarder nos backups dans Google Cloud Storage. Pour pouvoir le faire fonctionner il vous faut copier votre GCS key secret (qui est un fichier JSON) dans un gcs.conf que vous allez placer dans votre dossier Kustomize.<br /> Il vous suffit ensuite de modifier votre fichier postgres.yaml pour ajouter dans la partie backup la configuration pour une sauvegarde gcs :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-gcs-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster1/repo1 repos: - name: repo1 gcs: bucket: &quot;&lt;YOUR_GCS_BUCKET_NAME&gt;&quot; </pre> <p>Il ne vous reste plus qu’à regénérer vos pods, et votre sauvegarde arrivera directement dans votre Google Cloud Service.</p> <h5>4) Le stockage Azur Blob Storage :</h5> <p>Comme pour les deux points précédents, vous pouvez également stocker vos sauvegardes sur le blob storage d’Azure. Pour cela il vous faut créer un fichier dans votre kustomize, avec à l’intérieur la configuration pour votre point de sauvegarde Azure. Il vous faut l’appeler azure.conf et il devra contenir les lignes suivantes :</p> <pre class="brush: bash; title: ; notranslate"> repo1-azure-account=$YOUR_AZURE_ACCOUNT repo1-azure-key=$YOUR_AZURE_KEY </pre> <p>Il faut ensuite intégrer ces modifications dans votre fichier postgres.yaml :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-azure-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster/repo1 repos: - name: repo1 azure: container: &quot;&lt;YOUR_AZURE_CONTAINER&gt;&quot; </pre> <p>Bien sur rien ne vous interdit, et c’est même conseillé, de joindre plusieurs moyens de sauvegarde. Cela permet notamment de s’assurer une plus grande fiabilité du système de sauvegarde, en s’assurant qu’elles sont disponibles à plusieurs endroits.<br /> Une fois que vous avez décidé d’où vous allez stocker vos sauvegardes, et que vous l’avez configuré, il faut maintenant décider des différents paramètres de ces sauvegardes : la programmation, la rétention…</p> <h5>5) La programmation des sauvegardes :</h5> <p>Il faut savoir que par défaut, PGO sauvegarde automatiquement les WAL dans la méthode de sauvegarde que vous lui avez configuré. C’est donc une forme de sauvegarde en soit.<br /> Mais dans le cadre d’une récupération après incident majeur, il peut aussi être utilise d’avoir des sauvegardes full programmées. Pgbackrest, qui est l’outil utilisé par PGO permet de mettre en place trois types de sauvegarde : les incrémentales, les différentielles et les fulls.<br /> Chaque type de sauvegarde peut être programmée en suivant une notation identique à celle des crontab. Par exemple :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: repos: - name: repo1 schedules: full: &quot;0 1 * * 0&quot; differential: &quot;0 1 * * 1-6&quot; </pre> <p>Le fait d’implémenter ces planifications créera des CronJobs dans Kubernetes.</p> <h5>6) La rétention des backups :</h5> <p>Vous pouvez définir une rétention maximum pour vos backups sur le support de backup de votre choix. Une fois que cette rétention sera atteinte, pgbackrest fera le ménage tout seul des sauvegardes et des WAL qui lui sont reliées.<br /> Il y a deux types de rétentions que l’on peut définir : les rétentions « count » basées sur le nombre de backup que l’on souhaite garder et les rétentions « time » basées sur le nombre de jours ou vous souhaitez garder votre sauvegarde.</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: global: repo1-retention-full: &quot;14&quot; repo1-retention-full-type: time </pre> <h5>7) La sauvegarde unique :</h5> <p>Si dans le cadre d’un besoin particuliers, une grosse modification ou une migration par exemple, vous avez besoin de prendre une sauvegarde immédiate sans forcément attendre que le cron n’arrive, vous pouvez le faire.<br /> Pour la configuration de cette sauvegarde, il faudra l’annoter comme « manuelle » :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: manual: repoName: repo1 options: - --type=full </pre> <p>Il vous faudra ensuite déclencher cette sauvegarde avec une commande manuelle. Dans le cadre de notre cluster exemple pgcluster1 :<br /> kubectl annotate -n postgres-operator postgrescluster pgcluster1 \ postgres-operator.crunchydata.com/pgbackrest-backup=&#8221;$(date)&#8221;</p> <h5>8) Faire un clone à partir d’un repo :</h5> <p>Quand on a configuré un repo sur notre instance primaire, on peut facilement créer un clone de notre instance à l’aide de notre sauvegarde. Ainsi, on créer un tout nouveau Pods à partir des informations stockées à propos du pod que l’on possède déjà. Ici, nous allons créer un nouveau pod à partir de notre pod pgcluster1 :</p> <pre class="brush: yaml; title: ; notranslate"> apiVersion: postgres-operator.crunchydata.com/v1beta1 kind: PostgresCluster metadata: name: pgcluster2 spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0 postgresVersion: 16 instances: - dataVolumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 repos: - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>Ici on peut noter entre autres la partie spec de la configuration, qui est le morceau de yaml nous permettant de dire qu’on s’appuie sur le cluster existant pour créer un clone indépendant :</p> <pre class="brush: yaml; title: ; notranslate"> spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 </pre> <h5>9) Point in Time Recovery :</h5> <p>De la même façon, si l’on veut faire une restauration PITR, nous allons remplir la balise spec de notre yaml. Attention cependant, pour faire une restauration PITR, nous avons besoin de posséder encore la sauvegarde. On ne peut pas faire une restauration PITR sur une sauvegarde lointaine qu’on ne possèderait plus. Imaginons que je souhaite repartir d’une sauvegarde datant d’hier soir à 20h30 de mon instance pgcluster1 sur mon instance pgcluster2, la configuration serait la suivante :</p> <pre class="brush: yaml; title: ; notranslate"> apiVersion: postgres-operator.crunchydata.com/v1beta1 kind: PostgresCluster metadata: name: pgcluster2 spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 options: - --type=time - --target=&quot;2024-04-09 20:30:00-00&quot; image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0 postgresVersion: 16 instances: - dataVolumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 repos: - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>La partie qui nous intéresse ici est la partie spec, ou nous avons rajouter un type de restauration (ici time) et une heure target. Cela indique à pgbackrest qu’il doit aller chercher tous les fichiers de sauvegarde et WAL sur notre point de sauvegarde repo1 venant de l’instance pgcluster1 pour les réappliquer sur notre nouveau cluster pgcluster2.<br /> Vous pouvez également vouloir réaliser une restauration In Place, c’est-à-dire écraser l’instance présente pour la remplacer par la restauration. Auquel cas, plutôt que de préciser comment s’appellera notre nouveau cluster, il faut alors passer par la balise restore :</p> <pre class="brush: yaml; title: ; notranslate"> spec: backups: pgbackrest: restore: enabled: true repoName: repo1 options: - --type=time - --target=&quot;2024-04-09 20:30:00-00&quot; </pre> <p>Ici, comme précédemment, nous restaurons à l’heure de 20 :30 hier soir, et cela sur notre propre instance. Ne reste plus qu’à lancer la restauration :</p> <pre class="brush: bash; title: ; notranslate">kubectl annotate -n postgres-operator postgrescluster pgcluster1 --overwrite \ postgres-operator.crunchydata.com/pgbackrest-restore=&quot;$(date)&quot;</pre> <p>A noter qu’il ne faut pas oublier de désactiver ensuite le restore en le passant à false si vous ne souhaitez pas qu’il soit de nouveau écrasé au prochain changement de configuration.</p> <h5>10) Restaurer une base de données spécifique :</h5> <p>Si votre besoin est de restaurer une base de données spécifique plutôt que l’intégralité de l’instance, vous pouvez le préciser dans les paramètres de votre restauration.<br /> Attention cependant, ce n’est pas une restauration comme le serais un pg_dump. Ici si vous restaurez simplement une seule base de données et pas le reste du cluster, les autres bases que vous n’avez pas choisit de restaurer deviendront inaccessibles.<br /> Si nous voulons restaurer une base de données, et uniquement elle, voici la procédure :</p> <pre class="brush: yaml; title: ; notranslate"> spec: backups: pgbackrest: restore: enabled: true repoName: repo1 options: - --db-include=capdata </pre> <p>Ici, on ne restaurera que la base de données capdata, et aucunes autres bases à partir de notre repo1.</p> <h3>PgBouncer :</h3> <h4>Utilité :</h4> <p>PgBouncer est un pooler de connexion pour PostgreSQL. Un pooler de connexion permet de maintenir ouvertes des sessions entre lui-même et le serveur, ce qui rend plus rapide l&#8217;ouverture de sessions depuis les clients, une application Web par exemple.<br /> PgBouncer permet aussi de mutualiser les sessions dans le serveur, économisant ainsi des ressources. PgBouncer propose plusieurs modes de partage : par requête (default), par transaction ou par session.</p> <h4>Mise en place :</h4> <p>Pour ajouter un bouncer à notre configuration c’est une réalité très simple. Il suffit d’ajouter dans notre fichier postgres.yaml la rubrique proxy :</p> <pre class="brush: yaml; title: ; notranslate"> proxy: pgBouncer: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.21-3 </pre> <p>Une fois que vous avez rajouté cela dans la configuration, il n’y a plus qu’à appliquer celle-ci :</p> <pre class="brush: bash; title: ; notranslate"> kubectl apply -k kustomize/keycloak </pre> <p>Quand PGO créé un nouveau connexion pooler sur notre instance déployée, il modifier le fichier secrets de l’utilisateur.<br /> On voit que plusieurs champs qui concerne pg_bouncer sont apparus. Ils constituent les informations qui vont vous permettre de vous connecter sur votre bouncer nouvellement créé :</p> <pre class="brush: yaml; title: ; notranslate"> { &quot;apiVersion&quot;: &quot;v1&quot;, &quot;data&quot;: { &quot;dbname&quot;: &quot;cGdjbHVzdGVyMQ==&quot;, &quot;host&quot;: &quot;cGdjbHVzdGVyMS1wcmltYXJ5LnBvc3RncmVzLW9wZXJhdG9yLnN2Yw==&quot;, &quot;jdbc-uri&quot;: &quot;amRiYzpwb3N0Z3Jlc3FsOi8vcGdjbHVzdGVyMS1wcmltYXJ5LnBvc3RncmVzLW9wZXJhdG9yLnN2Yzo1NDMyL3BnY2x1c3RlcjE/cGFzc3dvcmQ9NXNSaSUzRCU1QmZZbSUzQ2lSSGslMkElNUIlM0VuWGhqaiU3Q1EmdXNlcj1wZ2NsdXN0ZXIx&quot;, &quot;password&quot;: &quot;NXNSaT1bZlltPGlSSGsqWz5uWGhqanxR&quot;, &quot;pgbouncer-host&quot;: &quot;cGdjbHVzdGVyMS1wZ2JvdW5jZXIucG9zdGdyZXMtb3BlcmF0b3Iuc3Zj&quot;, &quot;pgbouncer-jdbc-uri&quot;: &quot;amRiYzpwb3N0Z3Jlc3FsOi8vcGdjbHVzdGVyMS1wZ2JvdW5jZXIucG9zdGdyZXMtb3BlcmF0b3Iuc3ZjOjU0MzIvcGdjbHVzdGVyMT9wYXNzd29yZD01c1JpJTNEJTVCZlltJTNDaVJIayUyQSU1QiUzRW5YaGpqJTdDUSZwcmVwYXJlVGhyZXNob2xkPTAmdXNlcj1wZ2NsdXN0ZXIx&quot;, &quot;pgbouncer-port&quot;: &quot;NTQzMg==&quot;, &quot;pgbouncer-uri&quot;: &quot;cG9zdGdyZXNxbDovL3BnY2x1c3RlcjE6NXNSaT0lNUJmWW0lM0NpUkhrJTJBJTVCJTNFblhoamolN0NRQHBnY2x1c3RlcjEtcGdib3VuY2VyLnBvc3RncmVzLW9wZXJhdG9yLnN2Yzo1NDMyL3BnY2x1c3RlcjE=&quot;, &quot;port&quot;: &quot;NTQzMg==&quot;, &quot;uri&quot;: &quot;cG9zdGdyZXNxbDovL3BnY2x1c3RlcjE6NXNSaT0lNUJmWW0lM0NpUkhrJTJBJTVCJTNFblhoamolN0NRQHBnY2x1c3RlcjEtcHJpbWFyeS5wb3N0Z3Jlcy1vcGVyYXRvci5zdmM6NTQzMi9wZ2NsdXN0ZXIx&quot;, &quot;user&quot;: &quot;cGdjbHVzdGVyMQ==&quot;, &quot;verifier&quot;: &quot;U0NSQU0tU0hBLTI1NiQ0MDk2OlgyQ3NQRU1FZjh3QkVlc05McDFJTkE9PSRKcDhKakl5Q0o1ZEpFRVhia1ptUERTNE5rR3d0V00rczdrMElsQmx0YkpvPTpEaHg3VzNCOE5vNDRYSHJ1Qm1RdENMQW9jNEtnSUZQa2dIeStUMkVWUUowPQ==&quot; }, &quot;kind&quot;: &quot;Secret&quot;, &quot;metadata&quot;: { &quot;creationTimestamp&quot;: &quot;2024-04-09T16:37:36Z&quot;, &quot;labels&quot;: { &quot;postgres-operator.crunchydata.com/cluster&quot;: &quot;pgcluster1&quot;, &quot;postgres-operator.crunchydata.com/pguser&quot;: &quot;pgcluster1&quot;, &quot;postgres-operator.crunchydata.com/role&quot;: &quot;pguser&quot; }, &quot;name&quot;: &quot;pgcluster1-pguser-pgcluster1&quot;, &quot;namespace&quot;: &quot;postgres-operator&quot;, &quot;ownerReferences&quot;: [ { &quot;apiVersion&quot;: &quot;postgres-operator.crunchydata.com/v1beta1&quot;, &quot;blockOwnerDeletion&quot;: true, &quot;controller&quot;: true, &quot;kind&quot;: &quot;PostgresCluster&quot;, &quot;name&quot;: &quot;pgcluster1&quot;, &quot;uid&quot;: &quot;7260b882-116f-4b02-b51a-18d4fe3a8038&quot; } ], &quot;resourceVersion&quot;: &quot;9495&quot;, &quot;uid&quot;: &quot;1fbdf1d2-48ea-4a45-b7d6-01248317dbee&quot; }, &quot;type&quot;: &quot;Opaque&quot; } </pre> <p>Pour se connecter à notre pgbouncer, il suffit d’utiliser les informations fournies par le fichier de secret à la place de nos infos de connexion habituelles, et cela nous permet d’accéder directement au bouncer et non plus à l’instance elle-même.</p> <p>Cette connexion peut être facilement modifiée en utilisant la documentation de pgbouncer afin de pouvoir configurer à notre guise notre pgbouncer. Un exemple de configuration qu’on pourrais rencontrer serait :</p> <pre class="brush: yaml; title: ; notranslate"> proxy: pgBouncer: image: {{.Values.image.pgBouncer }} config: global: default_pool_size: &quot;100&quot; max_client_conn: &quot;10000&quot; pool_mode: transaction </pre> <p>Pour cet exemple on voit qu’on a définit un nombre de client maximum, la taille du pool à 100 et un mode transaction pour notre pool.</p> <h3>PGO et Prometheus</h3> <h4>Utilité :</h4> <p>Prometheus est une trousse à outils de surveillance et d&#8217;alerte des systèmes en open source.<br /> Prometheus collecte et stocke ses métriques sous forme de données de séries temporelles, c&#8217;est-à-dire que les informations de métriques sont stockées avec le timestamp auquel elles ont été enregistrées, aux côtés de paires clé-valeur optionnelles appelées labels.<br /> &#8211; Un modèle de données multidimensionnel avec des données de séries temporelles identifiées par le nom de la métrique et des paires clé-valeur<br /> &#8211; PromQL, un langage de requête flexible pour exploiter cette dimensionnalité<br /> &#8211; Aucune dépendance sur le stockage distribué ; les nœuds de serveur individuels sont autonomes<br /> &#8211; La collecte de séries temporelles se fait via un modèle de tirage sur HTTP<br /> &#8211; La poussée de séries temporelles est prise en charge via une passerelle intermédiaire<br /> &#8211; Les cibles sont découvertes via la découverte de service ou la configuration statique<br /> &#8211; Prise en charge de plusieurs modes de graphiques et de tableaux de bord</p> <h4>Mise en place :</h4> <p>Pour pouvoir mettre en place une surveillance pour notre cluster, il est plus simple de télécharger et compléter le modèle fournit dans les exemples de pgo.<br /> Ainsi, on peut récupérer les exemples à l’aide de git :</p> <pre class="brush: bash; title: ; notranslate"> YOUR_GITHUB_UN=&quot;$YOUR_GITHUB_USERNAME&quot; git clone --depth 1 &quot;git@github.com:${YOUR_GITHUB_UN}/postgres-operator-examples.git&quot; cd postgres-operator-examples </pre> <p>Les différentes configurations se trouvent dans le dossier kustomize/monitoring.<br /> Pour activer le monitoring de notre instance, il faut ajouter la balise monitoring à notre fichier postgres.yaml :</p> <pre class="brush: yaml; title: ; notranslate"> monitoring: pgmonitor: exporter: image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-5.5.1-0 </pre> <p>Une fois notre configuration modifiée, on l’applique afin que PGO détecte les changements et configure tout seul l’exporter pour qu’il puisse se connecter à nos bases de données et récupérer les métriques.</p> <pre class="brush: bash; title: ; notranslate"> kubectl apply -k kustomize/postgres </pre> <p>Il faut ensuite appliquer la configuration de base de pgmonitor pour qu’il créé lui-même les fichiers de configuration pour prometheus (il le fera en même temps pour Grafana et Alertmanager qui sont deux autres outils de surveillance). Pour cela on applique le kustomize présent dans le dossier monitoring :</p> <pre class="brush: bash; title: ; notranslate"> $kubectl apply -k kustomize\postgres postgrescluster.postgres-operator.crunchydata.com/pgcluster1 configured $kubectl apply -k kustomize\monitoring serviceaccount/alertmanager created serviceaccount/grafana created serviceaccount/prometheus created clusterrole.rbac.authorization.k8s.io/prometheus created clusterrolebinding.rbac.authorization.k8s.io/prometheus created configmap/alert-rules-config created configmap/alertmanager-config created configmap/crunchy-prometheus created configmap/grafana-dashboards created configmap/grafana-datasources created secret/grafana-admin created service/crunchy-alertmanager created service/crunchy-grafana created service/crunchy-prometheus created persistentvolumeclaim/alertmanagerdata created persistentvolumeclaim/grafanadata created persistentvolumeclaim/prometheusdata created deployment.apps/crunchy-alertmanager created deployment.apps/crunchy-grafana created deployment.apps/crunchy-prometheus created </pre> <p>Nos services ont été correctement déployés, il ne nous reste plus qu’à utiliser celui qui nous intéresse, ici service/crunchy-prometheus et lui indiquer de commencer à envoyer les informations sur notre prometheus :</p> <pre class="brush: bash; title: ; notranslate"> $kubectl -n postgres-operator port-forward service/crunchy-prometheus 9090:9090 Forwarding from 127.0.0.1:9090 -&gt; 9090 Forwarding from [::1]:9090 -&gt; 9090 Handling connection for 9090 Handling connection for 9090 </pre> <p>Afin d’accéder à notre service prometheus, il ne nous reste plus qu’à se connecter avec l’adresse de notre machine, sur le port 9090 préalablement ouvert, pour voir apparaitre le dashboard de prometheus :</p> <p><a href="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2.jpg"><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-10567" src="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-300x66.jpg" alt="" width="300" height="66" srcset="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-300x66.jpg 300w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-1024x226.jpg 1024w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-768x170.jpg 768w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2.jpg 1386w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p> <h3>PGO Client :</h3> <h4>Utilité :</h4> <p>Pour pouvoir gérer plus facilement le cluster créé par PGO, CrunchyData à développé une surcouche à kubectl qui permet de faciliter les commandes que nous pouvons réaliser sur le cluster.<br /> Cela permet de ne pas avoir à taper les longues lignes de commandes qui permettent par exemple de démarrer les sauvegardes unitaires.</p> <h4>Mise en place :</h4> <p>Pour pouvoir installer cette surcouche, il faut télécharger la version qui correspond au système d’exploitation à partir du GIT de pgo client :</p> <pre class="brush: bash; title: ; notranslate"> # wget https://github.com/CrunchyData/postgres-operator-client/releases/download/v0.4.1/kubectl-pgo-linux-arm64 --2024-04-11 12:07:45-- https://github.com/CrunchyData/postgres-operator-client/releases/download/v0.4.1/kubectl-pgo-linux-arm64 Resolving github.com (github.com)... 140.82.121.4 Connecting to github.com (github.com)|140.82.121.4|:443... connected. HTTP request sent, awaiting response... 302 Found Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.110.133, ... Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.109.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 47895849 (46M) [application/octet-stream] Saving to: ‘kubectl-pgo-linux-arm64’ kubectl-pgo-linux-arm64 100%[========================================================================================================================================&gt;] 45.68M --.-KB/s in 0.1s 2024-04-11 12:07:45 (373 MB/s) - ‘kubectl-pgo-linux-arm64’ saved [47895849/47895849] </pre> <p>On renome le fichier téléchargé en kubectl-pgo et on le déplace dans nos bin pour pouvoir les utiliser :</p> <pre class="brush: bash; title: ; notranslate"> # mv kubectl-pgo-linux-arm64 kubectl-pgo # sudo mv kubectl-pgo /usr/local/bin/kubectl-pgo # sudo chmod +x /usr/local/bin/kubectl-pgo Une fois que ces actions sont réalisées, on peut tester le fonctionnement : # kubectl pgo version Client Version: v0.4.1 Operator Version: v5.5.1 </pre> <p>Les commandes disponibles avec cette extension sont les suivantes :<br /> &#8211; backup : Backup cluster<br /> &#8211; create : Create a resource<br /> &#8211; delete : Delete a resource<br /> &#8211; help : Help about any command<br /> &#8211; restore : Restore cluster<br /> &#8211; show Show : PostgresCluster details<br /> &#8211; start : Start cluster<br /> &#8211; stop : Stop cluster<br /> &#8211; support : Crunchy Support commands for PGO<br /> &#8211; version : PGO client<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pgo-operateurs-kubernetes-pour-postgresql-la-suite/" rel="bookmark" title="6 juin 2023">PGO : opérateurs kubernetes pour PostgreSQL, la suite !</a> (David Baffaleuf) [ContainerDevopsPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/kubegres-loperateur-kubernetes-cle-en-main-pour-postgresql/" rel="bookmark" title="26 avril 2023">Kubegres : l&#8217;opérateur Kubernetes clé en main pour PostgreSQL</a> (David Baffaleuf) [ContainerDevopsPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-sur-la-solution-kubernetes-locale-minikube/" rel="bookmark" title="29 mars 2023">PostgreSQL sur la solution Kubernetes locale Minikube</a> (Emmanuel RAMI) [ContainerPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-comparatif-entre-barman-et-pgbackrest/" rel="bookmark" title="4 février 2020">PostgreSQL : Comparatif entre Barman et pgBackRest</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/sauvegardes-sql-server-dans-un-azure-blob-storage/" rel="bookmark" title="21 août 2018">Sauvegardes SQL Server dans un Azure Blob Storage</a> (Capdata team) [AzureSQL Server]</li> </ul> <p><!-- Similar Posts took 2.212 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;title=PGO%20%3A%20la%20suite" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PGO%20%3A%20la%20suite&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pgo-la-suite/">PGO : la suite</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;title=PGO%20%3A%20la%20suite" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PGO%20%3A%20la%20suite&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>La gestion efficace des clusters PostgreSQL dans un environnement Kubernetes est un défi complexe auquel sont confrontées de nombreuses entreprises aujourd&#8217;hui. PGO offre une solution déclarative qui automatise la gestion des clusters PostgreSQL, simplifiant ainsi le déploiement, la mise à l&#8217;échelle et la gestion des bases de données PostgreSQL dans un environnement Kubernetes.</p> <p>Pour faire suite à l&#8217;article de David sur PGO et à la demande d&#8217;un de nos clients, j&#8217;ai réalisé une étude approfondie de plusieurs fonctionnalités de PGO.<br /> Cet article va faire un petit tour d&#8217;horizon des outils principaux inclus dans l&#8217;implémentation de PGO. Que ce soit pour la sauvegarde avec pgbackrest, pour la balance des connexion avec pgbouncer ou pour le monitoring avec prometheus, PGO ne manque pas d&#8217;utilitaire dont l&#8217;utilisation est facilitée par la solution tout embarqué.</p> <h3>Pgbackrest :</h3> <h4>Utilité :</h4> <p>PgBackRest est une solution de sauvegarde et de restauration pour les bases de données PostgreSQL qui propose plusieurs fonctionnalités, telles que la sauvegarde et la restauration parallèles, la compression, les sauvegardes complètes, différentielles et incrémentielles, la rotation des sauvegardes et l&#8217;expiration des archives, l&#8217;intégrité des sauvegardes, etc. Il prend en charge plusieurs référentiels, qui peuvent être situés localement ou à distance via TLS/SSH, ou être des stockages fournis par le cloud comme S3/GCS/Azure.<br /> L&#8217;architecture de pgbackrest pour PGO est la suivante :</p> <p><a href="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1.png"><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-10564" src="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1-300x168.png" alt="" width="300" height="168" srcset="https://blog.capdata.fr/wp-content/uploads/2024/05/Image1-300x168.png 300w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image1.png 605w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p> <h4>Mise en place :</h4> <p>On peut imaginer plusieurs moyens de mettre en place le pgbackrest. Dans un premier temps, nous avons la sauvegarde classique en système de fichier, comme dans notre exemple sur le blog :</p> <h5>1) La sauvegarde sur volume persistant Kubernetes :</h5> <pre class="brush: yaml; title: ; notranslate"> - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>Ce type de sauvegarde utilise un volume persistant de Kubernetes pour recueillir nos sauvegardes et les garder.<br /> Une PersistentVolumeClaim (PVC) est une demande de stockage faite par un utilisateur. Elle est similaire à un Pod. Les Pods consomment des ressources de nœud et les PVC consomment des ressources de PV (PersistentVolume). Les Pods peuvent demander des niveaux spécifiques de ressources (CPU et mémoire). Les revendications peuvent demander une taille spécifique et des modes d&#8217;accès spécifiques (par exemple, elles peuvent être montées en ReadWriteOnce, ReadOnlyMany, ReadWriteMany, ou ReadWriteOncePod, voir AccessModes).</p> <h5>2) Le stockage pour S3 :</h5> <p>Pour pouvoir faire du stockage dans S3, il faut rajouter un fichier de configuration dans notre dossier de déploiement. Le fichier doit s’appeler s3.conf. Ce fichier contient les crédential de connexion à un AWS S3 :</p> <pre class="brush: bash; title: ; notranslate"> repo1-s3-key=$YOUR_AWS_S3_KEY repo1-s3-key-secret=$YOUR_AWS_S3_KEY_SECRET </pre> <p>Une fois que c’est configuré dans votre fichier, il ne reste plus qu’à modifier le postgresql.yaml, et configurer dans la partie backup :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-s3-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster1/repo1 repos: - name: repo1 s3: bucket: &quot;&lt;YOUR_AWS_S3_BUCKET_NAME&gt;&quot; endpoint: &quot;&lt;YOUR_AWS_S3_ENDPOINT&gt;&quot; region: &quot;&lt;YOUR_AWS_S3_REGION&gt;&quot; </pre> <p>Une fois configuré, et le job mis dans le cron, vous devriez voir apparaitre les sauvegardes sur le volume S3.</p> <h5>3) Le stockage GCS :</h5> <p>Comme pour Amazon S3 on peut sauvegarder nos backups dans Google Cloud Storage. Pour pouvoir le faire fonctionner il vous faut copier votre GCS key secret (qui est un fichier JSON) dans un gcs.conf que vous allez placer dans votre dossier Kustomize.<br /> Il vous suffit ensuite de modifier votre fichier postgres.yaml pour ajouter dans la partie backup la configuration pour une sauvegarde gcs :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-gcs-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster1/repo1 repos: - name: repo1 gcs: bucket: &quot;&lt;YOUR_GCS_BUCKET_NAME&gt;&quot; </pre> <p>Il ne vous reste plus qu’à regénérer vos pods, et votre sauvegarde arrivera directement dans votre Google Cloud Service.</p> <h5>4) Le stockage Azur Blob Storage :</h5> <p>Comme pour les deux points précédents, vous pouvez également stocker vos sauvegardes sur le blob storage d’Azure. Pour cela il vous faut créer un fichier dans votre kustomize, avec à l’intérieur la configuration pour votre point de sauvegarde Azure. Il vous faut l’appeler azure.conf et il devra contenir les lignes suivantes :</p> <pre class="brush: bash; title: ; notranslate"> repo1-azure-account=$YOUR_AZURE_ACCOUNT repo1-azure-key=$YOUR_AZURE_KEY </pre> <p>Il faut ensuite intégrer ces modifications dans votre fichier postgres.yaml :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 configuration: - secret: name: pgo-azure-creds global: repo1-path: /pgbackrest/postgres-operator/pgcluster/repo1 repos: - name: repo1 azure: container: &quot;&lt;YOUR_AZURE_CONTAINER&gt;&quot; </pre> <p>Bien sur rien ne vous interdit, et c’est même conseillé, de joindre plusieurs moyens de sauvegarde. Cela permet notamment de s’assurer une plus grande fiabilité du système de sauvegarde, en s’assurant qu’elles sont disponibles à plusieurs endroits.<br /> Une fois que vous avez décidé d’où vous allez stocker vos sauvegardes, et que vous l’avez configuré, il faut maintenant décider des différents paramètres de ces sauvegardes : la programmation, la rétention…</p> <h5>5) La programmation des sauvegardes :</h5> <p>Il faut savoir que par défaut, PGO sauvegarde automatiquement les WAL dans la méthode de sauvegarde que vous lui avez configuré. C’est donc une forme de sauvegarde en soit.<br /> Mais dans le cadre d’une récupération après incident majeur, il peut aussi être utilise d’avoir des sauvegardes full programmées. Pgbackrest, qui est l’outil utilisé par PGO permet de mettre en place trois types de sauvegarde : les incrémentales, les différentielles et les fulls.<br /> Chaque type de sauvegarde peut être programmée en suivant une notation identique à celle des crontab. Par exemple :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: repos: - name: repo1 schedules: full: &quot;0 1 * * 0&quot; differential: &quot;0 1 * * 1-6&quot; </pre> <p>Le fait d’implémenter ces planifications créera des CronJobs dans Kubernetes.</p> <h5>6) La rétention des backups :</h5> <p>Vous pouvez définir une rétention maximum pour vos backups sur le support de backup de votre choix. Une fois que cette rétention sera atteinte, pgbackrest fera le ménage tout seul des sauvegardes et des WAL qui lui sont reliées.<br /> Il y a deux types de rétentions que l’on peut définir : les rétentions « count » basées sur le nombre de backup que l’on souhaite garder et les rétentions « time » basées sur le nombre de jours ou vous souhaitez garder votre sauvegarde.</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: global: repo1-retention-full: &quot;14&quot; repo1-retention-full-type: time </pre> <h5>7) La sauvegarde unique :</h5> <p>Si dans le cadre d’un besoin particuliers, une grosse modification ou une migration par exemple, vous avez besoin de prendre une sauvegarde immédiate sans forcément attendre que le cron n’arrive, vous pouvez le faire.<br /> Pour la configuration de cette sauvegarde, il faudra l’annoter comme « manuelle » :</p> <pre class="brush: yaml; title: ; notranslate"> backups: pgbackrest: manual: repoName: repo1 options: - --type=full </pre> <p>Il vous faudra ensuite déclencher cette sauvegarde avec une commande manuelle. Dans le cadre de notre cluster exemple pgcluster1 :<br /> kubectl annotate -n postgres-operator postgrescluster pgcluster1 \ postgres-operator.crunchydata.com/pgbackrest-backup=&#8221;$(date)&#8221;</p> <h5>8) Faire un clone à partir d’un repo :</h5> <p>Quand on a configuré un repo sur notre instance primaire, on peut facilement créer un clone de notre instance à l’aide de notre sauvegarde. Ainsi, on créer un tout nouveau Pods à partir des informations stockées à propos du pod que l’on possède déjà. Ici, nous allons créer un nouveau pod à partir de notre pod pgcluster1 :</p> <pre class="brush: yaml; title: ; notranslate"> apiVersion: postgres-operator.crunchydata.com/v1beta1 kind: PostgresCluster metadata: name: pgcluster2 spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0 postgresVersion: 16 instances: - dataVolumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 repos: - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>Ici on peut noter entre autres la partie spec de la configuration, qui est le morceau de yaml nous permettant de dire qu’on s’appuie sur le cluster existant pour créer un clone indépendant :</p> <pre class="brush: yaml; title: ; notranslate"> spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 </pre> <h5>9) Point in Time Recovery :</h5> <p>De la même façon, si l’on veut faire une restauration PITR, nous allons remplir la balise spec de notre yaml. Attention cependant, pour faire une restauration PITR, nous avons besoin de posséder encore la sauvegarde. On ne peut pas faire une restauration PITR sur une sauvegarde lointaine qu’on ne possèderait plus. Imaginons que je souhaite repartir d’une sauvegarde datant d’hier soir à 20h30 de mon instance pgcluster1 sur mon instance pgcluster2, la configuration serait la suivante :</p> <pre class="brush: yaml; title: ; notranslate"> apiVersion: postgres-operator.crunchydata.com/v1beta1 kind: PostgresCluster metadata: name: pgcluster2 spec: dataSource: postgresCluster: clusterName: pgcluster1 repoName: repo1 options: - --type=time - --target=&quot;2024-04-09 20:30:00-00&quot; image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-16.2-0 postgresVersion: 16 instances: - dataVolumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi backups: pgbackrest: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbackrest:ubi8-2.49-0 repos: - name: repo1 volume: volumeClaimSpec: accessModes: - &quot;ReadWriteOnce&quot; resources: requests: storage: 1Gi </pre> <p>La partie qui nous intéresse ici est la partie spec, ou nous avons rajouter un type de restauration (ici time) et une heure target. Cela indique à pgbackrest qu’il doit aller chercher tous les fichiers de sauvegarde et WAL sur notre point de sauvegarde repo1 venant de l’instance pgcluster1 pour les réappliquer sur notre nouveau cluster pgcluster2.<br /> Vous pouvez également vouloir réaliser une restauration In Place, c’est-à-dire écraser l’instance présente pour la remplacer par la restauration. Auquel cas, plutôt que de préciser comment s’appellera notre nouveau cluster, il faut alors passer par la balise restore :</p> <pre class="brush: yaml; title: ; notranslate"> spec: backups: pgbackrest: restore: enabled: true repoName: repo1 options: - --type=time - --target=&quot;2024-04-09 20:30:00-00&quot; </pre> <p>Ici, comme précédemment, nous restaurons à l’heure de 20 :30 hier soir, et cela sur notre propre instance. Ne reste plus qu’à lancer la restauration :</p> <pre class="brush: bash; title: ; notranslate">kubectl annotate -n postgres-operator postgrescluster pgcluster1 --overwrite \ postgres-operator.crunchydata.com/pgbackrest-restore=&quot;$(date)&quot;</pre> <p>A noter qu’il ne faut pas oublier de désactiver ensuite le restore en le passant à false si vous ne souhaitez pas qu’il soit de nouveau écrasé au prochain changement de configuration.</p> <h5>10) Restaurer une base de données spécifique :</h5> <p>Si votre besoin est de restaurer une base de données spécifique plutôt que l’intégralité de l’instance, vous pouvez le préciser dans les paramètres de votre restauration.<br /> Attention cependant, ce n’est pas une restauration comme le serais un pg_dump. Ici si vous restaurez simplement une seule base de données et pas le reste du cluster, les autres bases que vous n’avez pas choisit de restaurer deviendront inaccessibles.<br /> Si nous voulons restaurer une base de données, et uniquement elle, voici la procédure :</p> <pre class="brush: yaml; title: ; notranslate"> spec: backups: pgbackrest: restore: enabled: true repoName: repo1 options: - --db-include=capdata </pre> <p>Ici, on ne restaurera que la base de données capdata, et aucunes autres bases à partir de notre repo1.</p> <h3>PgBouncer :</h3> <h4>Utilité :</h4> <p>PgBouncer est un pooler de connexion pour PostgreSQL. Un pooler de connexion permet de maintenir ouvertes des sessions entre lui-même et le serveur, ce qui rend plus rapide l&#8217;ouverture de sessions depuis les clients, une application Web par exemple.<br /> PgBouncer permet aussi de mutualiser les sessions dans le serveur, économisant ainsi des ressources. PgBouncer propose plusieurs modes de partage : par requête (default), par transaction ou par session.</p> <h4>Mise en place :</h4> <p>Pour ajouter un bouncer à notre configuration c’est une réalité très simple. Il suffit d’ajouter dans notre fichier postgres.yaml la rubrique proxy :</p> <pre class="brush: yaml; title: ; notranslate"> proxy: pgBouncer: image: registry.developers.crunchydata.com/crunchydata/crunchy-pgbouncer:ubi8-1.21-3 </pre> <p>Une fois que vous avez rajouté cela dans la configuration, il n’y a plus qu’à appliquer celle-ci :</p> <pre class="brush: bash; title: ; notranslate"> kubectl apply -k kustomize/keycloak </pre> <p>Quand PGO créé un nouveau connexion pooler sur notre instance déployée, il modifier le fichier secrets de l’utilisateur.<br /> On voit que plusieurs champs qui concerne pg_bouncer sont apparus. Ils constituent les informations qui vont vous permettre de vous connecter sur votre bouncer nouvellement créé :</p> <pre class="brush: yaml; title: ; notranslate"> { &quot;apiVersion&quot;: &quot;v1&quot;, &quot;data&quot;: { &quot;dbname&quot;: &quot;cGdjbHVzdGVyMQ==&quot;, &quot;host&quot;: &quot;cGdjbHVzdGVyMS1wcmltYXJ5LnBvc3RncmVzLW9wZXJhdG9yLnN2Yw==&quot;, &quot;jdbc-uri&quot;: &quot;amRiYzpwb3N0Z3Jlc3FsOi8vcGdjbHVzdGVyMS1wcmltYXJ5LnBvc3RncmVzLW9wZXJhdG9yLnN2Yzo1NDMyL3BnY2x1c3RlcjE/cGFzc3dvcmQ9NXNSaSUzRCU1QmZZbSUzQ2lSSGslMkElNUIlM0VuWGhqaiU3Q1EmdXNlcj1wZ2NsdXN0ZXIx&quot;, &quot;password&quot;: &quot;NXNSaT1bZlltPGlSSGsqWz5uWGhqanxR&quot;, &quot;pgbouncer-host&quot;: &quot;cGdjbHVzdGVyMS1wZ2JvdW5jZXIucG9zdGdyZXMtb3BlcmF0b3Iuc3Zj&quot;, &quot;pgbouncer-jdbc-uri&quot;: &quot;amRiYzpwb3N0Z3Jlc3FsOi8vcGdjbHVzdGVyMS1wZ2JvdW5jZXIucG9zdGdyZXMtb3BlcmF0b3Iuc3ZjOjU0MzIvcGdjbHVzdGVyMT9wYXNzd29yZD01c1JpJTNEJTVCZlltJTNDaVJIayUyQSU1QiUzRW5YaGpqJTdDUSZwcmVwYXJlVGhyZXNob2xkPTAmdXNlcj1wZ2NsdXN0ZXIx&quot;, &quot;pgbouncer-port&quot;: &quot;NTQzMg==&quot;, &quot;pgbouncer-uri&quot;: &quot;cG9zdGdyZXNxbDovL3BnY2x1c3RlcjE6NXNSaT0lNUJmWW0lM0NpUkhrJTJBJTVCJTNFblhoamolN0NRQHBnY2x1c3RlcjEtcGdib3VuY2VyLnBvc3RncmVzLW9wZXJhdG9yLnN2Yzo1NDMyL3BnY2x1c3RlcjE=&quot;, &quot;port&quot;: &quot;NTQzMg==&quot;, &quot;uri&quot;: &quot;cG9zdGdyZXNxbDovL3BnY2x1c3RlcjE6NXNSaT0lNUJmWW0lM0NpUkhrJTJBJTVCJTNFblhoamolN0NRQHBnY2x1c3RlcjEtcHJpbWFyeS5wb3N0Z3Jlcy1vcGVyYXRvci5zdmM6NTQzMi9wZ2NsdXN0ZXIx&quot;, &quot;user&quot;: &quot;cGdjbHVzdGVyMQ==&quot;, &quot;verifier&quot;: &quot;U0NSQU0tU0hBLTI1NiQ0MDk2OlgyQ3NQRU1FZjh3QkVlc05McDFJTkE9PSRKcDhKakl5Q0o1ZEpFRVhia1ptUERTNE5rR3d0V00rczdrMElsQmx0YkpvPTpEaHg3VzNCOE5vNDRYSHJ1Qm1RdENMQW9jNEtnSUZQa2dIeStUMkVWUUowPQ==&quot; }, &quot;kind&quot;: &quot;Secret&quot;, &quot;metadata&quot;: { &quot;creationTimestamp&quot;: &quot;2024-04-09T16:37:36Z&quot;, &quot;labels&quot;: { &quot;postgres-operator.crunchydata.com/cluster&quot;: &quot;pgcluster1&quot;, &quot;postgres-operator.crunchydata.com/pguser&quot;: &quot;pgcluster1&quot;, &quot;postgres-operator.crunchydata.com/role&quot;: &quot;pguser&quot; }, &quot;name&quot;: &quot;pgcluster1-pguser-pgcluster1&quot;, &quot;namespace&quot;: &quot;postgres-operator&quot;, &quot;ownerReferences&quot;: [ { &quot;apiVersion&quot;: &quot;postgres-operator.crunchydata.com/v1beta1&quot;, &quot;blockOwnerDeletion&quot;: true, &quot;controller&quot;: true, &quot;kind&quot;: &quot;PostgresCluster&quot;, &quot;name&quot;: &quot;pgcluster1&quot;, &quot;uid&quot;: &quot;7260b882-116f-4b02-b51a-18d4fe3a8038&quot; } ], &quot;resourceVersion&quot;: &quot;9495&quot;, &quot;uid&quot;: &quot;1fbdf1d2-48ea-4a45-b7d6-01248317dbee&quot; }, &quot;type&quot;: &quot;Opaque&quot; } </pre> <p>Pour se connecter à notre pgbouncer, il suffit d’utiliser les informations fournies par le fichier de secret à la place de nos infos de connexion habituelles, et cela nous permet d’accéder directement au bouncer et non plus à l’instance elle-même.</p> <p>Cette connexion peut être facilement modifiée en utilisant la documentation de pgbouncer afin de pouvoir configurer à notre guise notre pgbouncer. Un exemple de configuration qu’on pourrais rencontrer serait :</p> <pre class="brush: yaml; title: ; notranslate"> proxy: pgBouncer: image: {{.Values.image.pgBouncer }} config: global: default_pool_size: &quot;100&quot; max_client_conn: &quot;10000&quot; pool_mode: transaction </pre> <p>Pour cet exemple on voit qu’on a définit un nombre de client maximum, la taille du pool à 100 et un mode transaction pour notre pool.</p> <h3>PGO et Prometheus</h3> <h4>Utilité :</h4> <p>Prometheus est une trousse à outils de surveillance et d&#8217;alerte des systèmes en open source.<br /> Prometheus collecte et stocke ses métriques sous forme de données de séries temporelles, c&#8217;est-à-dire que les informations de métriques sont stockées avec le timestamp auquel elles ont été enregistrées, aux côtés de paires clé-valeur optionnelles appelées labels.<br /> &#8211; Un modèle de données multidimensionnel avec des données de séries temporelles identifiées par le nom de la métrique et des paires clé-valeur<br /> &#8211; PromQL, un langage de requête flexible pour exploiter cette dimensionnalité<br /> &#8211; Aucune dépendance sur le stockage distribué ; les nœuds de serveur individuels sont autonomes<br /> &#8211; La collecte de séries temporelles se fait via un modèle de tirage sur HTTP<br /> &#8211; La poussée de séries temporelles est prise en charge via une passerelle intermédiaire<br /> &#8211; Les cibles sont découvertes via la découverte de service ou la configuration statique<br /> &#8211; Prise en charge de plusieurs modes de graphiques et de tableaux de bord</p> <h4>Mise en place :</h4> <p>Pour pouvoir mettre en place une surveillance pour notre cluster, il est plus simple de télécharger et compléter le modèle fournit dans les exemples de pgo.<br /> Ainsi, on peut récupérer les exemples à l’aide de git :</p> <pre class="brush: bash; title: ; notranslate"> YOUR_GITHUB_UN=&quot;$YOUR_GITHUB_USERNAME&quot; git clone --depth 1 &quot;git@github.com:${YOUR_GITHUB_UN}/postgres-operator-examples.git&quot; cd postgres-operator-examples </pre> <p>Les différentes configurations se trouvent dans le dossier kustomize/monitoring.<br /> Pour activer le monitoring de notre instance, il faut ajouter la balise monitoring à notre fichier postgres.yaml :</p> <pre class="brush: yaml; title: ; notranslate"> monitoring: pgmonitor: exporter: image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres-exporter:ubi8-5.5.1-0 </pre> <p>Une fois notre configuration modifiée, on l’applique afin que PGO détecte les changements et configure tout seul l’exporter pour qu’il puisse se connecter à nos bases de données et récupérer les métriques.</p> <pre class="brush: bash; title: ; notranslate"> kubectl apply -k kustomize/postgres </pre> <p>Il faut ensuite appliquer la configuration de base de pgmonitor pour qu’il créé lui-même les fichiers de configuration pour prometheus (il le fera en même temps pour Grafana et Alertmanager qui sont deux autres outils de surveillance). Pour cela on applique le kustomize présent dans le dossier monitoring :</p> <pre class="brush: bash; title: ; notranslate"> $kubectl apply -k kustomize\postgres postgrescluster.postgres-operator.crunchydata.com/pgcluster1 configured $kubectl apply -k kustomize\monitoring serviceaccount/alertmanager created serviceaccount/grafana created serviceaccount/prometheus created clusterrole.rbac.authorization.k8s.io/prometheus created clusterrolebinding.rbac.authorization.k8s.io/prometheus created configmap/alert-rules-config created configmap/alertmanager-config created configmap/crunchy-prometheus created configmap/grafana-dashboards created configmap/grafana-datasources created secret/grafana-admin created service/crunchy-alertmanager created service/crunchy-grafana created service/crunchy-prometheus created persistentvolumeclaim/alertmanagerdata created persistentvolumeclaim/grafanadata created persistentvolumeclaim/prometheusdata created deployment.apps/crunchy-alertmanager created deployment.apps/crunchy-grafana created deployment.apps/crunchy-prometheus created </pre> <p>Nos services ont été correctement déployés, il ne nous reste plus qu’à utiliser celui qui nous intéresse, ici service/crunchy-prometheus et lui indiquer de commencer à envoyer les informations sur notre prometheus :</p> <pre class="brush: bash; title: ; notranslate"> $kubectl -n postgres-operator port-forward service/crunchy-prometheus 9090:9090 Forwarding from 127.0.0.1:9090 -&gt; 9090 Forwarding from [::1]:9090 -&gt; 9090 Handling connection for 9090 Handling connection for 9090 </pre> <p>Afin d’accéder à notre service prometheus, il ne nous reste plus qu’à se connecter avec l’adresse de notre machine, sur le port 9090 préalablement ouvert, pour voir apparaitre le dashboard de prometheus :</p> <p><a href="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2.jpg"><img loading="lazy" decoding="async" class="alignnone size-medium wp-image-10567" src="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-300x66.jpg" alt="" width="300" height="66" srcset="https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-300x66.jpg 300w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-1024x226.jpg 1024w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2-768x170.jpg 768w, https://blog.capdata.fr/wp-content/uploads/2024/05/Image2.jpg 1386w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a></p> <h3>PGO Client :</h3> <h4>Utilité :</h4> <p>Pour pouvoir gérer plus facilement le cluster créé par PGO, CrunchyData à développé une surcouche à kubectl qui permet de faciliter les commandes que nous pouvons réaliser sur le cluster.<br /> Cela permet de ne pas avoir à taper les longues lignes de commandes qui permettent par exemple de démarrer les sauvegardes unitaires.</p> <h4>Mise en place :</h4> <p>Pour pouvoir installer cette surcouche, il faut télécharger la version qui correspond au système d’exploitation à partir du GIT de pgo client :</p> <pre class="brush: bash; title: ; notranslate"> # wget https://github.com/CrunchyData/postgres-operator-client/releases/download/v0.4.1/kubectl-pgo-linux-arm64 --2024-04-11 12:07:45-- https://github.com/CrunchyData/postgres-operator-client/releases/download/v0.4.1/kubectl-pgo-linux-arm64 Resolving github.com (github.com)... 140.82.121.4 Connecting to github.com (github.com)|140.82.121.4|:443... connected. HTTP request sent, awaiting response... 302 Found Resolving objects.githubusercontent.com (objects.githubusercontent.com)... 185.199.109.133, 185.199.111.133, 185.199.110.133, ... Connecting to objects.githubusercontent.com (objects.githubusercontent.com)|185.199.109.133|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 47895849 (46M) [application/octet-stream] Saving to: ‘kubectl-pgo-linux-arm64’ kubectl-pgo-linux-arm64 100%[========================================================================================================================================&gt;] 45.68M --.-KB/s in 0.1s 2024-04-11 12:07:45 (373 MB/s) - ‘kubectl-pgo-linux-arm64’ saved [47895849/47895849] </pre> <p>On renome le fichier téléchargé en kubectl-pgo et on le déplace dans nos bin pour pouvoir les utiliser :</p> <pre class="brush: bash; title: ; notranslate"> # mv kubectl-pgo-linux-arm64 kubectl-pgo # sudo mv kubectl-pgo /usr/local/bin/kubectl-pgo # sudo chmod +x /usr/local/bin/kubectl-pgo Une fois que ces actions sont réalisées, on peut tester le fonctionnement : # kubectl pgo version Client Version: v0.4.1 Operator Version: v5.5.1 </pre> <p>Les commandes disponibles avec cette extension sont les suivantes :<br /> &#8211; backup : Backup cluster<br /> &#8211; create : Create a resource<br /> &#8211; delete : Delete a resource<br /> &#8211; help : Help about any command<br /> &#8211; restore : Restore cluster<br /> &#8211; show Show : PostgresCluster details<br /> &#8211; start : Start cluster<br /> &#8211; stop : Stop cluster<br /> &#8211; support : Crunchy Support commands for PGO<br /> &#8211; version : PGO client<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pgo-operateurs-kubernetes-pour-postgresql-la-suite/" rel="bookmark" title="6 juin 2023">PGO : opérateurs kubernetes pour PostgreSQL, la suite !</a> (David Baffaleuf) [ContainerDevopsPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/kubegres-loperateur-kubernetes-cle-en-main-pour-postgresql/" rel="bookmark" title="26 avril 2023">Kubegres : l&#8217;opérateur Kubernetes clé en main pour PostgreSQL</a> (David Baffaleuf) [ContainerDevopsPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-sur-la-solution-kubernetes-locale-minikube/" rel="bookmark" title="29 mars 2023">PostgreSQL sur la solution Kubernetes locale Minikube</a> (Emmanuel RAMI) [ContainerPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-comparatif-entre-barman-et-pgbackrest/" rel="bookmark" title="4 février 2020">PostgreSQL : Comparatif entre Barman et pgBackRest</a> (Capdata team) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/sauvegardes-sql-server-dans-un-azure-blob-storage/" rel="bookmark" title="21 août 2018">Sauvegardes SQL Server dans un Azure Blob Storage</a> (Capdata team) [AzureSQL Server]</li> </ul> <p><!-- Similar Posts took 3.088 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562&#038;title=PGO%20%3A%20la%20suite" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=PGO%20%3A%20la%20suite&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10562" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pgo-la-suite/">PGO : la suite</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>La gestion efficace des clusters PostgreSQL dans un environnement Kubernetes est un défi complexe auquel sont confrontées de nombreuses entreprises aujourd&#8217;hui. PGO offre une solution déclarative qui automatise la gestion des clusters PostgreSQL, simplifiant ainsi le déploiement, la mise à&#8230; <a href="https://blog.capdata.fr/index.php/pgo-la-suite/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pgo-la-suite/">PGO : la suite</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> @@ -116,7 +116,7 @@ pg_recursively_delete : Simplifier les suppressions récursives 2024-04-03T13:11:08Z tag:blog.capdata.fr,2024-04-03:/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;title=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>Si vous avez été amené au fil de votre carrière à manipuler de gros volumes de données contenus dans plusieurs tables possédant des références croisées entre elles, dépendantes d&#8217;autres tables, qui elles-mêmes dépendent d&#8217;autres tables, vous savez à quel point il peut être compliqué de remonter l&#8217;intégralité de l&#8217;arbre de dépendance pour supprimer la moindre ligne. Cela peut être long et fastidieux.</p> <p>Vous ne savez pas vraiment ce que vous supprimez, dans quelles tables, et quels impacts cela peut avoir sur votre base de données. Si les dépendances sont nombreuses, il est d&#8217;autant plus compliqué de tout retracer et d&#8217;être sûr à 100 % de ce que votre DELETE va entraîner.</p> <p>Dans cet article, je vais vous présenter rapidement un petit outil sous la forme d&#8217;une extension que je trouve pratique à utiliser dans ce cas de figure. L&#8217;outil s&#8217;appelle pg_recursively_delete, et il permet de tracer avant d&#8217;exécuter l&#8217;ordre de suppression de votre ligne, et d&#8217;avoir une arborescence des différentes données que vous allez impacter.</p> <h2>Installation d&#8217;un moteur et de l&#8217;extension :</h2> <p>Pour cet article, j&#8217;ai choisi d&#8217;utiliser PostgreSQL en version 16 pour tester si l&#8217;extension fonctionnait toujours.</p> <pre class="brush: bash; title: ; notranslate">root:~#sudo apt update &amp;amp;&amp;amp; sudo apt upgrade root:~#sudo sh -c 'echo &quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&quot; &amp;gt; /etc/apt/sources.list.d/pgdg.list' root:~#wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - root:~#sudo apt -y update root:~#sudo apt -y install postgresql-16</pre> <p>Notre moteur de base de données est installé, à présent il nous faut télécharger les sources de l&#8217;extension, et l&#8217;installer.</p> <pre class="brush: bash; title: ; notranslate">root:~# git clone https://github.com/trlorenz/PG-recursively_delete.git Cloning into 'PG-recursively_delete'... remote: Enumerating objects: 155, done. remote: Counting objects: 100% (95/95), done. remote: Compressing objects: 100% (62/62), done. remote: Total 155 (delta 41), reused 74 (delta 29), pack-reused 60 Receiving objects: 100% (155/155), 38.55 KiB | 3.21 MiB/s, done. Resolving deltas: 100% (70/70), done. root:~# cd PG-recursively_delete/ root:~/PG-recursively_delete# make cp sql/recursively_delete.sql sql/recursively_delete--0.1.5.sql root:~/PG-recursively_delete# sudo make install /bin/mkdir -p '/usr/share/postgresql/16/extension' /bin/mkdir -p '/usr/share/postgresql/16/extension' /bin/mkdir -p '/usr/share/doc/postgresql-doc-16/extension' /usr/bin/install -c -m 644 .//recursively_delete.control '/usr/share/postgresql/16/extension/' /usr/bin/install -c -m 644 .//sql/recursively_delete--0.1.5.sql '/usr/share/postgresql/16/extension/' /usr/bin/install -c -m 644 .//doc/changelog.md '/usr/share/doc/postgresql-doc-16/extension/'</pre> <h2>Mise en place de l&#8217;environnement</h2> <p>Pour illustrer le fonctionnement de l&#8217;extension, je vais utiliser la base de données de démonstration dvdrental. Nous allons donc la télécharger et la charger dans une toute nouvelle base de données que nous aurons créée sur notre instance fraîchement créée :</p> <pre class="brush: bash; title: ; notranslate"> postgres:~$ wget https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip --2024-03-11 08:34:54-- https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip Resolving www.postgresqltutorial.com (www.postgresqltutorial.com)... 104.21.2.174, 172.67.129.129, 2606:4700:3037::6815:2ae, ... Connecting to www.postgresqltutorial.com (www.postgresqltutorial.com)|104.21.2.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 550906 (538K) [application/zip] Saving to: ‘dvdrental.zip’ dvdrental.zip 100%[========================================================================================================================================&gt;] 537.99K --.-KB/s in 0.01s 2024-03-11 08:34:54 (46.0 MB/s) - ‘dvdrental.zip’ saved [550906/550906] </pre> <p>Une fois téléchargée, on la dezippe :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ ls -l total 544 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip postgres:~$ unzip dvdrental.zip Archive: dvdrental.zip inflating: dvdrental.tar postgres:~$ ls -l total 3316 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 2835456 May 12 2019 dvdrental.tar -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip</pre> <p>On créé la base de données pour accueillir nos données, et on charge le fichier de sauvegarde :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# create database dvdrental; CREATE DATABASE postgres=# \l List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges -----------+----------+----------+-----------------+---------+---------+------------+-----------+----------------------- dvdrental | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres (4 rows)</pre> <pre class="brush: bash; title: ; notranslate">postgres:~$ pg_restore -U postgres -d dvdrental dvdrental.tar</pre> <p>Une fois que c&#8217;est fait, on peut se connecter pour vérifier que tout a bien été chargé :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# \c dvdrental You are now connected to database &quot;dvdrental&quot; as user &quot;postgres&quot;. dvdrental=# \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+---------- public | actor | table | postgres public | address | table | postgres public | category | table | postgres public | city | table | postgres public | country | table | postgres public | customer | table | postgres public | film | table | postgres public | film_actor | table | postgres public | film_category | table | postgres public | inventory | table | postgres public | language | table | postgres public | payment | table | postgres public | rental | table | postgres public | staff | table | postgres public | store | table | postgres (15 rows) </pre> <h2>L&#8217;extension :</h2> <p>Pour tester l&#8217;extension, nous allons essayer de supprimer un client de la liste des clients.<br /> Le schéma de la base de données dvdrental est le suivant :<br /> <a href="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram.png"><img loading="lazy" decoding="async" class="alignnone wp-image-10507" src="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram-238x300.png" alt="" width="336" height="424" srcset="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram-238x300.png 238w, https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram.png 730w" sizes="auto, (max-width: 336px) 100vw, 336px" /></a></p> <p>Si l&#8217;on observe attentivement le schéma ci-dessus, en voulant supprimer une donnée de la table customer, cela devrait avoir un impact sur les tables rental et payment qui sont directement liées à la table customer. De plus, ces deux tables sont également liées entre elles, ce qui signifie que supprimer une donnée dans la table rental modifiera nécessairement la table payment.</p> <p>Prenons l&#8217;exemple de la suppression du client numéro 1. Si nous recherchons les dépendances de ce client dans la table rental, nous obtenons 32 lignes associées au customer_id 1 :</p> <pre class="brush: sql; title: ; notranslate"> dvdrental=# select count(*) from rental where customer_id = 1; count ------- 32 (1 row) </pre> <p>Et si nous allons maintenant chercher toutes les occurrences de ce même client dans la table des paiements, nous obtenons :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select count(*) from payment where customer_id = 1; count ------- 30 (1 row) </pre> <p>À présent, avec l&#8217;extension recursive_delete, nous allons chercher à obtenir le schéma de suppression pour vérifier si les résultats que nous avons trouvés sont corrects :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# create extension recursively_delete; CREATE EXTENSION dvdrental=# \set VERBOSITY terse dvdrental=# select recursively_delete('customer', 1); INFO: DAMAGE PREVIEW (recursively_delete v0.1.5) INFO: INFO: 1 customer INFO: 30 r | payment.[&quot;customer_id&quot;] INFO: 32 r | rental.[&quot;customer_id&quot;] INFO: ~ n | | payment.[&quot;rental_id&quot;] INFO: recursively_delete -------------------- 0 (1 row) </pre> <p>La fonction de suppression de l&#8217;extension fonctionne avec les paramètres suivants :</p> <ul> <li>Le nom de la table en premier paramètre</li> <li>La clause WHERE du DELETE en second paramètre, qui peut être de multiples types (des entiers, des chaînes de caractères, des listes, des UUID&#8230;)</li> <li>Le mode de fonctionnement de l&#8217;extension, par défaut à false, qui indique au programme de ne pas effectuer les suppressions, mais simplement de dresser le schéma. Le passer à true entraînerait les suppressions.</li> </ul> <p>Pour interpréter le schéma, voici la composition de chaque nœud :</p> <ol> <li>La première colonne correspond au nombre de lignes</li> <li>Le type de contraintes qui expliquent l&#8217;implication de la table dans le schéma : &#8216;a&#8217;, &#8216;r&#8217;, &#8216;c&#8217;, &#8216;n&#8217;, ou &#8216;d&#8217; (&#8216;no action&#8217;, &#8216;restrict&#8217;, &#8216;cascade&#8217;, &#8216;set null&#8217;, ou &#8216;set default&#8217;)</li> <li>Un indicateur de si oui ou non le champ en question participe à une référence circulaire.</li> </ol> <p>En examinant le résultat renvoyé par notre extension, nous constatons que nous obtenons les mêmes résultats : 30 lignes pour payment et 32 lignes pour rental. Nous obtenons également une dernière ligne qui nous indique que payment possède une référence à rental dans sa structure, et qu&#8217;il va lui aussi procéder à des suppressions en fonction du rental_id. Cela pourrait être par exemple le cas où une location effectuée par un client serait payée par un autre.</p> <p>Pour effectuer la suppression, il suffit simplement de préciser true en troisième paramètre.</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select recursively_delete('customer', 1, true); recursively_delete -------------------- 1 (1 row) </pre> <p>Et à présent, si nous consultons notre table customer, la ligne 1 a disparu, ainsi que toutes les lignes qui la concernent dans d&#8217;autres tables également.</p> <pre class="brush: sql; title: ; notranslate"> dvdrental=# select count(*) from customer where customer_id = 1; count ------- 0 (1 row) dvdrental=# select count(*) from rental where customer_id = 1; count ------- 0 (1 row) dvdrental=# select count(*) from payment where customer_id = 1; count ------- 0 (1 row) </pre> <p>Nos lignes ont bel et bien disparu.</p> <p>Cette extension fonctionne également avec les clés primaires composites. Il suffit de préciser entre crochets les deux valeurs de notre clé primaire, et le tour est joué.</p> <p>Pour illustrer davantage le fonctionnement, je vais réaliser une suppression sur la table film. Cette table possède quelques dépendances.<br /> Disons que nous souhaitons supprimer les 10 premiers films de notre liste, car ils ne sont plus loués étant trop anciens (plus personne n&#8217;a de magnétoscope pour regarder de bonnes vieilles cassettes !).</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select recursively_delete('film', (SELECT array_agg(film_id) FROM film WHERE film_id between 1 and 10)); INFO: DAMAGE PREVIEW (recursively_delete v0.1.5) INFO: INFO: 10 film INFO: 62 r | film_actor.[&quot;film_id&quot;] INFO: 10 r | film_category.[&quot;film_id&quot;] INFO: 52 r | inventory.[&quot;film_id&quot;] INFO: 165 r | | rental.[&quot;inventory_id&quot;] INFO: ~ n | | | payment.[&quot;rental_id&quot;] INFO: recursively_delete -------------------- 0 (1 row)</pre> <p>Nous observons donc que notre suppression de 10 films (dans un array) entraîne la suppression d&#8217;acteurs, de catégories, d&#8217;inventaires, et par extension, de locations et de paiements</p> <h2>Conclusion :</h2> <p>En conclusion, l&#8217;extension pg_recursively_delete offre une solution pratique pour supprimer récursivement des données dans PostgreSQL, simplifiant ainsi les tâches de maintenance et de nettoyage des bases de données. Cependant, malgré ses avantages, cette extension présente certaines limites en termes de performances.</p> <p>L&#8217;une des principales limitations réside dans le fait que la suppression récursive peut entraîner des opérations coûteuses en termes de temps d&#8217;exécution, surtout lorsque les données concernées sont fortement imbriquées ou que la base de données est volumineuse. Les performances peuvent également être affectées lorsque les tables impliquées dans la suppression ont des index complexes ou des contraintes de clés étrangères.</p> <p>De plus, il est crucial de reconnaître les risques associés à la suppression de données ayant de nombreuses dépendances dans une base de données. La suppression inconsidérée de telles données peut entraîner des incohérences dans la base de données, des erreurs d&#8217;intégrité référentielle et même des pertes de données importantes. Il est donc essentiel de procéder avec prudence et de prendre en compte toutes les implications potentielles avant d&#8217;utiliser cette extension.</p> <p>En résumé, bien que l&#8217;extension pg_recursively_delete offre une fonctionnalité utile pour gérer les opérations de suppression récursive dans PostgreSQL, il est essentiel pour les utilisateurs de comprendre ses limites en termes de performances et les risques potentiels associés à la suppression de données avec de nombreuses dépendances. Une utilisation judicieuse et une évaluation minutieuse des scénarios d&#8217;utilisation sont indispensables pour garantir l&#8217;intégrité et la performance de la base de données.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/" rel="bookmark" title="27 mars 2024">pg_dirtyread où comment réparer facilement un delete sauvage</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-anonymizer/" rel="bookmark" title="7 juillet 2022">PostgreSQL Anonymizer</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/" rel="bookmark" title="19 décembre 2024">La montée de version en zero-downtime : merci la réplication !</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 1.955 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;title=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/">pg_recursively_delete : Simplifier les suppressions récursives</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;title=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>Si vous avez été amené au fil de votre carrière à manipuler de gros volumes de données contenus dans plusieurs tables possédant des références croisées entre elles, dépendantes d&#8217;autres tables, qui elles-mêmes dépendent d&#8217;autres tables, vous savez à quel point il peut être compliqué de remonter l&#8217;intégralité de l&#8217;arbre de dépendance pour supprimer la moindre ligne. Cela peut être long et fastidieux.</p> <p>Vous ne savez pas vraiment ce que vous supprimez, dans quelles tables, et quels impacts cela peut avoir sur votre base de données. Si les dépendances sont nombreuses, il est d&#8217;autant plus compliqué de tout retracer et d&#8217;être sûr à 100 % de ce que votre DELETE va entraîner.</p> <p>Dans cet article, je vais vous présenter rapidement un petit outil sous la forme d&#8217;une extension que je trouve pratique à utiliser dans ce cas de figure. L&#8217;outil s&#8217;appelle pg_recursively_delete, et il permet de tracer avant d&#8217;exécuter l&#8217;ordre de suppression de votre ligne, et d&#8217;avoir une arborescence des différentes données que vous allez impacter.</p> <h2>Installation d&#8217;un moteur et de l&#8217;extension :</h2> <p>Pour cet article, j&#8217;ai choisi d&#8217;utiliser PostgreSQL en version 16 pour tester si l&#8217;extension fonctionnait toujours.</p> <pre class="brush: bash; title: ; notranslate">root:~#sudo apt update &amp;amp;&amp;amp; sudo apt upgrade root:~#sudo sh -c 'echo &quot;deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main&quot; &amp;gt; /etc/apt/sources.list.d/pgdg.list' root:~#wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - root:~#sudo apt -y update root:~#sudo apt -y install postgresql-16</pre> <p>Notre moteur de base de données est installé, à présent il nous faut télécharger les sources de l&#8217;extension, et l&#8217;installer.</p> <pre class="brush: bash; title: ; notranslate">root:~# git clone https://github.com/trlorenz/PG-recursively_delete.git Cloning into 'PG-recursively_delete'... remote: Enumerating objects: 155, done. remote: Counting objects: 100% (95/95), done. remote: Compressing objects: 100% (62/62), done. remote: Total 155 (delta 41), reused 74 (delta 29), pack-reused 60 Receiving objects: 100% (155/155), 38.55 KiB | 3.21 MiB/s, done. Resolving deltas: 100% (70/70), done. root:~# cd PG-recursively_delete/ root:~/PG-recursively_delete# make cp sql/recursively_delete.sql sql/recursively_delete--0.1.5.sql root:~/PG-recursively_delete# sudo make install /bin/mkdir -p '/usr/share/postgresql/16/extension' /bin/mkdir -p '/usr/share/postgresql/16/extension' /bin/mkdir -p '/usr/share/doc/postgresql-doc-16/extension' /usr/bin/install -c -m 644 .//recursively_delete.control '/usr/share/postgresql/16/extension/' /usr/bin/install -c -m 644 .//sql/recursively_delete--0.1.5.sql '/usr/share/postgresql/16/extension/' /usr/bin/install -c -m 644 .//doc/changelog.md '/usr/share/doc/postgresql-doc-16/extension/'</pre> <h2>Mise en place de l&#8217;environnement</h2> <p>Pour illustrer le fonctionnement de l&#8217;extension, je vais utiliser la base de données de démonstration dvdrental. Nous allons donc la télécharger et la charger dans une toute nouvelle base de données que nous aurons créée sur notre instance fraîchement créée :</p> <pre class="brush: bash; title: ; notranslate"> postgres:~$ wget https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip --2024-03-11 08:34:54-- https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip Resolving www.postgresqltutorial.com (www.postgresqltutorial.com)... 104.21.2.174, 172.67.129.129, 2606:4700:3037::6815:2ae, ... Connecting to www.postgresqltutorial.com (www.postgresqltutorial.com)|104.21.2.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 550906 (538K) [application/zip] Saving to: ‘dvdrental.zip’ dvdrental.zip 100%[========================================================================================================================================&gt;] 537.99K --.-KB/s in 0.01s 2024-03-11 08:34:54 (46.0 MB/s) - ‘dvdrental.zip’ saved [550906/550906] </pre> <p>Une fois téléchargée, on la dezippe :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ ls -l total 544 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip postgres:~$ unzip dvdrental.zip Archive: dvdrental.zip inflating: dvdrental.tar postgres:~$ ls -l total 3316 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 2835456 May 12 2019 dvdrental.tar -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip</pre> <p>On créé la base de données pour accueillir nos données, et on charge le fichier de sauvegarde :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# create database dvdrental; CREATE DATABASE postgres=# \l List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges -----------+----------+----------+-----------------+---------+---------+------------+-----------+----------------------- dvdrental | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres (4 rows)</pre> <pre class="brush: bash; title: ; notranslate">postgres:~$ pg_restore -U postgres -d dvdrental dvdrental.tar</pre> <p>Une fois que c&#8217;est fait, on peut se connecter pour vérifier que tout a bien été chargé :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# \c dvdrental You are now connected to database &quot;dvdrental&quot; as user &quot;postgres&quot;. dvdrental=# \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+---------- public | actor | table | postgres public | address | table | postgres public | category | table | postgres public | city | table | postgres public | country | table | postgres public | customer | table | postgres public | film | table | postgres public | film_actor | table | postgres public | film_category | table | postgres public | inventory | table | postgres public | language | table | postgres public | payment | table | postgres public | rental | table | postgres public | staff | table | postgres public | store | table | postgres (15 rows) </pre> <h2>L&#8217;extension :</h2> <p>Pour tester l&#8217;extension, nous allons essayer de supprimer un client de la liste des clients.<br /> Le schéma de la base de données dvdrental est le suivant :<br /> <a href="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram.png"><img loading="lazy" decoding="async" class="alignnone wp-image-10507" src="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram-238x300.png" alt="" width="336" height="424" srcset="https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram-238x300.png 238w, https://blog.capdata.fr/wp-content/uploads/2024/03/dvd-rental-sample-database-diagram.png 730w" sizes="auto, (max-width: 336px) 100vw, 336px" /></a></p> <p>Si l&#8217;on observe attentivement le schéma ci-dessus, en voulant supprimer une donnée de la table customer, cela devrait avoir un impact sur les tables rental et payment qui sont directement liées à la table customer. De plus, ces deux tables sont également liées entre elles, ce qui signifie que supprimer une donnée dans la table rental modifiera nécessairement la table payment.</p> <p>Prenons l&#8217;exemple de la suppression du client numéro 1. Si nous recherchons les dépendances de ce client dans la table rental, nous obtenons 32 lignes associées au customer_id 1 :</p> <pre class="brush: sql; title: ; notranslate"> dvdrental=# select count(*) from rental where customer_id = 1; count ------- 32 (1 row) </pre> <p>Et si nous allons maintenant chercher toutes les occurrences de ce même client dans la table des paiements, nous obtenons :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select count(*) from payment where customer_id = 1; count ------- 30 (1 row) </pre> <p>À présent, avec l&#8217;extension recursive_delete, nous allons chercher à obtenir le schéma de suppression pour vérifier si les résultats que nous avons trouvés sont corrects :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# create extension recursively_delete; CREATE EXTENSION dvdrental=# \set VERBOSITY terse dvdrental=# select recursively_delete('customer', 1); INFO: DAMAGE PREVIEW (recursively_delete v0.1.5) INFO: INFO: 1 customer INFO: 30 r | payment.[&quot;customer_id&quot;] INFO: 32 r | rental.[&quot;customer_id&quot;] INFO: ~ n | | payment.[&quot;rental_id&quot;] INFO: recursively_delete -------------------- 0 (1 row) </pre> <p>La fonction de suppression de l&#8217;extension fonctionne avec les paramètres suivants :</p> <ul> <li>Le nom de la table en premier paramètre</li> <li>La clause WHERE du DELETE en second paramètre, qui peut être de multiples types (des entiers, des chaînes de caractères, des listes, des UUID&#8230;)</li> <li>Le mode de fonctionnement de l&#8217;extension, par défaut à false, qui indique au programme de ne pas effectuer les suppressions, mais simplement de dresser le schéma. Le passer à true entraînerait les suppressions.</li> </ul> <p>Pour interpréter le schéma, voici la composition de chaque nœud :</p> <ol> <li>La première colonne correspond au nombre de lignes</li> <li>Le type de contraintes qui expliquent l&#8217;implication de la table dans le schéma : &#8216;a&#8217;, &#8216;r&#8217;, &#8216;c&#8217;, &#8216;n&#8217;, ou &#8216;d&#8217; (&#8216;no action&#8217;, &#8216;restrict&#8217;, &#8216;cascade&#8217;, &#8216;set null&#8217;, ou &#8216;set default&#8217;)</li> <li>Un indicateur de si oui ou non le champ en question participe à une référence circulaire.</li> </ol> <p>En examinant le résultat renvoyé par notre extension, nous constatons que nous obtenons les mêmes résultats : 30 lignes pour payment et 32 lignes pour rental. Nous obtenons également une dernière ligne qui nous indique que payment possède une référence à rental dans sa structure, et qu&#8217;il va lui aussi procéder à des suppressions en fonction du rental_id. Cela pourrait être par exemple le cas où une location effectuée par un client serait payée par un autre.</p> <p>Pour effectuer la suppression, il suffit simplement de préciser true en troisième paramètre.</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select recursively_delete('customer', 1, true); recursively_delete -------------------- 1 (1 row) </pre> <p>Et à présent, si nous consultons notre table customer, la ligne 1 a disparu, ainsi que toutes les lignes qui la concernent dans d&#8217;autres tables également.</p> <pre class="brush: sql; title: ; notranslate"> dvdrental=# select count(*) from customer where customer_id = 1; count ------- 0 (1 row) dvdrental=# select count(*) from rental where customer_id = 1; count ------- 0 (1 row) dvdrental=# select count(*) from payment where customer_id = 1; count ------- 0 (1 row) </pre> <p>Nos lignes ont bel et bien disparu.</p> <p>Cette extension fonctionne également avec les clés primaires composites. Il suffit de préciser entre crochets les deux valeurs de notre clé primaire, et le tour est joué.</p> <p>Pour illustrer davantage le fonctionnement, je vais réaliser une suppression sur la table film. Cette table possède quelques dépendances.<br /> Disons que nous souhaitons supprimer les 10 premiers films de notre liste, car ils ne sont plus loués étant trop anciens (plus personne n&#8217;a de magnétoscope pour regarder de bonnes vieilles cassettes !).</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select recursively_delete('film', (SELECT array_agg(film_id) FROM film WHERE film_id between 1 and 10)); INFO: DAMAGE PREVIEW (recursively_delete v0.1.5) INFO: INFO: 10 film INFO: 62 r | film_actor.[&quot;film_id&quot;] INFO: 10 r | film_category.[&quot;film_id&quot;] INFO: 52 r | inventory.[&quot;film_id&quot;] INFO: 165 r | | rental.[&quot;inventory_id&quot;] INFO: ~ n | | | payment.[&quot;rental_id&quot;] INFO: recursively_delete -------------------- 0 (1 row)</pre> <p>Nous observons donc que notre suppression de 10 films (dans un array) entraîne la suppression d&#8217;acteurs, de catégories, d&#8217;inventaires, et par extension, de locations et de paiements</p> <h2>Conclusion :</h2> <p>En conclusion, l&#8217;extension pg_recursively_delete offre une solution pratique pour supprimer récursivement des données dans PostgreSQL, simplifiant ainsi les tâches de maintenance et de nettoyage des bases de données. Cependant, malgré ses avantages, cette extension présente certaines limites en termes de performances.</p> <p>L&#8217;une des principales limitations réside dans le fait que la suppression récursive peut entraîner des opérations coûteuses en termes de temps d&#8217;exécution, surtout lorsque les données concernées sont fortement imbriquées ou que la base de données est volumineuse. Les performances peuvent également être affectées lorsque les tables impliquées dans la suppression ont des index complexes ou des contraintes de clés étrangères.</p> <p>De plus, il est crucial de reconnaître les risques associés à la suppression de données ayant de nombreuses dépendances dans une base de données. La suppression inconsidérée de telles données peut entraîner des incohérences dans la base de données, des erreurs d&#8217;intégrité référentielle et même des pertes de données importantes. Il est donc essentiel de procéder avec prudence et de prendre en compte toutes les implications potentielles avant d&#8217;utiliser cette extension.</p> <p>En résumé, bien que l&#8217;extension pg_recursively_delete offre une fonctionnalité utile pour gérer les opérations de suppression récursive dans PostgreSQL, il est essentiel pour les utilisateurs de comprendre ses limites en termes de performances et les risques potentiels associés à la suppression de données avec de nombreuses dépendances. Une utilisation judicieuse et une évaluation minutieuse des scénarios d&#8217;utilisation sont indispensables pour garantir l&#8217;intégrité et la performance de la base de données.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/" rel="bookmark" title="27 mars 2024">pg_dirtyread où comment réparer facilement un delete sauvage</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-anonymizer/" rel="bookmark" title="7 juillet 2022">PostgreSQL Anonymizer</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/la-montee-de-version-en-zero-downtime-merci-la-replication/" rel="bookmark" title="19 décembre 2024">La montée de version en zero-downtime : merci la réplication !</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 3.032 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505&#038;title=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_recursively_delete%20%3A%20Simplifier%20les%20suppressions%20r%C3%A9cursives&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10505" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/">pg_recursively_delete : Simplifier les suppressions récursives</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>Si vous avez été amené au fil de votre carrière à manipuler de gros volumes de données contenus dans plusieurs tables possédant des références croisées entre elles, dépendantes d&#8217;autres tables, qui elles-mêmes dépendent d&#8217;autres tables, vous savez à quel point&#8230; <a href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/">pg_recursively_delete : Simplifier les suppressions récursives</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> @@ -127,7 +127,7 @@ pg_dirtyread où comment réparer facilement un delete sauvage 2024-03-27T14:24:14Z tag:blog.capdata.fr,2024-03-27:/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/ - <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;title=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>Imaginez le scénario : nous sommes vendredi soir, en fin de journée, et pour une raison quelconque, un développeur qui a eu une semaine fatiguante se dit qu&#8217;il serait bien de supprimer les lignes qu&#8217;il a modifiées dans la base de données de développement, afin que lundi il puisse reprendre le travail à zéro. Il se connecte donc et, à l&#8217;aide d&#8217;une commande de suppression sur la table concernée, il efface ses quelques lignes&#8230; Avant de se rendre compte qu&#8217;il vient de le faire en production. </p> <p>Cela nous est arrivé à tous, au moins une fois dans notre carrière. Un manque d&#8217;attention, une absence de sécurisation, une erreur est si vite arrivée. Oui, mais voilà. La solution pour pallier à ce genre de problèmes, c&#8217;est généralement de recharger une sauvegarde de la base de données, pour ne surtout pas perdre cette ligne essentielle des paiements enregistrés&#8230; C&#8217;est long. La base est volumineuse&#8230; Et puis, nous n&#8217;avons pas un plan de sauvegarde fiable&#8230; Au secours !</p> <p>Avant de céder à la panique, peut-être existe-t-il une autre solution à votre problème.</p> <p>L&#8217;extension pg_dirtyread pourrait être une idée. Cette extension permet aux utilisateurs de lire des données supprimées dans les tables, ce qui est normalement impossible en temps normal. Cette extension est disponible gratuitement sur GIT, et elle peut vous sauver la vie dans le scénario évoqué juste avant. Nous allons voir ensemble comment l&#8217;utiliser :</p> <h2>Installer PostgreSQL et pg_dirtyread :</h2> <p>C&#8217;est assez rare pour le souligner, mais pg_dirtyread possède son propre package Ubuntu tout inclus. Ce package, si vous pouvez vous en servir, contient l&#8217;installation du moteur, l&#8217;installation des dépendances de développement de PostgreSQL, le client, et l&#8217;extension elle-même. Cela simplifie grandement le travail. Vous n&#8217;avez qu&#8217;à télécharger le package dans la version qui vous intéresse. Pour cet article, j&#8217;ai choisi de télécharger et installer la dernière version de PostgreSQL ainsi que la dernière version de l&#8217;extension.</p> <pre class="brush: bash; title: ; notranslate"> root:~/pg_dirtyread# sudo apt -y install postgresql-16-dirtyread Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libsensors-config libsensors5 libtypes-serialiser-perl postgresql-16 postgresql-client-16 postgresql-client-common postgresql-common ssl-cert sysstat Suggested packages: lm-sensors postgresql-doc-16 isag The following NEW packages will be installed: libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libsensors-config libsensors5 libtypes-serialiser-perl postgresql-16 postgresql-16-dirtyread postgresql-client-16 postgresql-client-common postgresql-common ssl-cert sysstat 0 upgraded, 14 newly installed, 0 to remove and 0 not upgraded. Need to get 21.3 MB of archives. ... Running kernel seems to be up-to-date. No services need to be restarted. No containers need to be restarted. No user sessions are running outdated binaries. No VM guests are running outdated hypervisor (qemu) binaries on this host.</pre> <p>Il existe une autre méthode d&#8217;installation, la plus classique. Il suffit de télécharger les sources depuis le dépôt GIT et de les compiler sur votre machine en utilisant les commandes &#8216;make&#8217; et &#8216;make install&#8217;.</p> <h2>Mise en place d&#8217;un environnement :</h2> <p>Pour notre test, j&#8217;ai choisit de me servir encore une fois de la base de données dvdrental, accessible à tous. Il me faut donc la télécharger :</p> <pre class="brush: bash; title: ; notranslate"> postgres:~$ wget https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip --2024-03-11 08:34:54-- https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip Resolving www.postgresqltutorial.com (www.postgresqltutorial.com)... 104.21.2.174, 172.67.129.129, 2606:4700:3037::6815:2ae, ... Connecting to www.postgresqltutorial.com (www.postgresqltutorial.com)|104.21.2.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 550906 (538K) [application/zip] Saving to: ‘dvdrental.zip’ dvdrental.zip 100%[========================================================================================================================================&gt;] 537.99K --.-KB/s in 0.01s 2024-03-11 08:34:54 (46.0 MB/s) - ‘dvdrental.zip’ saved [550906/550906] </pre> <p>Une fois téléchargée, on la dezippe :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ ls -l total 544 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip postgres:~$ unzip dvdrental.zip Archive: dvdrental.zip inflating: dvdrental.tar postgres:~$ ls -l total 3316 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 2835456 May 12 2019 dvdrental.tar -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip</pre> <p>On créé la base de données pour accueillir nos données, et on charge le fichier de sauvegarde :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# create database dvdrental; CREATE DATABASE postgres=# \l List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges -----------+----------+----------+-----------------+---------+---------+------------+-----------+----------------------- dvdrental | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres (4 rows)</pre> <pre class="brush: bash; title: ; notranslate">postgres:~$ pg_restore -U postgres -d dvdrental dvdrental.tar</pre> <p>Une fois que c&#8217;est fait, on peut se connecter pour vérifier que tout a bien été chargé :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# \c dvdrental You are now connected to database &quot;dvdrental&quot; as user &quot;postgres&quot;. dvdrental=# \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+---------- public | actor | table | postgres public | address | table | postgres public | category | table | postgres public | city | table | postgres public | country | table | postgres public | customer | table | postgres public | film | table | postgres public | film_actor | table | postgres public | film_category | table | postgres public | inventory | table | postgres public | language | table | postgres public | payment | table | postgres public | rental | table | postgres public | staff | table | postgres public | store | table | postgres (15 rows) </pre> <h2>Premier scénario : Suppression de lignes dans une table</h2> <p>Prenons ici le cas de la situation décrite dans l&#8217;introduction. Une ou plusieurs lignes ont été supprimées d&#8217;une ou plusieurs tables. Recharger l&#8217;intégralité d&#8217;une base de données juste pour ces quelques lignes, aussi essentielles soient-elles, demande un investissement énorme.</p> <p>Pour notre exemple, je vais supprimer un certain nombre de lignes de la table &#8216;payment&#8217;.</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from payment limit 10; payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+---------------------------- 17503 | 341 | 2 | 1520 | 7.99 | 2007-02-15 22:25:46.996577 17504 | 341 | 1 | 1778 | 1.99 | 2007-02-16 17:23:14.996577 17505 | 341 | 1 | 1849 | 7.99 | 2007-02-16 22:41:45.996577 17506 | 341 | 2 | 2829 | 2.99 | 2007-02-19 19:39:56.996577 17507 | 341 | 2 | 3130 | 7.99 | 2007-02-20 17:31:48.996577 17508 | 341 | 1 | 3382 | 5.99 | 2007-02-21 12:33:49.996577 17509 | 342 | 2 | 2190 | 5.99 | 2007-02-17 23:58:17.996577 17510 | 342 | 1 | 2914 | 5.99 | 2007-02-20 02:11:44.996577 17511 | 342 | 1 | 3081 | 2.99 | 2007-02-20 13:57:39.996577 17512 | 343 | 2 | 1547 | 4.99 | 2007-02-16 00:10:50.996577 (10 rows) dvdrental=# delete from payment where payment_id between 17523 and 17532; DELETE 10</pre> <div class="flex-1 overflow-hidden"> <div class="react-scroll-to-bottom--css-toqmf-79elbk h-full"> <div class="react-scroll-to-bottom--css-toqmf-1n7m0yu"> <div class="flex flex-col text-sm pb-9"> <div class="w-full text-token-text-primary" data-testid="conversation-turn-17"> <div class="px-4 py-2 justify-center text-base md:gap-6 m-auto"> <div class="flex flex-1 text-base mx-auto gap-3 md:px-5 lg:px-1 xl:px-5 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem] group final-completion"> <div class="relative flex w-full flex-col agent-turn"> <div class="flex-col gap-1 md:gap-3"> <div class="flex flex-grow flex-col max-w-full"> <div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="107c8c40-0671-4f34-88f1-895d9fe2648b"> <div class="markdown prose w-full break-words dark:prose-invert light"> <p>La bêtise est faite, les données ont disparu. Lorsque j&#8217;essaie de les requêter pour voir si elles sont encore là, je me heurte à la réalité : elles ont été supprimées.</p> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from payment where payment_id between 17523 and 17532; payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+-------------- (0 rows)</pre> <p>La première chose à faire, en toute urgence, c&#8217;est de désactiver l&#8217;auto-vacuum. C&#8217;est votre pire ennemi dans notre scénario. C&#8217;est même la première chose à faire lorsque vous vous rendez compte que l&#8217;erreur a été commise :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# alter table payment set (autovacuum_enabled=false);</pre> <p>Si l&#8217;auto-vacuum est passé sur la table avant que vous n&#8217;ayez eu le temps de le désactiver, malheureusement, pg_dirtyread ne vous servira plus à rien. Une fois cela fait, on initialise l&#8217;extension :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# CREATE EXTENSION pg_dirtyread; CREATE EXTENSION</pre> <p>L&#8217;extension étant maintenant créée, on peut requêter les données perdues. La syntaxe d&#8217;utilisation exige que vous rappeliez le schéma de la table en question au moment de requêter les lignes supprimées. Vous pouvez choisir de ne pas inclure toutes les colonnes, ou même d&#8217;en ajouter, comme nous le verrons à la fin de l&#8217;article. Dans notre exemple, la requête pour consulter les données effacées sera la suivante :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') AS t(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), payment_date timestamp without time zone); payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 17524 | 345 | 2 | 1550 | 0.99 | 2007-02-16 00:27:01.996577 17525 | 345 | 2 | 2766 | 4.99 | 2007-02-19 16:13:41.996577 17526 | 346 | 1 | 1994 | 5.99 | 2007-02-17 09:35:32.996577 17527 | 346 | 2 | 3372 | 2.99 | 2007-02-21 12:02:45.996577 17528 | 346 | 1 | 3421 | 2.99 | 2007-02-21 15:51:24.996577 17529 | 347 | 2 | 1711 | 8.99 | 2007-02-16 12:40:18.996577 17530 | 347 | 2 | 2274 | 0.99 | 2007-02-18 04:59:41.996577 17531 | 347 | 1 | 3026 | 4.99 | 2007-02-20 10:16:26.996577 17532 | 347 | 1 | 3092 | 8.99 | 2007-02-20 14:33:08.996577 17533 | 347 | 1 | 3326 | 7.99 | 2007-02-21 07:33:16.996577 17534 | 348 | 1 | 1654 | 2.99 | 2007-02-16 08:11:14.996577 17535 | 348 | 1 | 2041 | 8.99 | 2007-02-17 12:47:26.996577 17536 | 348 | 2 | 2499 | 0.99 | 2007-02-18 21:30:02.996577</pre> <p>On voit bien apparaître en début de liste nos lignes qui sont censées ne plus exister. Cela nous permet éventuellement d&#8217;extraire les données qu&#8217;elles contiennent pour pouvoir les réinjecter ensuite avec un INSERT. On peut également filtrer les données pour ne rechercher que celles que l&#8217;on a supprimées :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') payment(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), dropped_6 timestamp without time zone) where payment_id = 17523; payment_id | customer_id | staff_id | rental_id | amount | dropped_6 ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 (1 row) </pre> <h2>Deuxième scénario : On a supprimé une colonne entière</h2> <p>Deuxième cas typique, sûrement plus rare, celui où une colonne entière est supprimée. Pour notre exemple, je vais supprimer la colonne contenant la date de paiement :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# alter table payment drop column payment_date; ALTER TABLE</pre> <p>pg_dirtyread nous permet de retrouver les informations de cette colonne à condition qu&#8217;aucun VACUUM FULL ou CLUSTER n&#8217;ait été exécuté. Pour ce faire, il faut connaître la position de la colonne dans l&#8217;ordre des colonnes de la table. Pour notre exemple, la colonne de date est la dernière colonne de la table, donc en position 6. Pour retrouver nos données, on peut donc exécuter la commande suivante :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') payment(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), dropped_6 timestamp without time zone); payment_id | customer_id | staff_id | rental_id | amount | dropped_6 ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 17524 | 345 | 2 | 1550 | 0.99 | 2007-02-16 00:27:01.996577 17525 | 345 | 2 | 2766 | 4.99 | 2007-02-19 16:13:41.996577 17526 | 346 | 1 | 1994 | 5.99 | 2007-02-17 09:35:32.996577 17527 | 346 | 2 | 3372 | 2.99 | 2007-02-21 12:02:45.996577 </pre> <p>Le cas d&#8217;une restauration d&#8217;une colonne entière supprimée est compliqué à gérer. Il faudrait la recréer, puis modifier toutes les lignes une à une pour réajouter les valeurs de cette fameuse colonne. Cela peut s&#8217;avérer un peu complexe à réaliser. Cependant, si vous ne disposez pas d&#8217;une sauvegarde pg_dump, vous n&#8217;aurez peut-être pas d&#8217;autres alternatives que cela, à part recharger entièrement la base.</p> <h2>Bonus : Récupérer les informations système des lignes supprimées</h2> <p>Avec pg_dirtyread, il est possible de récupérer les informations système des lignes qui ont été supprimées. Pour ce faire, rien de plus simple : il suffit d&#8217;indiquer dans la requête les informations que vous souhaitez récupérer. De plus, il existe une colonne qui indique si la ligne est supposément supprimée ou non, ce qui pourrait être pratique pour trier les différentes lignes selon leur état :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# SELECT * FROM pg_dirtyread('payment') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean); tableoid | ctid | xmin | xmax | cmin | cmax | dead ----------+-----------+------+------+------+------+------ 16505 | (0,21) | 835 | 941 | 0 | 0 | f 16505 | (0,22) | 835 | 941 | 0 | 0 | f 16505 | (0,23) | 835 | 941 | 0 | 0 | f 16505 | (0,24) | 835 | 941 | 0 | 0 | f 16505 | (0,25) | 835 | 941 | 0 | 0 | f 16505 | (0,26) | 835 | 941 | 0 | 0 | f 16505 | (0,27) | 835 | 941 | 0 | 0 | f 16505 | (0,28) | 835 | 941 | 0 | 0 | f 16505 | (0,29) | 835 | 941 | 0 | 0 | f 16505 | (0,30) | 835 | 941 | 0 | 0 | f </pre> <h2>Conclusion :</h2> <p>En conclusion, l&#8217;extension pg_dirtyread offre une solution précieuse pour accéder aux données supprimées dans une base de données PostgreSQL. Son utilisation peut être cruciale dans des cas d&#8217;incidents critiques tels que la récupération de données perdues accidentellement ou la résolution d&#8217;incohérences de données. Cependant, il est essentiel de rappeler que son application reste extrêmement situationnelle et que pour qu&#8217;elle puisse être efficace, l&#8217;autovacuum doit être désactivé, ce qui n&#8217;est pas forcément recommandé.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/" rel="bookmark" title="3 avril 2024">pg_recursively_delete : Simplifier les suppressions récursives</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/aws-rds-les-extensions-postgresql/" rel="bookmark" title="21 janvier 2020">AWS RDS : les extensions PostgreSQL</a> (Emmanuel RAMI) [AWSPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pseudonymisation-postgresql/" rel="bookmark" title="30 janvier 2024">Pseudonymiser vos bases PostgreSQL</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 2.024 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;title=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/">pg_dirtyread où comment réparer facilement un delete sauvage</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> + <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;title=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>Imaginez le scénario : nous sommes vendredi soir, en fin de journée, et pour une raison quelconque, un développeur qui a eu une semaine fatiguante se dit qu&#8217;il serait bien de supprimer les lignes qu&#8217;il a modifiées dans la base de données de développement, afin que lundi il puisse reprendre le travail à zéro. Il se connecte donc et, à l&#8217;aide d&#8217;une commande de suppression sur la table concernée, il efface ses quelques lignes&#8230; Avant de se rendre compte qu&#8217;il vient de le faire en production. </p> <p>Cela nous est arrivé à tous, au moins une fois dans notre carrière. Un manque d&#8217;attention, une absence de sécurisation, une erreur est si vite arrivée. Oui, mais voilà. La solution pour pallier à ce genre de problèmes, c&#8217;est généralement de recharger une sauvegarde de la base de données, pour ne surtout pas perdre cette ligne essentielle des paiements enregistrés&#8230; C&#8217;est long. La base est volumineuse&#8230; Et puis, nous n&#8217;avons pas un plan de sauvegarde fiable&#8230; Au secours !</p> <p>Avant de céder à la panique, peut-être existe-t-il une autre solution à votre problème.</p> <p>L&#8217;extension pg_dirtyread pourrait être une idée. Cette extension permet aux utilisateurs de lire des données supprimées dans les tables, ce qui est normalement impossible en temps normal. Cette extension est disponible gratuitement sur GIT, et elle peut vous sauver la vie dans le scénario évoqué juste avant. Nous allons voir ensemble comment l&#8217;utiliser :</p> <h2>Installer PostgreSQL et pg_dirtyread :</h2> <p>C&#8217;est assez rare pour le souligner, mais pg_dirtyread possède son propre package Ubuntu tout inclus. Ce package, si vous pouvez vous en servir, contient l&#8217;installation du moteur, l&#8217;installation des dépendances de développement de PostgreSQL, le client, et l&#8217;extension elle-même. Cela simplifie grandement le travail. Vous n&#8217;avez qu&#8217;à télécharger le package dans la version qui vous intéresse. Pour cet article, j&#8217;ai choisi de télécharger et installer la dernière version de PostgreSQL ainsi que la dernière version de l&#8217;extension.</p> <pre class="brush: bash; title: ; notranslate"> root:~/pg_dirtyread# sudo apt -y install postgresql-16-dirtyread Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libsensors-config libsensors5 libtypes-serialiser-perl postgresql-16 postgresql-client-16 postgresql-client-common postgresql-common ssl-cert sysstat Suggested packages: lm-sensors postgresql-doc-16 isag The following NEW packages will be installed: libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libsensors-config libsensors5 libtypes-serialiser-perl postgresql-16 postgresql-16-dirtyread postgresql-client-16 postgresql-client-common postgresql-common ssl-cert sysstat 0 upgraded, 14 newly installed, 0 to remove and 0 not upgraded. Need to get 21.3 MB of archives. ... Running kernel seems to be up-to-date. No services need to be restarted. No containers need to be restarted. No user sessions are running outdated binaries. No VM guests are running outdated hypervisor (qemu) binaries on this host.</pre> <p>Il existe une autre méthode d&#8217;installation, la plus classique. Il suffit de télécharger les sources depuis le dépôt GIT et de les compiler sur votre machine en utilisant les commandes &#8216;make&#8217; et &#8216;make install&#8217;.</p> <h2>Mise en place d&#8217;un environnement :</h2> <p>Pour notre test, j&#8217;ai choisit de me servir encore une fois de la base de données dvdrental, accessible à tous. Il me faut donc la télécharger :</p> <pre class="brush: bash; title: ; notranslate"> postgres:~$ wget https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip --2024-03-11 08:34:54-- https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip Resolving www.postgresqltutorial.com (www.postgresqltutorial.com)... 104.21.2.174, 172.67.129.129, 2606:4700:3037::6815:2ae, ... Connecting to www.postgresqltutorial.com (www.postgresqltutorial.com)|104.21.2.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 550906 (538K) [application/zip] Saving to: ‘dvdrental.zip’ dvdrental.zip 100%[========================================================================================================================================&gt;] 537.99K --.-KB/s in 0.01s 2024-03-11 08:34:54 (46.0 MB/s) - ‘dvdrental.zip’ saved [550906/550906] </pre> <p>Une fois téléchargée, on la dezippe :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ ls -l total 544 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip postgres:~$ unzip dvdrental.zip Archive: dvdrental.zip inflating: dvdrental.tar postgres:~$ ls -l total 3316 drwxr-xr-x 3 postgres postgres 4096 Mar 11 08:30 16 -rw-rw-r-- 1 postgres postgres 2835456 May 12 2019 dvdrental.tar -rw-rw-r-- 1 postgres postgres 550906 May 12 2019 dvdrental.zip</pre> <p>On créé la base de données pour accueillir nos données, et on charge le fichier de sauvegarde :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# create database dvdrental; CREATE DATABASE postgres=# \l List of databases Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges -----------+----------+----------+-----------------+---------+---------+------------+-----------+----------------------- dvdrental | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres + | | | | | | | | postgres=CTc/postgres (4 rows)</pre> <pre class="brush: bash; title: ; notranslate">postgres:~$ pg_restore -U postgres -d dvdrental dvdrental.tar</pre> <p>Une fois que c&#8217;est fait, on peut se connecter pour vérifier que tout a bien été chargé :</p> <pre class="brush: bash; title: ; notranslate">postgres:~$ psql psql (16.2 (Ubuntu 16.2-1.pgdg22.04+1)) Type &quot;help&quot; for help. </pre> <pre class="brush: sql; title: ; notranslate">postgres=# \c dvdrental You are now connected to database &quot;dvdrental&quot; as user &quot;postgres&quot;. dvdrental=# \dt List of relations Schema | Name | Type | Owner --------+---------------+-------+---------- public | actor | table | postgres public | address | table | postgres public | category | table | postgres public | city | table | postgres public | country | table | postgres public | customer | table | postgres public | film | table | postgres public | film_actor | table | postgres public | film_category | table | postgres public | inventory | table | postgres public | language | table | postgres public | payment | table | postgres public | rental | table | postgres public | staff | table | postgres public | store | table | postgres (15 rows) </pre> <h2>Premier scénario : Suppression de lignes dans une table</h2> <p>Prenons ici le cas de la situation décrite dans l&#8217;introduction. Une ou plusieurs lignes ont été supprimées d&#8217;une ou plusieurs tables. Recharger l&#8217;intégralité d&#8217;une base de données juste pour ces quelques lignes, aussi essentielles soient-elles, demande un investissement énorme.</p> <p>Pour notre exemple, je vais supprimer un certain nombre de lignes de la table &#8216;payment&#8217;.</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from payment limit 10; payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+---------------------------- 17503 | 341 | 2 | 1520 | 7.99 | 2007-02-15 22:25:46.996577 17504 | 341 | 1 | 1778 | 1.99 | 2007-02-16 17:23:14.996577 17505 | 341 | 1 | 1849 | 7.99 | 2007-02-16 22:41:45.996577 17506 | 341 | 2 | 2829 | 2.99 | 2007-02-19 19:39:56.996577 17507 | 341 | 2 | 3130 | 7.99 | 2007-02-20 17:31:48.996577 17508 | 341 | 1 | 3382 | 5.99 | 2007-02-21 12:33:49.996577 17509 | 342 | 2 | 2190 | 5.99 | 2007-02-17 23:58:17.996577 17510 | 342 | 1 | 2914 | 5.99 | 2007-02-20 02:11:44.996577 17511 | 342 | 1 | 3081 | 2.99 | 2007-02-20 13:57:39.996577 17512 | 343 | 2 | 1547 | 4.99 | 2007-02-16 00:10:50.996577 (10 rows) dvdrental=# delete from payment where payment_id between 17523 and 17532; DELETE 10</pre> <div class="flex-1 overflow-hidden"> <div class="react-scroll-to-bottom--css-toqmf-79elbk h-full"> <div class="react-scroll-to-bottom--css-toqmf-1n7m0yu"> <div class="flex flex-col text-sm pb-9"> <div class="w-full text-token-text-primary" data-testid="conversation-turn-17"> <div class="px-4 py-2 justify-center text-base md:gap-6 m-auto"> <div class="flex flex-1 text-base mx-auto gap-3 md:px-5 lg:px-1 xl:px-5 md:max-w-3xl lg:max-w-[40rem] xl:max-w-[48rem] group final-completion"> <div class="relative flex w-full flex-col agent-turn"> <div class="flex-col gap-1 md:gap-3"> <div class="flex flex-grow flex-col max-w-full"> <div class="min-h-[20px] text-message flex flex-col items-start gap-3 whitespace-pre-wrap break-words [.text-message+&amp;]:mt-5 overflow-x-auto" data-message-author-role="assistant" data-message-id="107c8c40-0671-4f34-88f1-895d9fe2648b"> <div class="markdown prose w-full break-words dark:prose-invert light"> <p>La bêtise est faite, les données ont disparu. Lorsque j&#8217;essaie de les requêter pour voir si elles sont encore là, je me heurte à la réalité : elles ont été supprimées.</p> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from payment where payment_id between 17523 and 17532; payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+-------------- (0 rows)</pre> <p>La première chose à faire, en toute urgence, c&#8217;est de désactiver l&#8217;auto-vacuum. C&#8217;est votre pire ennemi dans notre scénario. C&#8217;est même la première chose à faire lorsque vous vous rendez compte que l&#8217;erreur a été commise :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# alter table payment set (autovacuum_enabled=false);</pre> <p>Si l&#8217;auto-vacuum est passé sur la table avant que vous n&#8217;ayez eu le temps de le désactiver, malheureusement, pg_dirtyread ne vous servira plus à rien. Une fois cela fait, on initialise l&#8217;extension :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# CREATE EXTENSION pg_dirtyread; CREATE EXTENSION</pre> <p>L&#8217;extension étant maintenant créée, on peut requêter les données perdues. La syntaxe d&#8217;utilisation exige que vous rappeliez le schéma de la table en question au moment de requêter les lignes supprimées. Vous pouvez choisir de ne pas inclure toutes les colonnes, ou même d&#8217;en ajouter, comme nous le verrons à la fin de l&#8217;article. Dans notre exemple, la requête pour consulter les données effacées sera la suivante :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') AS t(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), payment_date timestamp without time zone); payment_id | customer_id | staff_id | rental_id | amount | payment_date ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 17524 | 345 | 2 | 1550 | 0.99 | 2007-02-16 00:27:01.996577 17525 | 345 | 2 | 2766 | 4.99 | 2007-02-19 16:13:41.996577 17526 | 346 | 1 | 1994 | 5.99 | 2007-02-17 09:35:32.996577 17527 | 346 | 2 | 3372 | 2.99 | 2007-02-21 12:02:45.996577 17528 | 346 | 1 | 3421 | 2.99 | 2007-02-21 15:51:24.996577 17529 | 347 | 2 | 1711 | 8.99 | 2007-02-16 12:40:18.996577 17530 | 347 | 2 | 2274 | 0.99 | 2007-02-18 04:59:41.996577 17531 | 347 | 1 | 3026 | 4.99 | 2007-02-20 10:16:26.996577 17532 | 347 | 1 | 3092 | 8.99 | 2007-02-20 14:33:08.996577 17533 | 347 | 1 | 3326 | 7.99 | 2007-02-21 07:33:16.996577 17534 | 348 | 1 | 1654 | 2.99 | 2007-02-16 08:11:14.996577 17535 | 348 | 1 | 2041 | 8.99 | 2007-02-17 12:47:26.996577 17536 | 348 | 2 | 2499 | 0.99 | 2007-02-18 21:30:02.996577</pre> <p>On voit bien apparaître en début de liste nos lignes qui sont censées ne plus exister. Cela nous permet éventuellement d&#8217;extraire les données qu&#8217;elles contiennent pour pouvoir les réinjecter ensuite avec un INSERT. On peut également filtrer les données pour ne rechercher que celles que l&#8217;on a supprimées :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') payment(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), dropped_6 timestamp without time zone) where payment_id = 17523; payment_id | customer_id | staff_id | rental_id | amount | dropped_6 ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 (1 row) </pre> <h2>Deuxième scénario : On a supprimé une colonne entière</h2> <p>Deuxième cas typique, sûrement plus rare, celui où une colonne entière est supprimée. Pour notre exemple, je vais supprimer la colonne contenant la date de paiement :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# alter table payment drop column payment_date; ALTER TABLE</pre> <p>pg_dirtyread nous permet de retrouver les informations de cette colonne à condition qu&#8217;aucun VACUUM FULL ou CLUSTER n&#8217;ait été exécuté. Pour ce faire, il faut connaître la position de la colonne dans l&#8217;ordre des colonnes de la table. Pour notre exemple, la colonne de date est la dernière colonne de la table, donc en position 6. Pour retrouver nos données, on peut donc exécuter la commande suivante :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# select * from pg_dirtyread('payment') payment(payment_id integer, customer_id smallint, staff_id smallint, rental_id integer, amount numeric(5,2), dropped_6 timestamp without time zone); payment_id | customer_id | staff_id | rental_id | amount | dropped_6 ------------+-------------+----------+-----------+--------+---------------------------- 17523 | 345 | 1 | 1457 | 4.99 | 2007-02-15 18:34:15.996577 17524 | 345 | 2 | 1550 | 0.99 | 2007-02-16 00:27:01.996577 17525 | 345 | 2 | 2766 | 4.99 | 2007-02-19 16:13:41.996577 17526 | 346 | 1 | 1994 | 5.99 | 2007-02-17 09:35:32.996577 17527 | 346 | 2 | 3372 | 2.99 | 2007-02-21 12:02:45.996577 </pre> <p>Le cas d&#8217;une restauration d&#8217;une colonne entière supprimée est compliqué à gérer. Il faudrait la recréer, puis modifier toutes les lignes une à une pour réajouter les valeurs de cette fameuse colonne. Cela peut s&#8217;avérer un peu complexe à réaliser. Cependant, si vous ne disposez pas d&#8217;une sauvegarde pg_dump, vous n&#8217;aurez peut-être pas d&#8217;autres alternatives que cela, à part recharger entièrement la base.</p> <h2>Bonus : Récupérer les informations système des lignes supprimées</h2> <p>Avec pg_dirtyread, il est possible de récupérer les informations système des lignes qui ont été supprimées. Pour ce faire, rien de plus simple : il suffit d&#8217;indiquer dans la requête les informations que vous souhaitez récupérer. De plus, il existe une colonne qui indique si la ligne est supposément supprimée ou non, ce qui pourrait être pratique pour trier les différentes lignes selon leur état :</p> <pre class="brush: sql; title: ; notranslate">dvdrental=# SELECT * FROM pg_dirtyread('payment') AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean); tableoid | ctid | xmin | xmax | cmin | cmax | dead ----------+-----------+------+------+------+------+------ 16505 | (0,21) | 835 | 941 | 0 | 0 | f 16505 | (0,22) | 835 | 941 | 0 | 0 | f 16505 | (0,23) | 835 | 941 | 0 | 0 | f 16505 | (0,24) | 835 | 941 | 0 | 0 | f 16505 | (0,25) | 835 | 941 | 0 | 0 | f 16505 | (0,26) | 835 | 941 | 0 | 0 | f 16505 | (0,27) | 835 | 941 | 0 | 0 | f 16505 | (0,28) | 835 | 941 | 0 | 0 | f 16505 | (0,29) | 835 | 941 | 0 | 0 | f 16505 | (0,30) | 835 | 941 | 0 | 0 | f </pre> <h2>Conclusion :</h2> <p>En conclusion, l&#8217;extension pg_dirtyread offre une solution précieuse pour accéder aux données supprimées dans une base de données PostgreSQL. Son utilisation peut être cruciale dans des cas d&#8217;incidents critiques tels que la récupération de données perdues accidentellement ou la résolution d&#8217;incohérences de données. Cependant, il est essentiel de rappeler que son application reste extrêmement situationnelle et que pour qu&#8217;elle puisse être efficace, l&#8217;autovacuum doit être désactivé, ce qui n&#8217;est pas forcément recommandé.<strong>Continuez votre lecture sur le blog :</strong></p> <ul class="similar-posts"> <li><a href="https://blog.capdata.fr/index.php/pg_recursively_delete-simplifier-les-suppressions-recursives/" rel="bookmark" title="3 avril 2024">pg_recursively_delete : Simplifier les suppressions récursives</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pyrseas-et-postgresql-comparer-facilement-des-schema-de-base-de-donnees/" rel="bookmark" title="3 janvier 2023">Pyrseas et Postgresql : Comparer facilement des schémas de base de données</a> (Sarah FAVEERE) [PostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/postgresql-planifier-une-tache-avec-pg_cron/" rel="bookmark" title="24 septembre 2019">PostgreSQL : planifier une tâche avec pg_cron</a> (Emmanuel RAMI) [Non classéPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/aws-rds-les-extensions-postgresql/" rel="bookmark" title="21 janvier 2020">AWS RDS : les extensions PostgreSQL</a> (Emmanuel RAMI) [AWSPostgreSQL]</li> <li><a href="https://blog.capdata.fr/index.php/pseudonymisation-postgresql/" rel="bookmark" title="30 janvier 2024">Pseudonymiser vos bases PostgreSQL</a> (Sarah FAVEERE) [PostgreSQL]</li> </ul> <p><!-- Similar Posts took 2.027 ms --></p> <a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-twitter nolightbox" data-provider="twitter" target="_blank" rel="nofollow" title="Share on Twitter" href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;text=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="twitter" title="Share on Twitter" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/twitter.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-linkedin nolightbox" data-provider="linkedin" target="_blank" rel="nofollow" title="Share on Linkedin" href="https://www.linkedin.com/shareArticle?mini=true&#038;url=https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494&#038;title=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px;margin-right:5px"><img loading="lazy" decoding="async" alt="linkedin" title="Share on Linkedin" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/linkedin.png" /></a><a class="synved-social-button synved-social-button-share synved-social-size-24 synved-social-resolution-single synved-social-provider-mail nolightbox" data-provider="mail" rel="nofollow" title="Share by email" href="mailto:?subject=pg_dirtyread%20o%C3%B9%20comment%20r%C3%A9parer%20facilement%20un%20delete%20sauvage&#038;body=Article%20sur%20le%20blog%20de%20la%20Capdata%20Tech%20Team%20%3A%20:%20https%3A%2F%2Fblog.capdata.fr%2F%3Fp%3D10494" style="font-size: 0px;width:24px;height:24px;margin:0;margin-bottom:5px"><img loading="lazy" decoding="async" alt="mail" title="Share by email" class="synved-share-image synved-social-image synved-social-image-share" width="24" height="24" style="display: inline;width:24px;height:24px;margin: 0;padding: 0;border: none;box-shadow: none" src="https://blog.capdata.fr/wp-content/plugins/social-media-feather/synved-social/image/social/regular/48x48/mail.png" /></a><p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/">pg_dirtyread où comment réparer facilement un delete sauvage</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p> <p>Imaginez le scénario : nous sommes vendredi soir, en fin de journée, et pour une raison quelconque, un développeur qui a eu une semaine fatiguante se dit qu&#8217;il serait bien de supprimer les lignes qu&#8217;il a modifiées dans la base&#8230; <a href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/" class="more-link">Continuer la lecture <span class="meta-nav">&#8594;</span></a></p> <p>L’article <a rel="nofollow" href="https://blog.capdata.fr/index.php/pg_dirtyread-ou-comment-reparer-facilement-un-delete-sauvage/">pg_dirtyread où comment réparer facilement un delete sauvage</a> est apparu en premier sur <a rel="nofollow" href="https://blog.capdata.fr">Capdata TECH BLOG</a>.</p>