2

PostgreSQL : planifier une tâche avec pg_cron

twitterlinkedinmail

Hello

nous avons vu auparavant, dans un précédent article comment migrer une base Oracle vers PostgreSQL via un outil nommé ora2pg.
Au cours de cette migration, nous avons réalisé que certains packages Oracle ne pouvaient être retranscrits pour PostgreSQL.
Par exemple, le package Oracle DBMS_JOB n’a pu être migré sur l’instance PostgreSQL de destination. Il s’agit d’un package Oracle permettant de planifier des tâches (procédures, ordres SQL, stats ….) qui seront exécutées par le moteur Oracle.

Il existe, pour PostgreSQL, une extension qui permet de faire ce travail au sein d’une instance PostgreSQL.
L’extension se nomme “pg_cron”.

Cet article suit donc les étapes de configuration qui permettront d’utiliser cette extension. Nous partirons d’un serveur Linux Debian avec une instance PostgreSQL 10.5.

 

Les extensions, qu’est ce donc ?

Une extension est un module complémentaire proposant un ensemble de procédures/fonctions et tables permettant la gestion d’opérations  supplémentaires (ex pg_stat_statements, pg_cron, pgpsql….) en base.

Ces modules, ou “plug-ins”,  sont disponibles dans les « contribs » PostgreSQL (si installation depuis les dépôts systèmes « yum » ou « aptitude ».
Ou bien ces derniers seront à télécharger sur le site postgresql :

https://www.postgresql.org/download/products/6-postgresql-extensions/

Certaines extensions sont disponibles sur des sites communautaires  comme Github , ex pour pg_cron :

https://github.com/citusdata/pg_cron

 

Une extension : pg_cron

Cette extension, proposée par Citusdata, permet de planifier des tâches dans une instance PostgreSQL (le processus PostgreSQL s’appuie sur la Crontab du serveur).
Nous pourrons aller télécharger les sources depuis le site « github » par exemple.

 

Il faudra ensuite dezipper le fichier sur un emplacement de destination sur le serveur.

La plupart des extensions PostgreSQL sont écrites en C, il est donc impératif de disposer d’un compilateur C (gcc)

Installation

Le contenu du zip pour cette extension, est le suivant. Attention, vérifier qu’il s’agit bien de la version 1.2 ou <, car celle ci est bien plus complète que les anciennes.

 

postgres@:~/ora2pg/REF/sources/pg_cron_master$ pg_cron-master]$ ls -l
drwxr-xr-x. 2 postgres postgres   95 Aug 28 12:19 src
drwxr-xr-x. 2 postgres postgres   30 Aug 28 12:19 sql
-rw-r--r--. 1 postgres postgres 6293 Aug 28 12:19 README.md
-rw-r--r--. 1 postgres postgres 2034 Aug 28 12:19 pg_cron.sql
-rw-r--r--. 1 postgres postgres  121 Aug 28 12:19 pg_cron.control
-rw-r--r--. 1 postgres postgres  155 Aug 28 12:19 pg_cron--1.1--1.2.sql
-rw-r--r--. 1 postgres postgres  101 Aug 28 12:19 pg_cron--1.0--1.1.sql
-rw-r--r--. 1 postgres postgres 1335 Aug 28 12:19 META.json
-rw-r--r--. 1 postgres postgres  812 Aug 28 12:19 Makefile
-rw-r--r--. 1 postgres postgres  918 Aug 28 12:19 LICENSE
drwxr-xr-x. 2 postgres postgres  136 Aug 28 12:19 include
-rw-r--r--. 1 postgres postgres 4139 Aug 28 12:19 github-banner.png
drwxr-xr-x. 2 postgres postgres   30 Aug 28 12:19 expected
-rw-r--r--. 1 postgres postgres 1632 Aug 28 12:19 CHANGELOG.md

 

Il s’agira maintenant de compiler nos sources.

postgres@:~/ora2pg/REF/sources/pg_cron_master$ make
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security 
-fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -O2 -g -pipe -Wall 
-Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches 
-specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables 
-fstack-clash-protection -fcf-protection -fPIC -std=c99 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-maybe-uninitialized 
-I/usr/include -I. -I./ -I/usr/include/pgsql/server -I/usr/include/pgsql/internal  -D_GNU_SOURCE -I/usr/include/libxml2   
-c -o src/pg_cron.o src/pg_cron.c ………….

Dès à présent, nous voyons notre librairie “pg_cron.so” ainsi construite sur le répertoire courant.

