7 Connexions distantes
Connexions distantes
Accès à distance à d’autres sources de données
- Modules historiques : dblink
- SQL/MED & Foreign Data Wrappers
- Sharding par fonctions : PL/Proxy
- Le sharding est Work In Progress
Nativement, lorsqu’un utilisateur est connecté à une base de données PostgreSQL, sa vision du monde est contenue hermétiquement dans cette base. Il n’a pas accès aux objets des autres bases de la même instance ou d’une autre instance.
Cependant, il existe principalement 3 méthodes pour accéder à des données externes à la base sous PostgreSQL.
La norme SQL/MED est la méthode recommandée pour accéder à des objets distants. Elle permet l’accès à de nombreuses sources de données différentes grâce l’utilisation de connecteurs appelés Foreign Data Wrappers.
Historiquement, les utilisateurs de PostgreSQL passaient par l’extension dblink, qui permet l’accès à des données externes. Cependant, cet accès ne concerne que des serveurs PostgreSQL. De plus, son utilisation prête facilement à accès moins performant et moins sécurisés que la norme SQL/MED.
PL/Proxy est un cas d’utilisation très différent : cette extension, au départ développée par Skype, permet de distribuer des appels de fonctions PL sur plusieurs nœuds.
Le sharding n’est pas intégré de manière simple à PostgreSQL dans sa version officielle. Il est tout de même possible de créer des partitions basées sur des tables distantes (donc avec SQL/MED), mais ce n’est qu’un début. Des éditeurs proposent des extensions, propriétaires ou expérimentales, ou des forks de PostgreSQL dédiés ; ou encore des produits simplement « compatibles » avec PostgreSQL. Comme souvent, il faut se poser la question du besoin réel par rapport à une instance PostgreSQL bien optimisée avant d’utiliser des outils qui vont ajouter une couche supplémentaire de complexité dans votre infrastructure.
SQL/MED
- Management of External Data
- Extension de la norme SQL ISO
- Données externes présentées comme des tables
- Grand nombre de fonctionnalités disponibles
- mais tous les connecteurs n’implémentent pas tout
- Données accessibles par l’intermédiaire de tables
- ces tables ne contiennent pas les données localement
- l’accès à ces tables provoque une récupération des données distantes
SQL/MED est un des tomes de la norme SQL, traitant de l’accès aux données externes (Management of External Data).
Elle fournit donc un certain nombre d’éléments conceptuels, et de syntaxe, permettant la déclaration d’accès à des données externes. Ces données externes sont bien sûr présentées comme des tables.
PostgreSQL suit cette norme et est ainsi capable de requêter des tables distantes à travers des pilotes (appelés Foreign Data Wrapper). Les seuls connecteurs livrés par défaut sont file_fdw (pour lire des fichiers plats de type CSV accessibles du serveur PostgreSQL) et postgres_fdw (qui permet de se connecter à un autre serveur PostgreSQL.
Objets proposés par SQL/MED
- Foreign Data Wrapper
- connecteur permettant la connexion à un serveur externe et l’exécution de requête
- Foreign Server
- serveur distant
- User Mapping
- correspondance d’utilisateur local vers distant
- Foreign Table
- table distante (ou table externe)
La norme SQL/MED définit quatre types d’objets.
Le Foreign Data Wrapper est le connecteur permettant la connexion à un serveur distant, l’exécution de requêtes sur ce serveur, et la récupération des résultats par l’intermédiaire d’une table distante.
Le Foreign Server est la définition d’un serveur distant. Il est lié à un Foreign Data Wrapper lors de sa création, des options sont disponibles pour indiquer le fichier ou l’adresse IP et le port, ainsi que d’autres informations d’importance pour le connecteur.
Un User Mapping permet de définir qui localement a le droit de se connecter sur un serveur distant en tant que tel utilisateur sur le serveur distant. La définition d’un User Mapping est optionnel.
Une Foreign Table contient la définition de la table distante : nom des colonnes, et type. Elle est liée à un Foreign Server.
Foreign Data Wrapper
- Pilote d’accès aux données
- Couverture variable des fonctionnalités
- Qualité variable
- Exemples de connecteurs
- PostgreSQL, SQLite, Oracle, MySQL (lecture/écriture)
- fichier CSV, fichier fixe (en lecture)
- ODBC, JDBC
- CouchDB, Redis (NoSQL)
- Disponible généralement sous la forme d’une extension
- ajouter l’extension ajoute le Foreign Data Wrapper à une base
Les trois Foreign Data Wrappers les plus aboutis sont sans conteste ceux pour PostgreSQL (disponible en module contrib), Oracle et SQLite. Ces trois pilotes supportent un grand nombre de fonctionnalités (si ce n’est pas toutes) de l’implémentation SQL/MED par PostgreSQL.
De nombreux pilotes spécialisés existent, entre autres pour accéder à des bases NoSQL comme MongDB, CouchDB ou Redis, ou à des fichiers.
Il existe aussi des drivers génériques :
- ODBC : utilisation de driver ODBC
- JDBC : utilisation de driver JDBC
La liste complète des Foreign Data Wrappers disponibles pour PostgreSQL peut être consultée sur le wiki de postgresql.org. Encore une fois, leur couverture des fonctionnalités disponibles est très variable ainsi que leur qualité. Il convient de rester prudent et de bien tester ces extensions.
Par exemple, pour ajouter le Foreign Data Wrapper pour PostgreSQL, on procédera ainsi :
La création cette extension dans une base provoquera l’ajout du Foreign Data Wrapper :
Fonctionnalités disponibles pour un FDW (1/2)
- Support des lecture de tables (
SELECT) - Support des écriture de tables (y compris
TRUNCATE)- directement pour
INSERT - récupération de la ligne en local pour un
UPDATE/DELETE
- directement pour
- Envoi sur le serveur distant
- des prédicats
- des jointures si les deux tables jointes font partie du même serveur distant
- des agrégations
L’implémentation SQL/MED permet l’ajout de ces fonctionnalités dans un Foreign Data Wrapper. Cependant, une majorité de ces fonctionnalités est optionnelle. Seule la lecture des données est obligatoire.
Les chapitres suivant montrent des exemples de ces fonctionnalités sur deux Foreign Data Wrappers.
Fonctionnalités disponibles pour un FDW (2/2)
- Mais aussi
- support du
EXPLAIN - support du
ANALYZE(amélioration en v16) - support des triggers
- support de la parallélisation
- support des exécutions asynchrones
- possibilité d’importer un schéma complet
- support du
Les Foreign Data Wrappers sont fréquement améliorés. La dernière optimisation en date concerne la gestion de la commande ANALYZE en version 16 de PostgreSQL.
Jusque PostgreSQL 15, lorsque ANALYZE était exécuté sur une table distante, l’échantillonnage était effectué localement à l’instance. Les données étaient donc intégralement rapatriées avant que ne soient effectuées les opérations d’échantillonnage. Pour des grosses tables, cette manière de faire était tout sauf optimisée. À partir de PostgreSQL 16, l’échantillonnage des lignes se fait par défaut sur le serveur distant grâce à l’option analyze_sampling. La volumétrie transférée est alors bien plus basse. Le calcul des statistiques des données sur cet échantillon se fait toujours sur l’instance qui lance ANALYZE.
Cette option peut prendre les valeurs off, auto, system, bernoulli et random. La valeur par défaut est auto qui permettra d’utiliser soit bernoulli (cas général) soit random (pour des serveurs distants plus anciens que PostgreSQL 9.5). Par exemple :
analyze_sampling peut être appliqué sur le foreign server ou la foreign table directement. sur la FOREIGN TABLE directement.
Il est possible de créer des triggers locaux sur des tables étrangères. Un trigger sur TRUNCATE n’est pas possible avant PostgreSQL 16.
Foreign Server
- Encapsule les informations de connexion
- Le Foreign Data Wrapper utilise ces informations pour la connexion
- Chaque Foreign Data Wrapper propose des options spécifiques
- nom du fichier pour un FDW listant des fichiers
- adresse IP, port, nom de base pour un serveur SQL
- autres
Pour accéder aux données d’un autre serveur, il faut pouvoir s’y connecter. Le Foreign Server regroupe les informations permettant cette connexion : par exemple adresse IP et port.
Voici un exemple d’ajout de serveur distant :
User Mapping
- Correspondance utilisateur local / utilisateur distant
- Mot de passe stocké chiffré
- Optionnel
- aucun intérêt pour les FDW fichiers
- essentiel pour les FDW de bases de données
Définir un User Mapping permet d’indiquer au Foreign Data Wrapper quel utilisateur utilisé pour la connexion au serveur distant.
Par exemple, avec cette définition :
Si l’utilisateur bob local accède à une table distante dépendant du serveur distant serveur2, la connexion au serveur distant passera par l’utilisateur alice sur le serveur distant.
Foreign Table
- Définit une table distante
- Doit comporter les colonnes du bon type
- pas forcément toutes
- pas forcément dans le même ordre
- Peut être une partition d’une table partitionnée
- Possibilité d’importer un schéma complet
- simplifie grandement la création des tables distantes
Voici un premier exemple pour une table simple :
Lors de l’accès (avec un SELECT par exemple) à la table films, PostgreSQL va chercher la définition du serveur serveur2, ce qui lui permettra de connaître le Foreign Data Wrapper responsable de la récupération des données et donnera la main à ce connecteur.
Et voici un second exemple, cette fois pour une partition :
Dans ce cas, l’accès à la table partitionnée locale stock accédera à des données locales (les autres partitions) mais aussi à des données distantes avec au moins la partition stock202112.
Cette étape de création des tables distantes est fastidieuse et peut amener des problèmes si on se trompe sur le nom des colonnes ou sur leur type. C’est d’autant plus vrai que le nombre de tables à créer est important. Dans ce cas, elle peut être avantageusement remplacée par un appel à l’ordre IMPORT FOREIGN SCHEMA. Disponible à partir de la version 9.5, il permet l’import d’un schéma complet.
Exemple : file_fdw
Foreign Data Wrapper de lecture de fichiers CSV.
Quel que soit le connecteur, la création d’un accès se fait en 3 étapes minimum :
- Installation du connecteur : aucun Foreign Data Wrapper n’est présent par défaut. Il se peut que vous ayez d’abord à l’installer sur le serveur au niveau du système d’exploitation.
- Création du serveur : permet de spécifier un certain nombre d’informations génériques à un serveur distant, qu’on n’aura pas à repréciser pour chaque objet de ce serveur.
- Création de la table distante : l’objet qu’on souhaite rendre visible.
Éventuellement, on peut vouloir créer un User Mapping, mais ce n’est pas nécessaire pour le FDW file_fdw.
En reprenant l’exemple ci-dessus et avec un fichier /tmp/fichier_donnees_statistiques.csv contenant les lignes suivantes :
Voici ce que donnerait quelques opérations sur cette table distante :
Exemple : postgres_fdw
- Pilote le plus abouti, et pour cause
- il permet de tester les nouvelles fonctionnalités de SQL/MED
- il sert d’exemple pour les autres FDW
- Propose en plus :
- une gestion des transactions explicites
- un pooler de connexions
Nous créons une table sur un serveur distant. Par simplicité, nous utiliserons le même serveur mais une base différente. Créons cette base et cette table :
Maintenant nous pouvons revenir à notre base d’origine et mettre en place la relation avec le « serveur distant » :
Et c’est tout ! Nous pouvons désormais utiliser la table distante personnes comme si elle était une table locale de notre base.
En plus, si nous filtrons notre requête, le filtre est exécuté sur le serveur distant, réduisant considérablement le trafic réseau et le traitement associé.
Noter qu’EXPLAIN exige l’option VERBOSE pour afficher le code envoyé à l’instance distante.
Il est possible d’écrire vers ces tables aussi, à condition que le connecteur FDW le permette.
En utilisant l’exemple de la section précédente, on note qu’il y a un aller-retour entre la sélection des lignes à modifier (ou supprimer) et la modification (suppression) de ces lignes :
On peut aussi constater que l’écriture distante respecte les transactions :
Attention à ne pas perdre de vue qu’une table distante n’est pas une table locale. L’accès à ses données est plus lent, surtout quand on souhaite récupérer de manière répétitive peu d’enregistrements : on a systématiquement une latence réseau, éventuellement une analyse de la requête envoyée au serveur distant, etc.
Les jointures ne sont pas « poussées » au serveur distant avant PostgreSQL 9.6 et pour des bases PostgreSQL. Un accès par Nested Loop (boucle imbriquée entre les deux tables) est habituellement inenvisageable entre deux tables distantes : la boucle interne (celle qui en local serait un accès à une table par index) entraînerait une requête individuelle par itération, ce qui serait horriblement peu performant.
Comme avec tout FDW, il existe des restrictions.
Les tables distantes sont donc à réserver à des accès intermittents. Il ne faut pas les utiliser pour développer une application transactionnelle par exemple. Noter qu’entre serveurs PostgreSQL, chaque version améliore les performances (notamment pour « pousser » le maximum d’informations et de critères au serveur distant).
SQL/MED : Performances
- Tous les FDW : vues matérialisées et indexations
- postgres_fdw :
fetch_size
Pour améliorer les performances lors de l’utilisation de Foreign Data Wrapper, une pratique courante est de faire une vue matérialisée de l’objet distant. Les données sont récupérées en bloc et cette vue matérialisée peut être indexée. C’est une sorte de mise en cache. Évidemment cela ne convient pas à toutes les applications.
La documentation de postgres_fdw mentionne plusieurs paramètres, et le plus intéressant pour des requêtes de gros volume est fetch_size : la valeur par défaut n’est que de 100, et l’augmenter permet de réduire les aller-retours à travers le réseau.
SQL/MED : héritage
- Une table locale peut hériter d’une table distante et inversement
- Permet le partitionnement sur plusieurs serveurs
- Pour rappel, l’héritage ne permet pas de conserver
- les contraintes d’unicité et référentielles
- les index
- les droits
Cette fonctionnalité utilise le mécanisme d’héritage de PostgreSQL.
Exemple d’une table locale qui hérite d’une table distante
La table parent (ici une table distante) sera la table fgn_stock_londre et la table enfant sera la table local_stock (locale). Ainsi la lecture de la table fgn_stock_londre retournera les enregistrements de la table fgn_stock_londre et de la table local_stock.
Sur l’instance distante :
Créer une table stock_londre sur l’instance distante dans la base nommée « cave » et insérer des valeurs :
Sur l’instance locale :
Créer le serveur et la correspondance des droits :
Créer une table distante fgn_stock_londre correspondant à la table stock_londre de l’autre instance :
On peut bien lire les données :
Voici le plan d’exécution associé :
Créer une table local_stock sur l’instance locale qui va hériter de la table mère :
On insère des valeurs dans la table local_stock :
La table local_stock ne contient bien que 2 valeurs :
En revanche, la table fgn_stock_londre ne contient plus 4 valeurs mais 6 valeurs :
Dans le plan d’exécution on remarque bien la lecture des deux tables :
Note : Les données de la table stock_londre sur l’instance distante n’ont pas été modifiées.
Exemple d’une table distante qui hérite d’une table locale
La table parent sera la table master_stock et la table fille (ici distante) sera la table fgn_stock_londre. Ainsi une lecture de la table master_stock retournera les valeurs de la table master_stock et de la table fgn_stock_londre, sachant qu’une lecture de la table fgn_stock_londre retourne les valeurs de la table fgn_stock_londre et local_stock. Une lecture de la table master_stock retournera les valeurs des 3 tables : master_stock, fgn_stock_londre, local_stock.
Créer une table master_stock, insérer des valeurs dedans :
Modifier la table fgn_stock_londre pour qu’elle hérite de la table master_stock :
La lecture de la table master_stock nous montre bien les valeurs des 3 tables :
Le plan d’exécution confirme bien la lecture des 3 tables :
Dans cet exemple, on a un héritage « imbriqué » :
- la table
master_stockest parent de la table distantefgn_stock_londre - la table distante
fgn_stock_londreest enfant de la tablemaster_stocket parent de la tablelocal_stock - ma table
local_stockest enfant de la table distantefgn_stock_londre
Créons un index sur master_stock et ajoutons des données dans la table master_stock :
Maintenant effectuons une simple requête de sélection :
Étudions le plan d’exécution associé :
L’index ne se fait que sur master_stock.
En ajoutant l’option ONLY après la clause FROM, on demande au moteur de n’afficher que la table master_stock et pas les tables filles :
Attention, si on supprime les données sur la table parent, la suppression se fait aussi sur les tables filles :
En revanche avec l’option ONLY, on ne supprime que les données de la table parent :
Enfin, si nous ajoutons une contrainte CHECK sur la table distante, l’exclusion de partition basées sur ces contraintes s’appliquent naturellement :
Attention : La contrainte CHECK sur fgn_stock_londre est locale seulement. Si cette contrainte n’existe pas sur la table distante, le résultat de la requête pourra alors être faux !
Sur le serveur distant :
Sur le serveur local :
dblink
- Permet le requêtage inter-bases PostgreSQL
- Simple et bien documenté
- En lecture seule sauf à écrire des triggers sur vue
- Ne transmet pas les prédicats
- tout l’objet est systématiquement récupéré
- Préférer
postgres_fdw
Le module dblink de PostgreSQL a une logique différente de SQL/MED : ce dernier crée des tables virtuelles qui masquent des accès distants, alors qu’avec dblink, une requête est fournie à une fonction, qui l’exécute à distance puis renvoie le résultat.
Voici un exemple d’utilisation :
L’appel à la fonction dblink() va réaliser une connexion à la base b1 et l’exécution de la requête indiquée dans le deuxième argument. Le résultat de cette requête est renvoyé comme résultat de la fonction. Noter qu’il faut nommer les champs obtenus.
Généralement, on encapsule l’appel à dblink() dans une vue, ce qui donnerait par exemple :
Un problème est que, rapidement, on ne se rappelle plus que c’est une table externe et que, même si le résultat contient peu de lignes, tout le contenu de la table distante est récupérés avant que le filtre ne soit exécuté. Donc même s’il y a un index qui aurait pu être utilisé pour ce prédicat, il ne pourra pas être utilisé. Il est rapidement difficile d’obtenir de bonnes performances avec cette extension.
Noter que dblink n’est pas aussi riche que son homonyme dans d’autres SGBD concurrents.
De plus, cette extension est un peu ancienne et ne bénéficie pas de nouvelles fonctionnalités sur les dernières versions de PostgreSQL. On préférera utiliser à la place l’implémentation de SQL/MED de PostgreSQL et le Foreign Data Wrapper postgres_fdw qui évoluent de concert à chaque version majeure et deviennent de plus en plus puissants au fil des versions. Cependant, dblink a encore l’intérêt d’émuler des transactions autonomes ou d’appeler des fonctions sur le serveur distant, ce qui est impossible directement avec postgres_fdw.
dblink fournit quelques fonctions plus évoluées que l’exemple ci-dessus, décrites dans la documentation.
PL/Proxy
- Langage de procédures
- développée à la base par Skype
- Fonctionnalités
- connexion à un serveur ou à un ensemble de serveurs
- exécution de fonctions, pas de requêtes
- Possibilité de distribuer les requêtes
- Utile pour le « partionnement horizontal »
- Uniquement si votre application n’utilise que des appels de fonction
- dans le cas contraire, il faut revoir l’application
PL/Proxy propose d’exécuter une fonction suivant un mode parmi trois :
- ANY : la fonction est exécutée sur un seul nœud au hasard
- ALL : la fonction est exécutée sur tous les nœuds
- EXACT : la fonction est exécutée sur un nœud précis, défini dans le corps de la fonction
On peut mettre en place un ensemble de fonctions PL/Proxy pour « découper » une table volumineuse et la répartir sur plusieurs instances PostgreSQL.
Le langage PL/Proxy offre alors la possibilité de développer une couche d’abstraction transparente pour l’utilisateur final qui peut alors consulter et manipuler les données comme si elles se trouvaient dans une seule table sur une seule instance PostgreSQL.
On peut néanmoins se demander l’avenir de ce projet. La dernière version date de septembre 2020, et il n’y a eu aucune modification des sources depuis cette version. La société qui a développé ce langage au départ a été rachetée par Microsoft. Le développement du langage dépend donc d’un très petit nombre de contributeurs.
Conclusion
- Privilégier SQL/MED
dblinket PL/Proxy en perte de vitesse- à n’utiliser que s’ils résolvent un problème non gérable avec SQL/MED
Travaux pratiques
La version en ligne des solutions de ces TP est disponible sur https://dali.bo/v2_solutions.
Foreign Data Wrapper sur un fichier
But : Lire un fichier extérieur depuis PostgreSQL par un FDW
Avec le foreign data wrapper
file_fdw, créer une table distante qui présente les champs du fichier/etc/passwdsous forme de table.
Vérifier son bon fonctionnement avec un simple
SELECT.
Foreign Data Wrapper sur une autre base
But : Accéder à une autre base par un FDW
Accéder à une table de votre choix d’une autre machine, par exemple
stockdans la basecave, à travers une table distante (postgres_fdw) : configuration dupg_hba.conf, installation de l’extension dans une base locale, création du serveur, de la table, du mapping pour les droits.
Visualiser l’accès par un
EXPLAIN (ANALYZE VERBOSE) SELECT ….
Travaux pratiques (solutions)
Foreign Data Wrapper sur un fichier
Avec le foreign data wrapper
file_fdw, créer une table distante qui présente les champs du fichier/etc/passwdsous forme de table.
Vérifier son bon fonctionnement avec un simple
SELECT.
Foreign Data Wrapper sur une autre base
Accéder à une table de votre choix d’une autre machine, par exemple
stockdans la basecave, à travers une table distante (postgres_fdw) : configuration dupg_hba.conf, installation de l’extension dans une base locale, création du serveur, de la table, du mapping pour les droits.
Visualiser l’accès par un
EXPLAIN (ANALYZE VERBOSE) SELECT ….
Tout d’abord, vérifier que la connexion se fait sans mot de passe à la cible depuis le compte postgres de l’instance locale vers la base distante où se trouve la table cible.
Si cela ne fonctionne pas, vérifier le listen_addresses, le fichier pg_hba.conf et le firewall de la base distance, et éventuellement le ~postgres/.pgpass sur le serveur local.
Une fois la connexion en place, dans la base locale voulue, installer le foreign data wrapper :
Créer le foreign server vers le serveur cible (ajuster les options) :
Créer un user mapping, c’est-à-dire une correspondance entre l’utilisateur local et l’utilisateur distant :
Puis créer la foreign table :
Vérifier le bon fonctionnement :
Vérifier le plan :
Il faut l’option VERBOSE pour voir la requête envoyée au serveur distant. Vous constatez que le prédicat sur vin_id a été transmis, ce qui est le principal avantage de cette implémentation sur les DBLinks.
Navigation
Pour revenir au chapitre précédent, voir le chapitre précédent. Pour poursuivre la lecture, voir le chapitre suivant.