Niveaux d'isolation - Benchmark pour analyse des niveaux d'isolation
Par Olivier le mardi 9 décembre 2014, 18:00 - Lien permanent
Aujourd’hui, je vais vous en dire plus sur
le benchmark que j’ai réalisé pour
mes essais sur l’isolation des moteurs SGBDR. Vous allez voir que j’ai
essayé de faire les mêmes actions sur les 2 moteurs et que malgré que le code
est très basique, le protocole de test m’a pris beaucoup de temps. Si vous êtes
développeur qui recherche du code pour coder ses connexions, passez votre
chemin, cet article n’est en aucune façon une présentation des bonnes pratiques
pour se travailler avec les SGBDR.
L’unique table sur le SGBDR
Pour ce test de performance, je souhaite détecter les attentes lors de
SELECT dues à un grand nombre de modifications. Pour avoir le plus de
« collisions » entre les SELECT et les UPDATES ; je n’ai créé
qu’une seule table sans index et avec peu de lignes (300). Avec une si faible
volumétrie, il est certain que toutes les données de cette table seront
présentes en cache très rapidement (à la 1ère lecture ?) donc on s’épargne les
attentes dues aux disques.
Le code ci-dessus permet de créer
la table sous SQL Server et de la charger avec les 300 lignes. Le même genre de
table est créée sous Oracle.
UPDATE 17/12/2014 : Je désactive le lock_escalation sous SQL
Server car il pose des problèmes pour ce benchmark (lock exclusif sur la table
lors d'un UPDATE d'une ligne).
Le modèle Java
Pour travailler en multi-thread sous Java, la seule architecture dont j’ai l’habitude est via une classe qui hérite de Thread. J’ai donc réalisé l’architecture suivante :
Les classes Startup et Benchmark pourraient être fusionnées : la classe Startup ne contient que la méthode main de lancement.
Benchmark :
Cette classe s’initialise avec 3 paramètres :
- le nom du moteur sur lequel le benchmark va s’exécuter
- l’URL de connexion vers le moteur
- le nombre de worker qui seront initialisés
Selon le nom du moteur, la classe Benchmark va instancier autant de worker que demandés soit en MSSQLWorker soit en OracleWorker Méthode importante : endingWorker qui permet d’enlever un worker de la collection de worker lorsque ce thread a fini son travail. A noter que cette méthode est « synchronized » car les threads vont l’utiliser en parallèle et qu’il faut éviter que 2 threads modifient la collection en parallèle (il pourrait y avoir des threads qui ne sont pas supprimés de la collection). Les autres méthodes sont des aides à l’analyse (le thread de monitoring a fini son travail, on attendra + longtemps dans la boucle du Startup)
Worker :
Comme on l’a vu, cette classe hérite de Thread. Je l’ai définie comme étant « abstract » puisque le benchmark utilise soit des workers MSSQL soit Oracle. Cette classe a 3 attributs :
- le nom du worker (un numéro donné par Benchmark à l’initialisation)
- un booléen sur le fait d’être le worker surveillé = monitored
- un objet Connection
MSSQLWorker & OracleWorker :
Héritent de Worker ; aucun attribut supplémentaire. Le code principal est dans leur méthode run() ; cette méthode est la seule qui sera exécutée dans un vrai Thread Java séparé.
Méthode run()
Pour les 2 workers, l’organisation de la méthode run est identique.
- Je commence par définir quelques variables : les requêtes SQL qui seront exécutées
- Si le worker actuel est le monitored thread, je configure la session pour
récupérer les attentes du serveur pour cette session
- Avec SQL Server, je mets en place un Extended Event Session sur sqlos.wait_info
- Avec Oracle, j’active le TIMED_STATISTICS
- Selon le benchmark que je réalise, c’est à cette étape que je configure le mode d’isolation dans lequel va s’exécuter le thread (lors de la prise de statistiques sur ma première analyse, cette section était commentée ; Java était en paramétrage par défaut).
- Vient ensuite la boucle principale du benchmark (vous pouvez régler le nombre de fois qu’elle s’exécutera via numberOfRun).
- Elle se décompose ainsi :
- Exécution du select_SQL1
- Exécution du select_SQL2
- Si le worker actuel n’est pas le monitored thread, exécution de la procédure stockée small_table_update
- Exécution du select_SQL3
- Exécution du select_SQL4
- Si le worker actuel n’est pas le monitored thread, nouvelle exécution de la procédure stockée small_table_update
- Exécution du select_SQL5
- Exécution de 45 fois le select_SQLMORE
- Si le worker actuel n’est pas le monitored thread, dernière exécution de la procédure stockée small_table_update
- COMMIT (toujours exécuté sur Oracle ; selon le paramétrage de l’auto-commit sur SQL Server)
- Récupération des évènements d’attentes pour le monitored thread.
- Pour SQL Server, les Extended Event sont écrit dans un fichier qui est parsé pour calculer les statistiques d’attente.
- Sous Oracle, j’utilise la vue V$SESSION_EVENT
- Le thread indique qu’il a fini son boulot au Benchmark.
Procédure stockée small_table_update
Vous l’avez peut-être remarqué, cette procédure prend 3 paramètres en entrée. Cela est nécessaire pour éviter que plusieurs workers modifient les mêmes lignes. Pour dispatcher les modifications, j’ai divisé la table en autant de sous-ensemble qu’il y a de workers. Voici les paramètres de la procédure :
- le nombre d’UPDATE qui seront réalisés (normalement, chaque UPDATE modifie une seule ligne de la table)
- part_size = le nombre de lignes dans une partie de table dédiée à un worker (10 workers = la table divisée en 10 parties ; la table a 300 lignes, donc 30 lignes par worker)
- part_number = le numéro de la partie prise en charge par le worker actuel (= le numéro du worker)
Le code de la procédure est une simple boucle (pour faire autant d’UPDATE que demandé) dans lequel on réalise l’UPDATE WHERE le numéro est compris entre part_size*part_number et part_size*part_number+1 .
Résumé
Vous avez maintenant tout le détail de mon benchmark. Pour conclure, je vous
donne Sources Java
les sources Java de ce benchmark.
N'hésitez pas à au moins lire le code pour mieux comprendre mes tests. Dans un
prochain billet, je vais continuer avec le programme pour vous montrer comment
SQL Server prend en charge les transactions sans Auto-commit (par défaut, mon
programme Java est en auto-commit).