PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 17.1 » Interfaces client » libpq -- Bibliothèque C » Mode pipeline

32.5. Mode pipeline #

Le mode pipeline de libpq permet aux applications d'envoyer une requête sans avoir à lire le résultat de la requête précédemment envoyée. En utilisant le mode pipeline, un client attendra moins le serveur car plusieurs requêtes/résultats peuvent être envoyés/reçus dans une seule transaction réseau.

Alors que le mode pipeline offre une amélioration significative des performances, les clients en écriture utilisant le mode pipeline sont plus complexes car cela implique de gérer une queue de requêtes en cours et de trouver quel résultat correspond à quelle requête dans la queue.

Le mode pipeline consomme généralement aussi plus de mémoire sur le client et le serveur, bien qu'une gestion attentionnée et agressive de la queue d'envoi/réception peut mitiger cela. Ceci s'applique que la connexion soit ou non en mode blocage ou non blocage.

Alors que l'API de pipeline de libpq a été introduite dans PostgreSQL 14, c'est une fonctionnalité du côté client qui ne requiert pas de support spécial côté serveur, et fonctionne avec tout serveur qui supporte le protocole de requêtes étendu v3. Pour plus d'informations, voir Section 53.2.4.

32.5.1. Utiliser le mode pipeline #

Pour exécuter des pipelines, l'application doit basculer la connexion en mode pipeline, ce qui se fait avec PQenterPipelineMode. PQpipelineStatus peut être utilisé pour tester si le mode pipeline est actif. Dans le mode pipeline, seules les opérations asynchrones qui utilisent le protocole de requête étendue sont autorisées, les chaînes de commandes contenant plusieurs commandes SQL et COPY sont interdits. Utiliser les fonctions d'exécution de commandes synchrones telles que PQfn, PQexec, PQexecParams, PQprepare, PQexecPrepared, PQdescribePrepared, PQdescribePortal, PQclosePrepared, PQclosePortal, renvoie une erreur. PQsendQuery est aussi interdite parce qu'elle utilise le protocole de requête simple. Une fois que toutes les commandes envoyées ont eu leurs résultats traités, l'application peut retourner dans le mode sans pipeline avec PQexitPipelineMode.

Note

Il est préférable d'utiliser le mode pipeline avec libpq dans le mode sans blocage. Si utilisé en mode blocage, il est possible qu'un deadlock client/serveur survienne. [15]

32.5.1.1. Exécuter des requêtes #

Après être entré en mode pipeline, l'application envoie les requêtes en utilisant PQsendQueryParams ou un des équivalents pour requêtes préparées PQsendQueryPrepared. Ces requêtes sont mises en queue côté client jusqu'au vidage du serveur ; ceci survient quand PQpipelineSync est utilisé pour établir un point de synchronisation dans le pipeline ou quand PQflush est appelé. Les fonctions PQsendPrepare, PQsendDescribePrepared, PQsendDescribePortal, PQsendClosePrepared et PQsendClosePortal fonctionnent aussi en mode pipeline. Le traitement des résultats est décrit ci-dessous.

Le serveur exécute les requêtes et envoie les résultats dans l'ordre dans lequel le client les a envoyé. Le serveur commencera immédiatement l'exécution des commandes dans le pipeline. Notez que les résultats sont mis en tampon du côté du serveur ; le serveur vide ce tampon quand un point de synchronisation est établi avec soit PQpipelineSync soit PQsendPipelineSync, ou quand PQsendFlushRequest est appelé. Si l'exécution d'une requête rencontre une erreur, le serveur annule la transaction en cours et n'exécute pas toute autre commande dans la queue jusqu'au prochain point de synchronisation ; un résultat PGRES_PIPELINE_ABORTED est produit pour chacune de ces commandes. (Ceci reste vrai même si les commandes dans le pipeline vont annuler la transaction.) Le traitement des requêtes continue après le point de synchronisation.

