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.
Creation small_table + charge 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 :

Java Architecture

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).

Methode Run pour MSSQL

  • 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)

Procedure update small_table

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).