postgres@:~/ora2pg/REF/sources/pg_cron_master $ ls -l
-rw-r--r--. 1 postgres postgres   1632 Aug 28 12:19 CHANGELOG.md
drwxr-xr-x. 2 postgres postgres     30 Aug 28 12:19 expected
-rw-r--r--. 1 postgres postgres   4139 Aug 28 12:19 github-banner.png
drwxr-xr-x. 2 postgres postgres    136 Aug 28 12:19 include
-rw-r--r--. 1 postgres postgres    918 Aug 28 12:19 LICENSE
-rw-r--r--. 1 postgres postgres    812 Aug 28 12:19 Makefile
-rw-r--r--. 1 postgres postgres   1335 Aug 28 12:19 META.json
-rw-r--r--. 1 postgres postgres    101 Aug 28 12:19 pg_cron--1.0--1.1.sql
-rw-r--r--. 1 postgres postgres   2034 Sep 16 15:18 pg_cron--1.0.sql
-rw-r--r--. 1 postgres postgres    155 Aug 28 12:19 pg_cron--1.1--1.2.sql
-rw-r--r--. 1 postgres postgres    121 Aug 28 12:19 pg_cron.control
-rwxr-xr-x. 1 postgres postgres 223096 Sep 16 15:18 pg_cron.so
-rw-r--r--. 1 postgres postgres   2034 Aug 28 12:19 pg_cron.sql
-rw-r--r--. 1 postgres postgres   6293 Aug 28 12:19 README.md
drwxr-xr-x. 2 postgres postgres     30 Aug 28 12:19 sql
drwxr-xr-x. 2 postgres postgres    184 Sep 16 15:18 src

Cette nouvelle librairie devra être propagée aux librairies partagées du serveur auxquelles PostgreSQL sait accéder.
Seul le user “root” est habilité à effectuer cette modification et écrire dans le répertoire /usr.

root@:~/ora2pg/REF/sources/pg_cron_master # make install
usr/bin/mkdir -p '/usr/lib64/pgsql'
/usr/bin/mkdir -p '/usr/share/pgsql/extension'
/usr/bin/mkdir -p '/usr/share/pgsql/extension'
/usr/bin/install -c -m 755  pg_cron.so '/usr/lib64/pgsql/pg_cron.so'
/usr/bin/install -c -m 644 .//pg_cron.control '/usr/share/pgsql/extension/'
/usr/bin/install -c -m 644 .//pg_cron--1.0--1.1.sql .//pg_cron--1.1--1.2.sql pg_cron--1.0.sql '/usr/share/pgsql/extension/'

Notre librairie “pg_cron” est donc installée sur le répertoire “/usr/lib64/pgsql/”. Les fichiers SQL relatifs à cette extension sont présents dans “/usr/share/pgsql/extension”.
Les noms de ces répertoires peuvent variés en fonction de votre distribution Linux. L’installation de votre instance PostgreSQL aura également une influence sur le nom de ces répertoires, selon que vous ayez utilisé les dépôts OS ou bien les sources du site “postgresql.org”.

 

Configuration en base

Une fois installée, partie OS, il faudra modifier les paramètres base de données dans PostgreSQL afin de pouvoir utiliser cette extension.
Le fichier “postgresql.conf” devra indiquer les éléments suivants pour permettre la création de cette extension en base

  • share_preload_libraries
  • cron.database_name.
postgres@:~/ora2pg/REF/sources/pg_cron_master $ cd $PGDATA
postgres@:/data $ vi postgresql.conf
....
shared_preload_libraries = 'pg_cron'          # (change requires restart)
cron.database_name = 'capdata'
....

Le paramètre “shared_preload_libraries” peut contenir plusieurs entrées (si plusieurs extensions utilisées).

Il faudra redémarrer l’instance PostgreSQL pour la prise en compte des modifications. Une fois redémarrée, la log PostgreSQL doit indiquer les informations suivantes

2019-09-16 15:32:24.410 UTC [2262] LOG:  last completed transaction was at log time 2019-09-16 15:29:49.397743+00
2019-09-16 15:32:24.430 UTC [2260] LOG:  database system is ready to accept connections
2019-09-16 15:32:24.433 UTC [2268] LOG:  pg_cron scheduler started

Notre process pg_cron scheduler est UP. Il reste à déclarer l’extension sur notre base “capdata”.

postgresql@:~/ora2pg/REF $ psql -U capdata -d capdata

>  select name, setting, sourcefile from pg_settings where name ='shared_preload_libraries';
           name           |             setting             |                sourcefile
--------------------------+---------------------------------+-------------------------------------------
 shared_preload_libraries | pg_cron		            | /data/postgresql/10/main2/postgresql.conf

> create extension pg_cron ;
CREATE EXTENSION


> select * from pg_extension;
 extname            | extowner | extnamespace | extrelocatable | extversion |   extconfig   | extcondition
--------------------+----------+--------------+----------------+------------+---------------+--------------
 plpgsql            |       10 |           11 | f              | 1.0        | NULL          | NULL
 pg_stat_statements |       10 |         2200 | t              | 1.6        | NULL          | NULL
 pgcrypto           |       10 |         2200 | t              | 1.3        | NULL          | NULL
 pg_cron            |       10 |         2200 | f              | 1.2        | {24695,24693} | {"",""}

L’ordre de création de l’extension va déclarer un nouveau schéma nommé “cron” puis jouer les scripts SQL de création des objets relatifs à son utilisation.

 

L’utilisation

Afin de programmer une tâche , nous utiliserons la procédure “schedule” présente dans le schéma “cron” de notre base “capdata”.

Le modèle de planification d’une tâche est comme celui d’une crontab Linux :

 

