PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 15.8 » Administration du serveur » Superviser l'activité de la base de données » Traces dynamiques

28.5. Traces dynamiques

PostgreSQL fournit un support pour les traces dynamiques du serveur de bases de données. Ceci permet l'appel à un outil externe à certains points du code pour tracer son exécution.

Un certain nombre de sondes et de points de traçage sont déjà insérés dans le code source. Ces sondes ont pour but d'être utilisées par des développeurs et des administrateurs de base de données. Par défaut, les sondes ne sont pas compilées dans PostgreSQL ; l'utilisateur a besoin de préciser explicitement au script configure de rendre disponible les sondes.

Actuellement, l'outil DTrace est supporté. Il est disponible sur Solaris, macOS, FreeBSD, NetBSD et Oracle Linux. Le projet SystemTap fournit un équivalent DTrace et peut aussi être utilisé. Le support d'autres outils de traces dynamiques est possible théoriquement en modifiant les définitions des macros dans src/include/utils/probes.h.

28.5.1. Compiler en activant les traces dynamiques

Par défaut, les sondes ne sont pas disponibles, donc vous aurez besoin d'indiquer explicitement au script configure de les activer dans PostgreSQL. Pour inclure le support de DTrace, ajoutez --enable-dtrace aux options de configure. Lire Section 17.4 pour plus d'informations.

28.5.2. Sondes disponibles

Un certain nombre de sondes standards sont fournies dans le code source, comme indiqué dans Tableau 28.47. Tableau 28.48 précise les types utilisés dans les sondes. D'autres peuvent être ajoutées pour améliorer la surveillance de PostgreSQL.

Tableau 28.47. Sondes disponibles pour DTrace

