Master 2, Bases de données avancées, année 2023
TD3
L’objectif est de créer une fonction qui va extraire d’un texte les mots importants pour fournir des tags.
La spécification du problème consiste donc à prendre un texte, le décomposer en suite de mot et d’une manière ou d’une autre sélectionner un petit nombre de mot dans le texte.
Pour le texte tiré de la page wikipedia
L’asperge est une plante de la famille des Asparagaceae originaire de l’est du bassin méditerranéen. Connue des Romains, elle est cultivée comme plante potagère en France depuis le XVe siècle. Le terme désigne aussi ses pousses comestibles, qui proviennent de rhizomes d’où partent chaque année les bourgeons souterrains ou turions qui donnent naissance à des tiges s’élevant entre 1 et 1,5 mètre.
On peut avoir comme tag par exemple “asperge”, “potagère”, “Romains”, “rhizomes”.
Le but de ce TP est de déployer du code applicatif et de l’interconnecter à une base de donnée.
- Pour les ML, la partie apprentissage vue dans le cours de NLP peut être utilisé au lieu des versions simplistes proposée ici.
Partie 1: Un algorithme qui tague des textes
L’objectif est de tagguer automatiquement les textes qui sont dans la base de données.
Vous pouvez trouver ici un fichier
json
(compressé) contenant pour chaque mot sa fréquence
d’apparition dans un corpus de texte.
En vous basant sur ce document, proposer une implémentation primitive
et simple du problème de tags sous forme d’une fonction python
tag
qui prend en argument un texte retourne 5 mots du
textes choisit d’une certaine manière..
Partie 2: Un schema SQL qui contient des textes et des tags
- Créez une base de donnée
wiki_article
qui doit contenir une table:
article
avec des colonnes:
- id (un identifiant pour les articles)
- text (le document)
- titre (le titre de l’article)
tags
avec des colonnes:
- id du tag
- text du tag
Et une table:
tag_article
:
- id d’un tag
- id d’une article
Ajoutez des indexes pour que la recherche par tags soit efficace, et pour que récupérer les tags d’un article soit efficace.
- Écrire une API en Python qui:
add_article(titre, text)
: Ajoute un article à la basetag_article(titre, tags)
: Tag l’article de la baseget_articles_from_tags(tag)
: Retourne tout les titres d’articles à partir d’un tag
- (GL) Écrire une API Rest dans le langage votre choix et intégrer le dans une VM dédié pour ce middleware
add_article(titre, text)
: Ajoute un article à la basetag_article(titre, tags)
: Tag l’article de la baseget_articles_from_tags(tag)
: Retourne tout les titres d’articles à partir d’un tag
- Télécharger le document json qui contient des extraits de Wikipédia et ajouter les à votre base de donnée en utilisant votre API. Mesurez la performance de votre ajout.
On peut grandement améliorer les performances en créant une entrée
API add_articles
qui insère d’un coup tous les papiers.
Pour cela, on peut utiliser (en python) des primitive d’insertion par
batch comme executemany
. Des alternatives existent
dans d’autres languages.
Pour allez plus loin, vous pouvez regarder cette requête écrites avec des CTE.
Écrire un script qui exécute votre fonction tags sur chaque article de la base et qui insère le tag dans la base via votre API. Mesurer le temps d’exécution de vos fonctions..
Intégrer votre fonction
tag
que les tags soient insérés automatiquement par la base de données. Vous pouvez le faire, soit en changeant le schéma de base via unegenerated column
soit en utilisant des triggers soit avec un subtil mélange des deux solutions. Mesurez la performance de cette implémentation.
Partie 2: Une intégration simple de Python dans PostgreSQL
Pour éviter les A/R entre la base de donnée et le client Python, on
peut intégrer directement à PostgreSQL la fonction tag
.
Installer le paquet
postgresql-plpython3-14
qui l’installe l’extensionplpython3u
dans PostgreSQL. Activer l’extension à l’aide de la requête SQLCREATE EXTENSION plpython3u
Lisez la documentation pour comprendre comment exécuter une fonction python dans
PostgreSQL
.En exécutant la fonction
CREATE OR REPLACE FUNCTION pyenv()
RETURNS TEXTAS $$
import os, getpassreturn f"pwd: {os.getcwd()} user: {getpass.getuser()}"
$$ LANGUAGE plpython3u
En déduire quel utilisateur exécute ces processus et depuis quel répertoire.
Activer le timing dans
psql
à l’aide de la commande\timing on
. Comparer le temps d’exécution d’une requête SQL la plus simple possible avec le temps d’exécutionpyenv
. Estimez le surcout d’un appel à une fonction Python.Copiez le fichier
lexique.json
dans un répertoire accessible pour l’utilisateur qui exécute les fonctionsPython
et adapter votre code pour la fonctiontag
et importer là dansPostgreSQL
.Estimez le coût d’un appel à cette fonction, proposez une piste d’amélioration.
- Vous pouvez par exemple lire un peu plus la documentation et en particulier cet article
- Sans trop optimiser, j’arrive à 9s pour passer au travers de tous les articles.
- Le premier appel coûte cher (~250ms), puis le reste est très rapide (~1ms par appel).
Combien coûte en RAM les appels à la fonction
tag
?Que se passe-t-il en cas de nombreux appels concurrents qui utilisent cette fonction? Essayer avec 5 sessions ouvertes simultanément ayant chacune appelé la fonction
tag
au moins une fois (et maintenu ouverte). Constaté l’utilisation de la mémoire vive sur le serveur avec la commande htop.
Partie 3: Une approche microservice
Il y a plusieurs limite à l’intégration simple de Python dans PostgreSQL.
- Le coût du lancement de Python et des sous-modules. Certains modules
sont très long à être chargé:
numpy
se charge en 200msscipy
se charge en plus d’une seconde.
- Le coût de chargement en mémoire de données nécessaires (modèles par exemple, dictionnaires).
Ce coût est factorisable entre sessions et donc il est
possible de faire en sorte que le middleware
maintienne
ouvert la session avec la base de donnée. Cela n’est pas compatible avec
CGI et nécessite d’autres type d’interface, comme
FastCGI
.
Pour contourner ce type de difficulté, la stratégie est de séparer le calcul réalisé de la base de donnée via une architecture de micro-service. Un micro-service est un calcul stocké dans un processus distant, voire sure une autre machine ou dans le cloud.
On peut utiliser l’intégration Python
de PostgreSQL pour
l’interface entre le micro-service et PostgreSQL notamment.
Structure simple d’un microservice
Un microservice peut avoir la forme d’une application web et être
géré complètement via les protocoles http
classique. Cela
nécessite néanmoins de faire attention au type d’intégration de Python
choisi. On va s’appuyer sur des mécanismes simples et faire un
serveur TCP minimaliste.
Pour vous aider, voici un exemple de code utilisé pour le serveur:
class Handler(socketserver.StreamRequestHandler):
def handle(self):
= json.loads(self.rfile.readline().strip().decode())
data print("log in", data)
self.request.sendall(json.dumps(tag(data)).encode()+b"\n")
if __name__ == "__main__":
= "localhost", 9999
HOST, PORT print("Launching server:", HOST, PORT)
with socketserver.TCPServer((HOST, PORT), Handler) as server:
server.serve_forever()
Lancer le serveur sur votre machine hébergeant
PostgreSQL
et vérifier que tout marche bien à l’aide de
telnet localhost 9999
pour interagir avec.
Vous pouvez intégrer le code client à l’aide de simple connexion socket:
= "localhost", 9999
HOST, PORT with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
connect((HOST, PORT))
sock.= sock.makefile("w")
f +"\n") # Attention à bien mettre le retour à ligne.
f.write(json.dumps(t)
f.close()= sock.makefile("r")
f print(f.readline())
f.close()
On obtient ainsi environ 1ms par requête sans les problèmes évoqués avant mais au détriment d’un coût d’administration plus grand.
Pour rendre le système résilient, il faut ajouter le lancement du serveur python dans un Daemon afin de le relancer automatiquement. Il est également possible d’améliorer le mécanisme de communication pour éviter la sérialisation/déserialisation.
En optimisant raisonnablement la communication entre processus, j’arrive à 30secondes pour passer au travers de tous les articles sans latence pour la première exécution.
Partie 4 (GL)
Dans un micro-service modernes, on met en place des mécanismes pour pouvoir recharger à chaud le code exécuté. Cela passe par un micro-service capable de détecter que le module a été mis à jour et le cas échéant le recharger.
Pour mettre en place un rechargement à chaud du module faites:
Mettre dans un fichier python à part nommé
tags.py
la fonction Pythontags
.Mettre en place un rechargement à chaud via:
- la méthode
stat
du moduleos
- le rechargement de module dynamique à l’aide de la méthode
reload
du moduleimportlib
- la méthode
Partie 4 (ML)
Nous avons un modèle de tag
très simple. Proposez des
modèles plus complexe (et plus couteux à calculer et à déployer).
Compiled the: mar. 17 déc. 2024 14:03:11 CET