Il est possible qu'une opération dépende des résultats d'une opération précédente ; par exemple, une requête pourrait définir une table que la prochaine requête du même pipeline utilise. De façon similaire, une application pourrait créer une requête préparée nommée et l'exécuter dans les requêtes suivantes du même pipeline.

32.5.1.2. Traitement des résultats #

Pour traiter le résultat d'une requête dans un pipeline, l'application appelle PQgetResult de façon répété et gère chaque résultat jusqu'à ce que PQgetResult renvoie null. Le résultat de la prochaine requête dans le pipeline sera alors récupéré en utilisant de nouveau PQgetResult et le cycle sera répété. L'application gère les résultats de requête individuelle comme d'habitude. Quand les résultats de toutes les requêtes du pipeline ont été renvoyés, PQgetResult renvoie un résultat contenant la valeur de statut PGRES_PIPELINE_SYNC

Le client peut choisir de différer le traitement des résultats jusqu'à ce que le pipeline complet ait été envoyé ou de continuer en envoyant d'autres requêtes dans le pipeline ; voir Section 32.5.1.4.

PQgetResult se comporte comme pour le traitement asynchrone normal, sauf qu'il peut contenir les nouveaux types PGresult, à savoir PGRES_PIPELINE_SYNC et PGRES_PIPELINE_ABORTED. PGRES_PIPELINE_SYNC est indiqué exactement une fois pour chaque PQpipelineSync ou PQsendPipelineSync au point correspondant dans le pipeline. PGRES_PIPELINE_ABORTED est émis à la place d'un résultat de requête normal pour la première erreur et tous les résultats suivants jusqu'au prochain PGRES_PIPELINE_SYNC ; voir Section 32.5.1.3.

PQisBusy, PQconsumeInput, etc opèrent normalement lors du traitement ds résultats du pipeline. En particulier, un appel à PQisBusy au milieu d'un pipeline renvoie 0 si les résultats de toutes les requêtes lancées jusqu'à maintenant ont été consommés.

