PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 17.1 » Internes » Écrire un wrapper de données distantes » Planification de la requête avec un wrapper de données distantes

57.4. Planification de la requête avec un wrapper de données distantes #

Les fonctions d'appels d'un wrapper de données distantes, GetForeignRelSize, GetForeignPaths, GetForeignPlan, PlanForeignModify, GetForeignJoinPaths, GetForeignUpperPaths et PlanDirectModify doivent s'intégrer au fonctionnement du planificateur de PostgreSQL. Voici quelques notes sur ce qu'elles doivent faire.

Les informations dans root et baserel peuvent être utilisées pour réduire la quantité d'informations qui doivent être récupérées sur la table distante (et donc réduire le coût) baserel->baserestrictinfo est tout particulièrement intéressant car il contient les qualificatifs de restriction (clauses WHERE) qui doivent être utilisées pour filtrer les lignes à récupérer. (Le wrapper lui-même n'est pas requis de respecter ces clauses car l'exécuteur du moteur peut les vérifier à sa place.) baserel->reltargetlist peut être utilisé pour déterminer les colonnes à récupérer ; mais notez qu'il liste seulement les colonnes qui doivent être émises par le nœud ForeignScan, et non pas les colonnes qui sont utilisées pour satisfaire l'évaluation des qualificatifs et non renvoyées par la requête.

Divers champs privés sont disponibles pour que les fonctions de planification du wrapper de données distantes conservent les informations. Habituellement, tout ce que vous stockez dans les champs privées doit avoir été alloué avec la fonction palloc, pour que l'espace soit récupéré à la fin de la planification.

baserel->fdw_private est un pointeur void disponible pour que les fonctions de planification du wrapper y stockent des informations correspondant à la table distante spécifique. Le planificateur du moteur n'y touche pas sauf lors de son initialisation à NULL quand le nœud RelOptInfo est créé. Il est utile de passer des informations de GetForeignRelSize à GetForeignPaths et/ou GetForeignPaths à GetForeignPlan, évitant du coup un recalcul.

GetForeignPaths peut identifier la signification de chemins d'accès différents pour enregistrer des informations privées dans le champ fdw_private des nœuds ForeignPath. fdw_private est déclaré comme un pointeur List mais peut contenir réellement n'importe quoi car le planificateur du moteur n'y touche pas. Néanmoins, une bonne pratique est d'utiliser une représentation qui est affichable par nodeToString, pour son utilisation avec le support du débogage disponible dans le processus.

GetForeignPlan peut examiner le champ fdw_private du nœud ForeignPath, et peut générer les listes fdw_exprs et fdw_private à placer dans le nœud de plan ForeignScan, où elles seront disponibles au moment de l'exécution. Les deux listes doivent être représentées sous une forme que copyObject sait copier. La liste fdw_private n'a pas d'autres restrictions et n'est pas interprétée par le processus moteur. La liste fdw_exprs, si non NULL, devrait contenir les arbres d'expressions qui devront être exécutées. Ces arbres passeront par un post-traitement par le planificateur qui les rend complètement exécutables.

Dans GetForeignPlan, habituellement, la liste cible fournie peut être copiée dans le nœud du plan tel quel. La liste scan_clauses fournie contient les mêmes clauses que baserel->baserestrictinfo mais ces clauses pourraient être ré-ordonnées pour une meilleure efficacité à l'exécution. Dans les cas simples, le wrapper peut seulement supprimer les nœuds RestrictInfo de la liste scan_clauses (en utilisant extract_actual_clauses) et placer toutes les clauses dans la liste des qualificatifs du nœud. Cela signifie que toutes les clauses seront vérifiées par l'exécuteur au moment de l'exécution. Les wrappers les plus complexes peuvent être capables de vérifier certaines clauses en interne, auquel cas ces clauses peuvent être supprimées de la liste de qualificatifs du nœud du plan pour que le planificateur ne perde pas de temps à les vérifier de nouveau.

Comme exemple, le wrapper peut identifier certaines clauses de restriction de la forme variable_distante = sous_expression, qui, d'après lui, peut être exécuté sir le serveur distant en donnant la valeur évaluée localement de la sous_expression. L'identification réelle d'une telle clause doit survenir lors de l'exécution de GetForeignPaths car cela va affecter l'estimation ddu coût pour le chemin. Le champ fdw_private du chemin pourrait probablement inclure un pointeur vers le nœud RestrictInfo de la clause identifiée. Puis, GetForeignPlan pourrait supprimer cette clause de scan_clauses et ajouter la sous_expression à fdw_exprs pour s'assurer qu'elle soit convertie en une forme exécutable. Il pourrait aussi placer des informations de contrôle dans le champ fdw_private du nœud pour dire aux fonctions d'exécution ce qu'il faudra faire au moment de l'exécution. La requête transmise au serveur distant va impliquer quelque chose comme WHERE variable_distante = $1, avec la valeur du paramètre obtenu à l'exécution à partir de l'évaluation de l'arbre d'expression fdw_exprs.

Toutes les clauses enlevées de la liste des qualificatifs du nœud du plan doivent être à la place ajoutées à fdw_recheck_quals ou vérifiées à nouveau par RecheckForeignScan pour permettre un fonctionnement correct au niveau d'isolation READ COMMITED. Lorsqu'une mise à jour concurrente survient pour une autre table concernée par la requête, l'exécuteur peut avoir besoin de vérifier que tous les qualificatifs originaux sont encore satisfaits pour la ligne, éventuellement avec un ensemble différent de valeurs pour les paramètres. L'utilisation de fdw_recheck_quals est typiquement plus facile que de mettre en place les vérifications à l'intérieur de RecheckForeignScan, mais cette méthode sera insuffisante lorsque des jointures externes ont été poussées, dans la mesure où les lignes jointes dans ce cas peuvent avoir certaines colonnes à NULL sans rejeter la ligne entièrement.

