Master 2, Bases de données avancées, année 2022
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 et 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 CMS basique
Dans votre VM contenant PostgreSQL, créer une nouvelle base de donnée
article
contenant une table ou chaque ligne contient un
identifiant d’article, un texte et une date de publication.
Dans votre VM middleware
, créer un nouveau
middleware permettant:
- d’accéder à un article par son identifiant
- de poster un nouvel article,
- de le mettre à jour ou de le supprimer,
- de lister tous les article par date de publication décroissante
Réaliser votre schéma afin que ces opérations soient efficaces.
Télécharger le document json qui contient des extraits de Wikipédia et ajouter les à votre base de donnée.
Partie 1: Un algorithme qui tagues des textes
L’objectif est de tagguer automatiquement les textes qui sont dans la bases 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 certaines manières..
Ajoutez à votre schéma une table
Tags
qui associe des tags à chaque 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 un script qui exécute votre fonction tags sur chaque article de la base et qui insère le tag dans la base. Essayez autant que possible d’éviter les allés/retours entre la base de donnée et votre script. Mesurer le temps d’exécution de votre script.
Comment intégrer votre fonction
tag
au middleware pour garantir que les tags soient insérés dynamiquement?
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-12
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()}"
AS 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 ce passe-t-il en cas de nombreux appel concurrent 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.
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 par 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()
Mieux vaut utiliser un framework bien équipé pour faire tout ça, le
code ici est du bricolage. En Python,
fastapi
fait par exemple très bien l’affaire.
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.
Rechargement de module à chaud
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: Apprendre un modèle plus complexe
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: mer. 04 sept. 2024 12:49:53 CEST