libpq ne fournit pas d'informations à l'application quant à la requête en cours de traitement (sauf que PQgetResult renvoie null pour indiquer que nous commençons l'envoi des résultats de la requête suivante). L'application doit garder la trace de l'ordre dans lequel elle a envoyé les requête, pour les associer avec les résultats correspondants. Les applications utiliseront typiquement une machine à état ou une queue FIFO pur ça.

32.5.1.3. Gestion des erreurs #

Du point de vue du client, après que PQresultStatus renvoie PGRES_FATAL_ERROR, le pipeline est considéré comme annulé. PQresultStatus renverra un résultat PGRES_PIPELINE_ABORTED pour chaque opération restant en queue dans un pipeline annulé. Le résultat pour PQpipelineSync ou PQsendPipelineSync est rapporté comme PGRES_PIPELINE_SYNC pour signaler la fin du pipeline annulé et le retour au traitement normal des résultats.

Le client doit traiterles résultats avec PQgetResult lors de la récupération après erreur.

Si le pipeline utilisait une transaction implicite, alors les opérations qui se sont déjà exécutées sont annulées et les opérations en queue après l'opération annulée sont complètement ignorées. Le même comportement reste si le pipeline commence et valide une seule transaction explicite (la première requête est BEGIN et la dernière est COMMIT) sauf que la session reste dans un état de transaction annulée à la fin du pipeline. Si un pipeline contient plusieurs transactions explicites, toutes les transactions validées avant l'erreur restent validées, la transaction actuellement en cours est annulée, et toutes les opérations suivantes sont complètement ignorées, ceci incluant les transactions suivantes. Si unpoint de synchronisation du pipeline survient avec un bloc de transaction explicite dans un état annulé, le pipeline suivant deviendra immédiatement annulé sauf si la commande suivante place la transaction en mode normal avec ROLLBACK.

Note

Le client ne doit pas supposer que le travail est validé quand il envoie un COMMIT -- seulment quand le résultat correspondant est reçu pour confirmer que la validation est terminée. Comme des erreurs arrivent en asynchrone, l'application a besoin d'être capable de recommencer depuis le dernier changement validé reçu et renvoie le travail effectué après ce point si quelque chose ne s'est pas bien passé.

32.5.1.4. Interleaving Result Processing and Query Dispatch #

Pour éviter des verrous sur des pipelines importants, le client devrait être structuré autour d'une boucle d'événements non bloquante en utilisant les possibilités du système d'exploitation, tel que select, poll, WaitForMultipleObjectEx, etc.

L'application doit généralement maintenir une queue de travail restant à déployer et une queue de travail qui a déjà été déployée mais dont les résultats n'ont pas encore été traités. Quand le socket est accessible en écriture, il pourrait déployer plus de travail. Quand le socket est lisible, il pourrait lire les résultats et les traiter, les faisant correspondre à la prochaine entrée dans la queue de résultats correspondante. Suivant la mémoire disponible, les résultats provenant du socket doivent être lus fréquemment : il n'est pas nécessaire d'attendre la fin du pipeline pour lire les résultats. Les pipelines doivent être placés en unités logiquesde travail, habituellement (mais pas nécessairement) une transaction par pipeline. Il n'est pas besoin de quitter le mode pipeline et de ré-entrer dans ce mode entre les pipelines ou d'autre la fin d'un pipeline avant d'envoyer le suivant.

Un exemple utilisant select() et une simple machine à état pour trace le travail envoyé et reçu se trouve dans src/test/modules/libpq_pipeline/libpq_pipeline.c à partir de la distribution des sources de PostgreSQL.

32.5.2. Fonctions associées avec le mode pipeline #

PQpipelineStatus #

Renvoie le statut actuel du mode pipeline de la connexion libpq.

PGpipelineStatus PQpipelineStatus(const PGconn *conn);

PQpipelineStatus peut renvoyer une des valeurs suivantes :

PQ_PIPELINE_ON

La connexion libpq est en mode pipeline.

PQ_PIPELINE_OFF

La connexion libpq n'est pas en mode pipeline.

PQ_PIPELINE_ABORTED

La connexion libpq est en mode pipeline et une erreur est survenue lors du traitement du pipeline actuel. Le drapeau d'annulation est effacé quand PQgetResult renvoie un résultat de type PGRES_PIPELINE_SYNC.

PQenterPipelineMode #

Cause l'entrée en mode pipeline de la connexion si elle est actuellement inactive ou déjà en mode pipeline.

int PQenterPipelineMode(PGconn *conn);

Renvoie 1 en cas de succès. Renvoie 0 et n'a pas d'effet si la connection est actuellement active, par exemple elle a un résultat prêt ou elle est en attente d'entrée du serveur, etc. Cette fonction n'envoie rien au serveur, elle modifie juste l'état de la connexion libpq.

PQexitPipelineMode #

Cause la sortie du mode pipeline pour la connexion si elle est en mode pipeline avec une queue vide et aucun résultat en attente.

int PQexitPipelineMode(PGconn *conn);

Renvoie 1 en cas de succès. Renvoie 1 et ne fait aucune action si la connexion n'est pas en mode pipeline. Si l'instruction en cours n'a pas terminé son traitement ou si PQgetResult n'a pas été appelé pour récupérer les résultats de toutes les requêtes envoyées précédemment, renvoie 0 (auquel cas, utilisez PQerrorMessage pour obtenir plus d'informations sur l'échec).

PQpipelineSync #

Marque un point de synchronisation dans un pipeline en envoyant un message sync et en vidant le tampon d'envoi. Ceci sert comme délimiteur d'une transaction implicite et d'un point de restauration après erreur ;voir Section 32.5.1.3.

int PQpipelineSync(PGconn *conn);

Renvoie 1 en cas de succès. Renvoie 0 si la connexion n'est pas dans le mode pipeline ou est en cours d'envoi d'un message sync en échec.

PQsendPipelineSync #

Marque un point de synchronisation dans un pipeline en envoyant un message sync sans vider le tampon d'envoi. Cela sert comme délimiteur d'une transaction implicite et comme point de récupération d'erreur ; voir Section 32.5.1.3.

int PQsendPipelineSync(PGconn *conn);

Renvoie 1 en cas de succès. Renvoie 0 si la connexion n'est pas en mode pipeline ou si l'envoi du message sync a échoué. Notez que le message n'est pas lui-même vidé automatiquement du serveur ; utilisez PQflush si nécessaire.

PQsendFlushRequest #

Envoie une requête au serveur pour vider son tampon en sortie.

int PQsendFlushRequest(PGconn *conn);

Renvoie 1 en cas de succès. Renvoie 0 en cas d'échec.

Le serveur vide son tampon en sortie automatiquement comme conséquence de l'appel à PQpipelineSync ou pour toute requête quand la connexion n'utilise pas le mode pipeline ; cette fonction est utile pour forcer le serveur à vider son tampon en sortie dans le mode pipeline sans établir de point de synchronisation. Notez que la requête n'est pas elle-même vidée du serveur automatiquement ; utilisez PQflush si nécessaire.

32.5.3. Quand utiliser le mode pipeline #

Tout comme le mode de requête asynchrone, il n'y a pas de surcharge significative pour les performances lors de l'utilisation du mode pipeline. Il augmente la complexité de l'application cliente, et une grande attention est requise pour éviter les deadlocks client/serveur, mais le mode pipeline peut offrir des améliorations considérables des performances, en échange d'une utilisation mémoire accrue.

Le mode pipeline est plus utile quand le serveur est distant, autrement dit quand la latence réseau (« durée du ping ») est importante, et aussi quand plusieurs petites opérations sont en cours d'exécution en succession rapide. Il y a moins d'intérêt à utiliser les commandes en pipeline quand chaque requête prend plusieurs fois le temps des aller/retours client/serveur pour s'exécuter. Une exécution d'une opération de 100 instructions sur un serveur avec des aller/retour à 300 ms va prendre 30 secondes de latence réseau sans pipeline. Avec le pipeline, celle pourrait prendre 0,3 seconde à attendre les résultats du serveur.

Utilisez les commandes de pipeline quand votre application fait plein de petites opérations INSERT, UPDATE et DELETE qui ne peuvent pas facilement être transformées en opérations sur des ensembles ou en une opération COPY.

Le mode pipeline n'est pas utile quand l'information d'une opération est requise par le client pour produire la prochaine opération. Dans de tels cas, le client devra introduire un point de synchronisation et attendre un aller/retour complet client/serveur pour obtenir le résultat nécessaire. Néanmoins, il est souvent possible d'ajuster la conception du client pour échanger les informations requises côté serveur. Les cycles lecture/modification/écriture sont tout spécialement de bon candidat ; par exemple :

BEGIN;
SELECT x FROM mytable WHERE id = 42 FOR UPDATE;
-- result: x=2
-- client adds 1 to x:
UPDATE mytable SET x = 3 WHERE id = 42;
COMMIT;

pourrait être réalisé plus efficacement avec :

UPDATE mytable SET x = x + 1 WHERE id = 42;

Envoyer dans un pipeline est moins utile et plus complexe quand un seul pipeline contient plusieurs transactions (voir Section 32.5.1.3).



[15] Le client bloquera les tentatives d'envoyer des requêtes au serveur, mais le serveur bloquera en essayant d'envoyer les résultats au client des requêtes qu'il a déjà traité. Ceci survient seulement quand le client envoie suffisamment de requêtes pour remplir à la fois son tampon en sortie et le tampon de réception du serveur avant qu'il bascule pour traiter les entrées du serveur, mais il est difficile de prédire exactement quand cela arrivera.