Mooc Bases de données relationnelles une application web pour découvrir la concurrence d’accès
Architecture technique
La solution proposée a été développée sur une base Django 1.8, Python 3.4, la librairie mysqlclient pour Python 3 et MySQL/Innodb pour la gestion des bases de données. Elle a été déployée sur une machine sous Ubuntu 14.0, tournant avec Gunicorn comme serveur web et Nginx comme vhost. Nous avons choisi d’utiliser MySQL plutôt que PostgreSQL comme base de données. Le niveau de sécurité READ_UNCOMMITTED étant simulé par le niveau READ_COMMITTED sur PostgreSQL [2], il n’était en effet pas possible de disposer des 4 niveaux d’isolation. MySQL ne rencontre pas ce problème.
Nous nous sommes tournés vers Python pour disposer rapidement d’un prototype à valider. Nous pouvions utiliser les librairies mysql-python et mysqlclient pour effectuer les opérations SQL qui constituait le coeur de la problématique : • création d’une base de données par utilisateur ; • ouverture de deux transactions concurrentes ; • exécution pas à pas de requêtes de lecture et mise à jour des tables ; • exécution des requêtes selon le niveau d’isolation désiré par l’utilisateur. La librairie mysqlclient s’est imposée car elle était compatible avec Python 3 au moment du développement du prototype, ce qui n’était pas le cas de la librairie mysql-python.
Le prototype ayant montré ses preuves, nous nous sommes alors tournés vers un framework Python capable de donner satisfaction. Flask et Django couvraient l’ensemble de nos besoins en la matière, nous avons arbitrairement choisi d’utiliser ce dernier. Les deux disposaient en effet de vues et de templates rapides à mettre en oeuvre, de paramètres de session afin d’identifier simplement l’utilisateur connecté, d’une gestion des utilisateurs intégrée au framework et d’une protection contre le « Cross site request forgery » (CSRF) lors de l’envoi des formulaires contenant les requêtes SQL à exécuter.
S’il a été envisagé un instant pour sa facilité à être mis en oeuvre, l’utilisation de l’ORM de Django n’aurait pas été adaptée à la problématique (voir ci-dessus). Django n’est en autre pas conçu pour gérer une base de données par utilisateur. Hormis la gestion des utilisateurs qui a été laissée à sa charge, l’ensemble des requêtes et de la gestion des bases de données utilisateurs a été effectuée en conservant le prototype découle du fait qu’il s’agissait des dernières versions disponibles au moment du développement.
Cadre d’utilisation
L’interface proposée à l’utilisateur dispose : • d’un menu pour sélectionner le niveau d’isolation et réinitialiser les données ; • deux colonnes reprenant les données propres à chaque transaction : état des tables, variables locales utilisées pour la mise à jour des tables, et les 6 requêtes pouvant être exécutées (voir Fig.1) ; • l’historique des opérations effectuées par les deux transactions (voir Fig. 2 & 3).
Le MOOC a été lancé sur la plateforme France Université Numérique. L’application développée a été intégrée au MOOC sous la forme d’une iframe, qui pointait vers une unique url. Nous avons utilisé le moteur de session Django afin de pouvoir attribuer un profil unique à chaque utilisateur connecté depuis le MOOC. Si l’utilisateur ne dispose pas d’un identifiant de session, un identifiant est créé côté serveur, avec la création d’une base de données qui lui sera propre pour répondre aux exercices, et un identifiant de session est reçu et stocké côté client dans un cookie.
from MySQLdb import connect from MySQLdb.cursors import DictCursor try: conn = connect(db=self.db, user=self.user, passwd=self.passwd, cursorclass=DictCursor) except: # database does not exist -> create database conn = connect(db= »init », user=self.user, passwd=self.passwd) cursor = conn.cursor() cursor.execute(« CREATE DATABASE concurrency_%s », [self.etudiant_id]) # connect to new database and init it # table does not exist -> create tables # code to create tables and insert values into them…
A chaque nouvelle requête envoyée, deux connections à la base de données sont ouvertes : for id in [1, 2]: # init transactions and cursors setattr(self, « conn_{0} ».format(id), connect(db=self.db, user=self.user, passwd=self.passwd, cursorclass=DictCursor)) setattr(self, « cursor_{0} ».format(id), getattr(self, « conn_{0} ».format(id)).cursor()) L’ensemble des requêtes effectuées par l’utilisateur est alors effectué dans ces connections.
La requête Select V1 (voir section Description du besoin) est par exemple effectuée sous la forme suivante : # Select and return vol for connection connection_id getattr(self, « cursor_{0} ».format(connection_id)).execute(‘SELECT id, placesDispo, placesPrises, tarif FROM vol’) return getattr(self, « cursor_{0} ».format(transaction_id)).fetchone() Le résultat est renvoyé sous la forme d’un dictionnaire grace à la classe DictCursor de mysqlclient.