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.