Un autre champ ForeignScan qui peut être rempli par les FDW est fdw_scan_tlist, qui décrit les lignes renvoyées par le FDW pour ce nœud du plan. Pour les parcours simples de tables distantes, il peut être positionné à NIL, impliquant que les lignes renvoyées ont le type de ligne déclaré pour la table distante. Une valeur différente de NIL doit être une liste cible (liste de TargetEntry) contenant des variables et/ou expressions représentant les colonnes renvoyées. Ceci peut être utilisé, par exemple, pour montrer que le FDW a omis certaines colonnes qu'il a noté comme non nécessaire à la requête. Aussi, si le FDW peut calculer des expressions utilisées par la requête de manière moins coûteuse que localement, il pourrait ajouter ces expressions à fdw_scan_tlist. Notez que les plans de jointure (créés à partir des chemins construits par GetForeignJoinPaths) doivent toujours fournir fdw_scand_tlist pour décrire l'ensemble des colonnes qu'ils retourneront.

Le wrapper de données distantes devrait toujours construire au moins un chemin qui dépend seulement des clauses de restriction de la table. Dans les requêtes de jointure, il pourrait aussi choisir de construire des chemins qui dépendent des clauses de jointures. Par exemple, variable_distante = variable_local. De telles clauses ne se trouveront pas dans baserel->baserestrictinfo mais doivent être dans les listes de jointures des relations. Un chemin utilisant une telle clause est appelé un « parameterized path ». Il doit identifier les autres relations utilisées dans le(s) clause(s) de jointure sélectionnée(s) avec une valeur convenable pour param_info ; utilisez get_baserel_parampathinfo pour calculer cette valeur. Dans GetForeignPlan, la portion local_variable de la clause de jointure pourra être ajoutée à fdw_exprs, et ensuite à l'exécution, cela fonctionne de la même façon que pour une clause de restriction standard.

Si un FDW supporte les jointures distantes, GetForeignJoinPaths devrait produire ForeignPath pour les jointures distantes potentielles essentiellement de la même manière que GetForeignPaths le fait pour les tables de base. L'information à propos de la jointure envisagée peut être passée à GetForeignPlan de la même manière que décrit ci-dessus. Cependant, baserestrictinfo n'est pas applicable pour les tables d'une jointure ; à la place, les clauses de jointure applicables pour une jointure particulière sont passées à GetForeignJoinPaths comme un paramètre séparé (extra->restrictlist).

Un FDW pourrait supporter en plus l'exécution direct de certaines actions d'un plan, qui sont au-dessus du niveau d'un parcours ou d'une jointure, comme par exemple un regroupement ou un agrégat. Pour proposer ce genre d'options, le FDW doit générer des chemins et les insérer dans la relation de niveau supérieur appropriée. Par exemple, un chemin représentant un agrégat distant doit être inséré dans la relation UPPERREL_GROUP_AGG, en utilisant add_path. Ce chemin sera comparé suivant son coût et celui d'un agrégat local réalisé en lisant un chemin de parcours simple de la relation externe (notez qu'un tel chemin doit aussi être fourni... dans le cas contraire, une erreur est renvoyée lors de l'optimisation). Si le chemin de l'agrégat distant gagne (ce qui sera généralement le cas), il sera converti en un plan standard en appelant GetForeignPlan. L'endroit recommendé pour générer de tels chemins est dans la fonction callback GetForeignUpperPaths, qui est appelée pour chaque relation supérieure (autrement dit, chaque étape de traitement post-parcours/jointure) si toutes les relations de base de la requête viennent du même FDW.

PlanForeignModify et les autres callbacks décrits dans Section 57.2.4 sont conçus autour de la supposition que la relation externe sera parcourue de la façon standard et qu'ensuite, les mises à jour individuelles de lignes seront réalisées par un nœud local ModifyTable. Cette approche est nécessaire dans le cas général où une mise à jour nécessite de lire des tables locales ainsi que des tables externes. Néanmoins, si l'opération pouvoit être exécutée entièrement par le serveur distant, le FDW pourrait générer un plan représentant cela et l'insérer dans la relation de niveau supérieur UPPERREL_FINAL, où il serait comparé avec l'approche ModifyTable. Cette approche pourrait être utilisé pour implémenter un SELECT FOR UPDATE distant, plutôt que d'utiliser les callbacks de verrouillage de ligne décrits dans Section 57.2.6. Gardez à l'exprit qu'un chemin inséré dans UPPERREL_FINAL est responsable de l'implémentation de tout le comportement de cette requête.

Lors de la planification d'un UPDATE ou d'un DELETE, PlanForeignModify et PlanDirectModify peuvent rechercher la structure RelOptInfo pour la table distante et utiliser la donnée baserel->fdw_private créée précédemment par les fonctions de planification de parcours. Néanmoins, pour un INSERT, la table cible n'est pas parcourue, donc il n'existe aucun RelOptInfo pour elle. La structure List renvoyée par PlanForeignModify a les mêmes restrictions que la liste fdw_private d'un nœud de plan ForeignScan, c'est-à-dire qu'elle doit contenir seulement les structures que copyObject sait copier.

Une commande INSERT avec une clause ON CONFLICT ne supporte pas la spécification d'une cible de conflit, dans la mesure où les contraintes uniques ou les contraintes d'exclusion sur les tables distantes ne sont pas localement connues. Ceci entraîne également que ON CONFLICT DO UPDATE n'est pas supporté car la spécification est obligatoire ici.