L’exemple ci dessous crée une tâche qui va insérer une ligne dans la table “employes” chaque minute.

$ psql -U capdata -d capdata

> select * from employes;
 nom | poste | date_entree | societe
-----+-------+-------------+---------
(0 rows)

> select * from cron.schedule('* * * * *',$$insert into employes values ('Manu','DBA',now(),'Capdata')$$);
 schedule
----------
        3
(1 row)

> select * from cron.job;
 jobid | schedule  |                          command                           | nodename  | nodeport | database | username | active
-------+-----------+------------------------------------------------------------+-----------+----------+----------+----------+--------
     3 | * * * * * | insert into employes values ('Manu','DBA',now(),'Capdata') | localhost |     5432 | capdata  | capdata  | t
(1 row)

La table “job” que l’on interroge donne les renseignements sur les jobs en cours sur cette base, avec notamment les informations de connexions utilisées par le processus de scheduling (host, port, database). On s’assure que notre table “employes” est bien alimentée.

$ psql -U capdata -d capdata
> select now();
              now
-------------------------------
 2019-09-18 15:22:03.419635+00
(1 row)

> select * from employes;
 nom  | poste | date_entree | societe
------+-------+-------------+---------
 Manu | DBA   | 2019-09-18  | Capdata
 Manu | DBA   | 2019-09-18  | Capdata
 Manu | DBA   | 2019-09-18  | Capdata
 Manu | DBA   | 2019-09-18  | Capdata
 Manu | DBA   | 2019-09-18  | Capdata
(5 rows)

On pourra également créer une tâche qui supprime les lignes sur cette table

> select * from cron.schedule('* * * * *',$$delete from employes where nom='Manu'$$);
 schedule
----------
        4
(1 row)

> select * from cron.job;
 jobid | schedule  |                          command                           | nodename  | nodeport | database | username | active
-------+-----------+------------------------------------------------------------+-----------+----------+----------+----------+--------
     3 | * * * * * | insert into employes values ('Manu','DBA',now(),'Capdata') | localhost |     5432 | capdata  | capdata  | t
     4 | * * * * * | delete from employes where nom='Manu'                      | localhost |     5432 | capdata  | capdata  | t
(2 rows)

On peut tout aussi bien supprimer ces tâches via la procédure “unschedule”

> select * from cron.unschedule(3);

> select * from cron.unschedule(4);

> select * from cron.job;
jobid | schedule | command | nodename | nodeport | database | username | active
-------+----------+---------+----------+----------+----------+----------+--------
(0 rows)

Il est également possible de mettre en “pause” le process en allant modifier son état dans la table “job”. S’il l’on reprend notre ancien job de suppression de lignes

> select * from cron.job;
 jobid | schedule  |                command                | nodename  | nodeport | database | username | active
-------+-----------+---------------------------------------+-----------+----------+----------+----------+--------
     4 | * * * * * | delete from employes where nom='Manu' | localhost |     5432 | capdata  | capdata  | t
(1 row)

On voit que la colonne “active” est en fait un booléen qui définit si ce job est actif, ou non. Il s’agira alors de passer à FALSE ce job afin qu’il ne soit plus effectué, tout en conservant son utilisation si besoin par la suite.

> select * from cron.job;
jobid | schedule  |                 command               | nodename  | nodeport | database | username | active
------+-----------+---------------------------------------+-----------+----------+----------+----------+--------
    4 | * * * * * | delete from employes where nom='Manu' | localhost |     5432 | capdata  | capdata  | t
(1 row)


(postgres@[local]:5432) [capdata] > update cron.job set active='f' where jobid=4;
UPDATE 1

(postgres@[local]:5432) [capdata] > select * from cron.job;
jobid  | schedule  |               command                 | nodename  | nodeport | database | username | active
-------+-----------+---------------------------------------+-----------+----------+----------+----------+--------
     4 | * * * * * | delete from employes where nom='Manu' | localhost |   5432   | capdata  | capdata  | f
(1 row)

 

On s’assure par la suite que la table “employes” ne subit plus de suppression.

Afin d’avoir un visu sur tous les objets liés à cette extension, on peut interroger psql

 > \dx+ pg_cron
Objets dans l'extension « pg_cron »
Description d'objet
--------------------------------------
fonction cron.job_cache_invalidate()
fonction cron.schedule(text,text)
fonction cron.unschedule(bigint)
schéma cron
séquence cron.jobid_seq
table cron.job
(6 lignes)

 

🙂

Emmanuel RAMI

Continuez votre lecture sur le blog :

twitterlinkedinmail

Emmanuel RAMI

2 commentaires

  1. Slt, si j’ai bien pigé la chose il n’y a pas une base dédié à pg_cron mais plutôt un shema sur chaque bases ou on veut utiliser cette extension. Si c’est bien cela il faut donc déclarer dans le postgresql.conf toutes les bases qui vont bien dans “cron.database_name” ?

  2. Hello
    oui dans le postgresql.conf on doit declarer chaque base ou l’on souhaite utiliser l’extension :
    cron.database_name=’base1,base2,base3….”

    Cordialement

Laisser un commentaire

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

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