Reviers, le 12 janvier 2024

La période entre Noël et le jour de l’an est une période où nous pouvons enfin trouver un peu de temps, ce temps qui nous a beaucoup manqué en fin d’année, entre un regroupement d’équipe en novembre, l’OSXP début décembre, et PGConf.EU mi-décembre. Bref. Il y a bien eu une quatrième journée interne de hacking sur PostgreSQL en octobre, en voici un petit compte-rendu.

Pour cette journée, il nous a semblé intéressant de nous plonger dans le code des processus exécutés par PostgreSQL.

Nous avons étudié le code de plusieurs de ces processus, notamment le « logger process » (gestion des fichiers de trace), l’« archiver process » (archivage des journaux de transactions) et l’autovacuum (nettoyage des fichiers de données, et calcul des statistiques sur les données).

Il serait difficile en un article d’aborder tous les processus. Les différents processus ayant tous le même squelette, nous allons donc nous limiter dans cet article à un seul, nommément le collecteur des traces (logger process). Une raison pour viser ce processus spécifique est l’écriture d’un petit patch pour ce dernier.

Fonctionnement du processus syslogger

PostgreSQL génère un ensemble de traces auxquelles il attribue des niveaux. La configuration permet à la fois d’augmenter ou de diminuer le niveau des traces, tout en indiquant où envoyer les traces. PostgreSQL dispose d’un processus, le logger process, dont le but, lorsqu’il est activé, est d’enregistrer les traces qu’il émet dans des fichiers et de gérer ces fichiers.

Le code de ce processus se trouve dans le fichier src/backend/postmaster/syslogger.c.

Tous les processus PostgreSQL disposent d’un point d’entrée principal, une fonction clé. Pour ce processus, il s’agit de la fonction SysLoggerMain().

Cette fonction commence par récupérer un certain nombre d’informations, comme les arguments en ligne de commande, et son type de processus. Elle utilise la fonction init_ps_display(), qui permet de modifier l’affichage de son nom pour les commandes systèmes comme ps.

Une partie réalisée par tous les processus est la mise en place de la gestion des signaux. Pour ce processus, seuls deux sont gérés, les autres sont ignorés. Le signal SIGHUP est utilisé pour avertir ce processus qu’il doit recharger la configuration. Le signal SIGUSR1 est utilisé pour demander à ce processus de procéder à un changement de fichier de trace. Par exemple, ce signal est envoyé lors de l’exécution de la fonction SQL pg_rotate_logfile, dont le code C est disponible dans le fichier src/backend/storage/ipc/signalfuncs.c.

Ceci fait, il enregistre quelques informations dans des variables :

  • nom du fichier de trace standard ;
  • nom du fichier de trace CSV ;
  • nom du fichier de trace JSON ;
  • nom complet du répertoire des traces.

Il calcule quand doit avoir lieu le prochain changement du fichier de trace. Pour cela, il utilise le paramètre de configuration log_rotation_age.

Ceci fait, il enregistre le nom des fichiers de trace actifs dans le fichier current_logfiles du répertoire principal des données de l’instance.

Il peut enfin entrer dans sa boucle principale.

La première action dans cette boucle est de vérifier si un utilisateur a demandé un rechargement de la configuration. Si c’est bien le cas, il recharge cette configuration. La nouvelle configuration peut avoir modifié le nom du fichier de trace ou le répertoire où stocker ce fichier, donc il vérifie s’ils ont changé et, dans ce cas, il ferme les anciens fichiers, ouvre les nouveaux et met à jour le fichier current_logfiles. Il est aussi possible que ce rechargement de la configuration a modifié la valeur du paramètre log_rotation_age. Dans ce cas, il faut de nouveau calculer le moment du prochain changement de fichier de trace.

Le processus vérifie ensuite si une demande de changement de fichier de trace a été envoyé. Si c’est le cas, il ferme l’ancien fichier et ouvre le nouveau, suivant la configuration du paramètre log_filename.