NomParamètresAperçu
transaction-start(LocalTransactionId)Sonde qui se déclenche au lancement d'une nouvelle transaction. arg0 est l'identifiant de transaction
transaction-commit(LocalTransactionId)Sonde qui se déclenche quand une transaction se termine avec succès. arg0 est l'identifiant de transaction
transaction-abort(LocalTransactionId)Sonde qui se déclenche quand une transaction échoue. arg0 est l'identifiant de transaction
query-start(const char *)Sonde qui se déclenche lorsque le traitement d'une requête commence. arg0 est la requête
query-done(const char *)Sonde qui se déclenche lorsque le traitement d'une requête se termine. arg0 est la requête
query-parse-start(const char *)Sonde qui se déclenche lorsque l'analyse d'une requête commence. arg0 est la requête
query-parse-done(const char *)Sonde qui se déclenche lorsque l'analyse d'une requête se termine. arg0 est la requête
query-rewrite-start(const char *)Sonde qui se déclenche lorsque la ré-écriture d'une requête commence. arg0 est la requête
query-rewrite-done(const char *)Sonde qui se déclenche lorsque la ré-écriture d'une requête se termine. arg0 est la requête
query-plan-start()Sonde qui se déclenche lorsque la planification d'une requête commence
query-plan-done()Sonde qui se déclenche lorsque la planification d'une requête se termine
query-execute-start()Sonde qui se déclenche lorsque l'exécution d'une requête commence
query-execute-done()Sonde qui se déclenche lorsque l'exécution d'une requête se termine
statement-status(const char *)Sonde qui se déclenche à chaque fois que le processus serveur met à jour son statut dans pg_stat_activity.status. arg0 est la nouvelle chaîne de statut
checkpoint-start(int)Sonde qui se déclenche quand un point de retournement commence son exécution. arg0 détient les drapeaux bit à bit utilisés pour distinguer les différents types de points de retournement, comme un point suite à un arrêt, un point immédiat ou un point forcé
checkpoint-done(int, int, int, int, int)Sonde qui se déclenche quand un point de retournement a terminé son exécution (les sondes listées après se déclenchent en séquence lors du traitement d'un point de retournement). arg0 est le nombre de tampons mémoires écrits. arg1 est le nombre total de tampons mémoires. arg2, arg3 et arg4 contiennent respectivement le nombre de journaux de transactions ajoutés, supprimés et recyclés
clog-checkpoint-start(bool)Sonde qui se déclenche quand la portion CLOG d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt
clog-checkpoint-done(bool)Sonde qui se déclenche quand la portion CLOG d'un point de retournement commence. arg0 a la même signification que pour clog-checkpoint-start
subtrans-checkpoint-start(bool)Sonde qui se déclenche quand la portion SUBTRANS d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt
subtrans-checkpoint-done(bool)Sonde qui se déclenche quand la portion SUBTRANS d'un point de retournement se termine. arg0 a la même signification que pour subtrans-checkpoint-start
multixact-checkpoint-start(bool)Sonde qui se déclenche quand la portion MultiXact d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt
multixact-checkpoint-done(bool)Sonde qui se déclenche quand la portion MultiXact d'un point de retournement se termine. arg0 a la même signification que pour multixact-checkpoint-start
buffer-checkpoint-start(int)Sonde qui se déclenche quand la portion d'écriture de tampons d'un point de retournement commence. arg0 contient les drapeaux bit à bit pour distinguer différents types de point de retournement comme le point après arrêt, un point immédiat, un point forcé
buffer-sync-start(int, int)Sonde qui se déclenche quand nous commençons d'écrire les tampons modifiés pendant un point de retournement (après identification des tampons qui doivent être écrits). arg0 est le nombre total de tampons. arg1 est le nombre de tampons qui sont modifiés et n'ont pas besoin d'être écrits
buffer-sync-written(int)Sonde qui se déclenche après chaque écriture d'un tampon lors d'un point de retournement. arg0 est le numéro d'identifiant du tampon
buffer-sync-done(int, int, int)Sonde qui se déclenche quand tous les tampons modifiés ont été écrits. arg0 est le nombre total de tampons. arg1 est le nombre de tampons réellement écrits par le processus de point de retournement. arg2 est le nombre attendu de tampons à écrire (arg1 de buffer-sync-start) ; toute différence reflète d'autres processus écrivant des tampons lors du point de retournement
buffer-checkpoint-sync-start()Sonde qui se déclenche une fois les tampons modifiés écrits par le noyau et avant de commencer à lancer des requêtes fsync
buffer-checkpoint-done()Sonde qui se déclenche après la fin de la synchronisation des tampons sur le disque
twophase-checkpoint-start()Sonde qui se déclenche quand la portion deux-phases d'un point de retournement est commencée
twophase-checkpoint-done()Sonde qui se déclenche quand la portion deux-phases d'un point de retournement est terminée
buffer-read-start(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool)Sonde qui se déclenche quand la lecture d'un tampon commence. arg0 et arg1 contiennent les numéros de fork et de bloc de la page (arg1 vaudra -1 s'il s'agit de demande d'extension de la relation). arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est true pour une demande d'extension de la relation, false pour une lecture ordinaire
buffer-read-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool)Sonde qui se déclenche quand la lecture d'un tampon se termine. arg0 et arg1 contiennent les numéros de fork et de bloc de la page (arg1 contient le numéro de bloc du nouveau bloc ajouté s'il s'agit de demande d'extension de la relation). arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est true pour une demande d'extension de la relation, false pour une lecture ordinaire. arg7 est true si la tampon était disponible en mémoire, false sinon
buffer-flush-start(ForkNumber, BlockNumber, Oid, Oid, Oid)Sonde qui se déclenche avant de lancer une demande d'écriture pour un bloc partagé. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation
buffer-flush-done(ForkNumber, BlockNumber, Oid, Oid, Oid)Sonde qui se déclenche quand une demande d'écriture se termine. (Notez que ceci ne reflète que le temps passé pour fournir la donnée au noyau ; ce n'est habituellement pas encore écrit sur le disque.) Les arguments sont identiques à ceux de buffer-flush-start
buffer-write-dirty-start(ForkNumber, BlockNumber, Oid, Oid, Oid)Sonde qui se déclenche quand un processus serveur commence à écrire un tampon modifié sur disque. Si cela arrive souvent, cela implique que shared_buffers est trop petit ou que les paramètres de contrôle de bgwriter ont besoin d'un ajustement.) arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation
buffer-write-dirty-done(ForkNumber, BlockNumber, Oid, Oid, Oid)Sonde qui se déclenche quand l'écriture d'un tampon modifié est terminé. Les arguments sont identiques à ceux de buffer-write-dirty-start
wal-buffer-write-dirty-start()Sonde qui se déclenche quand un processus serveur commence à écrire un tampon modifié d'un journal de transactions parce qu'il n'y a plus d'espace disponible dans le cache des journaux de transactions. (Si cela arrive souvent, cela implique que wal_buffers est trop petit.)
wal-buffer-write-dirty-done()Sonde qui se déclenche quand l'écriture d'un tampon modifié d'un journal de transactions est terminée
wal-insert(unsigned char, unsigned char)Sonde qui se déclenche quand un enregistrement est inséré dans un journal de transactions. arg0 est le gestionnaire de ressource (rmid) pour l'enregistrement. arg1 contient des informations supplémentaires
wal-switch()Sonde qui se déclenche quand une bascule du journal de transactions est demandée
smgr-md-read-start(ForkNumber, BlockNumber, Oid, Oid, Oid, int)Sonde qui se déclenche au début de la lecture d'un bloc d'une relation. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé
smgr-md-read-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)Sonde qui se déclenche à la fin de la lecture d'un bloc. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est le nombre d'octets réellement lus alors que arg7 est le nombre d'octets demandés (s'il y a une différence, il y a un problème)
smgr-md-write-start(ForkNumber, BlockNumber, Oid, Oid, Oid, int)Sonde qui se déclenche au début de l'écriture d'un bloc dans une relation. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé
smgr-md-write-done(ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int)Sonde qui se déclenche à la fin de l'écriture d'un bloc. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est le nombre d'octets réellement écrits alors que arg7 est le nombre d'octets demandés (si ces nombres sont différents, cela indique un problème)
sort-start(int, bool, int, int, bool, int)Sonde qui se déclenche quand une opération de tri est démarré. arg0 indique un tri de la table, de l'index ou d'un datum. arg1 est true si on force les valeurs uniques. arg2 est le nombre de colonnes clés. arg3 est le nombre de ko de mémoire autorisé pour ce travail. arg4 est true si un accès aléatoire au résultat du tri est requis arg5 indique serial si 0, parallel worker si 1, ou parallel leader si 2.
sort-done(bool, long)Sonde qui se déclenche quand un tri est terminé. arg0 est true pour un tri externe, false pour un tri interne. arg1 est le nombre de blocs disque utilisés pour un tri externe, ou le nombre de ko de mémoire utilisés pour un tri interne
lwlock-acquire(char *, LWLockMode)Sonde qui se déclenche quand un LWLock a été acquis. arg0 est la tranche de LWLock. arg1 est le mode de verrou attendu, soit exclusif soit partagé.
lwlock-release(char *)Sonde qui se déclenche quand un LWLock a été relâché (mais notez que tout processus en attente n'a pas encore été réveillé). arg0 est la tranche de LWLock.
lwlock-wait-start(char *, LWLockMode)Sonde qui se déclenche quand un LWLock n'était pas immédiatement disponible et qu'un processus serveur a commencé à attendre la disponibilité du verrou. arg0 est la tranche de LWLock. arg1 est le mode de verrou attendu, soit exclusif soit partagé.
lwlock-wait-done(char *, LWLockMode)Sonde qui se déclenche quand un processus serveur n'est plus en attente d'un LWLock (il n'a pas encore le verrou). arg0 est la tranche de LWLock. arg1 est le mode de verrou attendu, soit exclusif soit partagé.
lwlock-condacquire(char *, LWLockMode)Sonde qui se déclenche quand un LWLock a été acquis avec succès malgré le fait que l'appelant ait demandé de ne pas attendre. arg0 est la tranche de LWLock. arg1 est le mode de verrou attendu, soit exclusif soit partagé.
lwlock-condacquire-fail(char *, LWLockMode)Sonde qui se déclenche quand un LWLock, demandé sans attente, n'est pas accepté. arg0 est la tranche de LWLock. arg1 est le mode de verrou attendu, soit exclusif soit partagé.
lock-wait-start(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE)Sonde qui se déclenche quand une demande d'un gros verrou (lmgr lock) a commencé l'attente parce que le verrou n'était pas disponible. arg0 à arg3 sont les champs identifiant l'objet en cours de verrouillage. arg4 indique le type d'objet à verrouiller. arg5 indique le type du verrou demandé
lock-wait-done(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE)Sonde qui se déclenche quand une demande d'un gros verrou (lmgr lock) a fini d'attendre (c'est-à-dire que le verrou a été accepté). Les arguments sont identiques à ceux de lock-wait-start
deadlock-found()Sonde qui se déclenche quand un verrou mortel est trouvé par le détecteur

Tableau 28.48. Types définis utilisés comme paramètres de sonde

TypeDefinition
LocalTransactionIdunsigned int
LWLockModeint
LOCKMODEint
BlockNumberunsigned int
Oidunsigned int
ForkNumberint
boolunsigned char

28.5.3. Utiliser les sondes

L'exemple ci-dessous montre un script DTrace pour l'analyse du nombre de transactions sur le système, comme alternative à l'interrogation régulière de pg_stat_database avant et après un test de performance :

#!/usr/sbin/dtrace -qs

 postgresql$1:::transaction-start
 {
       @start["Start"] = count();
       self->ts  = timestamp;
 }

 postgresql$1:::transaction-abort
 {
       @abort["Abort"] = count();
 }

 postgresql$1:::transaction-commit
 /self->ts/
 {
       @commit["Commit"] = count();
       @time["Total time (ns)"] = sum(timestamp - self->ts);
       self->ts=0;
 }
    

À son exécution, le script de l'exemple D donne une sortie comme :

# ./txn_count.d `pgrep -n postgres` or ./txn_count.d <PID>
 ^C

 Start                                          71
 Commit                                         70
 Total time (ns)                        2312105013
    

Note

SystemTap utilise une notation différente de DTrace pour les scripts de trace, même si les points de trace sont compatibles. Il est intéressant de noter que, lorsque nous avons écrit ce texte, les scripts SystemTap doivent référencer les noms des sondes en utilisant des tirets bas doubles à la place des tirets simples. Il est prévu que les prochaines versions de SystemTap corrigent ce problème.

Vous devez vous rappeler que les programmes DTrace doivent être écrits soigneusement, sinon les informations récoltées pourraient ne rien valoir. Dans la plupart des cas où des problèmes sont découverts, c'est l'instrumentation qui est erronée, pas le système sous-jacent. En discutant des informations récupérées en utilisant un tel système, il est essentiel de s'assurer que le script utilisé est lui-aussi vérifié et discuter.

28.5.4. Définir de nouvelles sondes

De nouvelles sondes peuvent être définies dans le code partout où le développeur le souhaite bien que cela nécessite une nouvelle compilation. Voici les étapes nécessaires pour insérer de nouvelles sondes :

  1. Décider du nom de la sonde et des données nécessaires pour la sonde

  2. Ajoutez les définitions de sonde dans src/backend/utils/probes.d

  3. Inclure pg_trace.h s'il n'est pas déjà présent dans le module contenant les points de sonde, et insérer les macros TRACE_POSTGRESQL aux emplacements souhaités dans le code source

  4. Recompiler et vérifier que les nouvelles sondes sont disponibles

Exemple :  Voici un exemple d'ajout d'une sonde pour tracer toutes les nouvelles transactions par identifiant de transaction.

  1. La sonde sera nommée transaction-start et nécessite un paramètre de type LocalTransactionId

  2. Ajout de la définition de la sonde dans src/backend/utils/probes.d :

           probe transaction__start(LocalTransactionId);
          

    Notez l'utilisation du double tiret bas dans le nom de la sonde. Dans un script DTrace utilisant la sonde, le double tiret bas doit être remplacé par un tiret, donc transaction-start est le nom à documenter pour les utilisateurs.

  3. Au moment de la compilation, transaction__start est converti en une macro appelée TRACE_POSTGRESQL_TRANSACTION_START (notez que les tirets bas ne sont plus doubles ici), qui est disponible en incluant le fichier pg_trace.h. Il faut ajouter l'appel à la macro aux bons emplacements dans le code source. Dans ce cas, cela ressemble à :

         TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
          

  4. Après une nouvelle compilation et l'exécution du nouveau binaire, il faut vérifier que la nouvelle sonde est disponible en exécutant la commande DTrace suivante. Vous deviez avoir cette sortie :

     # dtrace -ln transaction-start
        ID    PROVIDER          MODULE           Fonction NAME
     18705 postgresql49878     postgres     StartTransactionCommand transaction-start
     18755 postgresql49877     postgres     StartTransactionCommand transaction-start
     18805 postgresql49876     postgres     StartTransactionCommand transaction-start
     18855 postgresql49875     postgres     StartTransactionCommand transaction-start
     18986 postgresql49873     postgres     StartTransactionCommand transaction-start
          

Il faut faire attention à d'autres choses lors de l'ajout de macros de trace dans le code C :

  • Vous devez faire attention au fait que les types de données indiqués pour les paramètres d'une sonde correspondent aux types de données des variables utilisées dans la macro. Dans le cas contraire, vous obtiendrez des erreurs de compilation.

  • Sur la plupart des plateformes, si PostgreSQL est construit avec --enable-dtrace, les arguments pour une macro de trace seront évalués à chaque fois que le contrôle passe dans la macro, même si aucun traçage n'est réellement en cours. Cela a généralement peu d'importance si vous rapportez seulement les valeurs de quelques variables locales, mais faites bien attention à l'utilisation de fonctions coûteuses. Si vous devez le faire, pensez à protéger la macro avec une vérification pour vous assurer que la trace est bien activée :

         if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED())
             TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
           

    Chaque macro de trace a une macro ENABLED correspondante.