Après cela, il s’endort. Plusieurs événements peuvent le réveiller :

  • réception de traces à écrire dans les fichiers ;
  • réception du signal SIGHUP ou SIGUSR1 ;
  • dépassement du délai avant changement du fichier de traces.

Si le réveil du processus est dû à la réception de traces, ces dernières sont enregistrées dans le fichier de trace actuellement ouvert.

Dans les autres cas, la boucle revient au début de son exécution et ces cas sont gérés en début de boucle.

Voici rapidement comment est codé ce processus. Nous avons évidemment laissé des petits bouts ici ou là, mais nous en avons vu le principal. Le gros intérêt de lire le code d’un processus PostgreSQL, c’est de comprendre son fonctionnement et, surtout, de voir que ce n’est pas si compliqué. Évidemment, tout dépend du processus visé, certains étant plus complexes que les autres, notamment si on cherche à tout analyser. Cependant, cela reste assez clair et permet de mieux comprendre le comportement et le fonctionnement de PostgreSQL.

Petit patch sur ce processus

Cela permet aussi de l’améliorer si le besoin s’en fait sentir.

Il se trouve que j’ai toujours trouvé curieux que ce processus n’indique jamais de statut dans la liste des processus. La commande ps nous indique uniquement son nom :

$ ps -ef | grep "[p]ostgres.*logger"
postgres  6378  6373  0  11:24  ?  00:00:00  postgres: r16: logger 

J’aimerais beaucoup qu’il indique sur quel fichier il est en train de travailler. En soit, ce n’est pas si compliqué de le faire. Nous avons déjà vu plus haut qu’il existe une fonction, appelée init_ps_display() pour changer le nom du processus en cours d’exécution. Il suffit donc d’appeler cette fonction et de lui préciser le nom du fichier de trace. Cependant, il faut l’appeler à chaque changement de ce fichier de trace. Or, à chaque changement, la fonction update_metainfo_datafile() est appelée pour mettre à jour le fichier current_logfiles. Il suffit donc d’ajouter le code suivant dans cette fonction :

diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 858a2f6b2b..c91ba578e7 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -1526,6 +1526,7 @@ update_metainfo_datafile(void)
 {
        FILE       *fh;
        mode_t          oumask;
+       char            activitymsg[100];

        if (!(Log_destination & LOG_DESTINATION_STDERR) &&
                !(Log_destination & LOG_DESTINATION_CSVLOG) &&
@@ -1573,6 +1574,8 @@ update_metainfo_datafile(void)
                        fclose(fh);
                        return;
                }
+               snprintf(activitymsg, sizeof(activitymsg), "%s", last_sys_file_name);
+               set_ps_display(activitymsg);
        }

        if (last_csv_file_name && (Log_destination & LOG_DESTINATION_CSVLOG))

Après compilation, et installation de cette version personnalisée de PostgreSQL, voici ce que j’obtiens après redémarrage de PostgreSQL :

$ ps -ef | grep "[p]ostgres.*logger"
postgres  33364  33359  0  12:18  ?  00:00:00  postgres: r16: logger log/postgresql-2023-12-27_121827.log

Et si on change le fichier de trace après un appel à la fonction SQL pg_rotate_logfile(), le nom du processus change pour indiquer le nouveau fichier de trace :

$ psql -XAtqc "SELECT pg_rotate_logfile()"
t
$ ps -ef | grep "[p]ostgres.*logger"
postgres  33364  33359  0  12:18  ?  00:00:00  postgres: r16: logger log/postgresql-2023-12-27_121946.log

Conclusion

Ce patch est un peu trop simpliste. Notamment, il ne tient pas compte des formats CSV et JSON. Il ne serait donc pas accepté en l’état par la communauté PostgreSQL. Il montre néanmoins qu’il est possible de modifier le fonctionnement des processus PostgreSQL, pour peu qu’on ait un peu de temps pour appréhender le code des processus.


DALIBO

DALIBO est le spécialiste français de PostgreSQL®. Nous proposons du support, de la formation et du conseil depuis 2005.