Le Test de volume vérifie le comportement d’un système lorsqu’il est soumis à de larges volumes de données. Dans la plupart des applications, les données sont stockées dans une base de données. Le principe du test de volume est de multiplier les lignes contenues dans les tables principales de la base de données de l’application.
Les tests de volume sont pratiques parce qu’ils évitent la découverte des gros problèmes de performance bien avant la mise en production. Bien entendu, ils nécessitent une bonne connaissance de l’application et du schéma de la base de données.
Cas d’application
Cas où les tests de volume sont peu utiles :
- Peu de données prévues en base de données.
- Faible complexité du schéma avec peu de relations.
Ce qu’il faut savoir, c’est qu’une base de données avec des tables ne contenant que 100.000 lignes présente peu de problèmes de performance pour les serveurs modernes.
Cas où les tests de volume sont nécessaires :
- Applications avec un volume très important (plus d’un million de lignes).
- Applications utilisant des requêtes SQL avec beaucoup de jointures.
- Applications de reporting ou de BI.
- Applications construite avec un framework de persistance donc le paramétrage n’a pas été optimisé.
Principe technique
dbmonster est une application Java capable d’injecter des lignes de valeurs dans les tables d’une base de données existante. Par défaut, ces données sont aléatoires, mais elles peuvent être constantes ou respecter une certaine logique. Par exemple, on peut injecter une valeur qui soit toujours supérieure à un seuil ou qui respecte une loi de probabilité. Comme nous allons le voir par la suite, le cas des clés étrangères est également géré par l’outil.
Processus
- Télécharger le driver JDBC pour votre base de données et copier le jar dans le répertoire de dbmonster.
- Configurer dmonster à l’aide d’un fichier de propriétés.
- Scanner la base de données de manière à obtenir un fichier décrivant son schéma et les modalités d’injection des données.
- Éditer le fichier décrivant le schéma de la base de données de manière à l’adapter à vos besoins d’injection.
- Injecter des données à l’aide de dbmonster.
Exemple avec OpenCart
Nous allons dérouler ce processus à l’aide de l’application open source OpenCart (site web en PHP de gestion de boutique).
Obtenir le driver de la base de données
- Aller sur le site de MySQL et télécharger la dernière version du connecteur pour Java (ou cliquer sur le lien « Looking for previous GA versions? » si vous rencontrez des problèmes avec la dernière version).
- Copiez le JAR obtenu dans le répertoire d’installation de dbmonster (ou dans un autre endroit, mais pensez à modifier le chemin d’accès dans les les commandes fournies dans la suite de ce tutoriel).
Configurer dmonster
Créez ou modifiez le fichier dbmonster.properties en fonction de la configuration de votre base de données. Voici un exemple de configuration :
dbmonster.jdbc.driver=com.mysql.jdbc.Driver
dbmonster.jdbc.url=jdbc:mysql://localhost:3306/opencart
dbmonster.jdbc.username=root
dbmonster.jdbc.password=
Scanner la base de données
L’étape suivante consiste à examiner la base de données de manière à produire une première version du fichier d’injection. Disons que ce fichier décrit la structure de la base de données et la manière dont dbmonster va injecter les données.
Ouvrez une console de commande ou un terminal ; puis tapez la commande :
java -classpath mysql-connector-java-5.1.21-bin.jar;dbmonster-core-1.0.3.jar pl.kernelpanic.dbmonster.Launcher -c dbmonster.properties –grab -o dbmonster-schema.xml
Cette commande devrait produire un fichier dbmonster-schema.xml dans le répertoire courant
Éditer le fichier décrivant l’injection
Le fichier dbmonster-schema.xml ne peut pas être utilisé en l’état. En effet, si vous le lancez tel quel :
- Il n’injectera que 100 lignes par table.
- dbmonster tentera de faire une injection sans tenir compte des contraintes.
- Idem pour les règles de gestion de votre application.
Dans les paragraphes suivants, nous verrons comment adresser un à un tous ces points de configuration.
Pour ne remplir qu’une seule table
Le fichier XML contient la liste de toutes les tables de votre base de données et les consignes d’injection pour chacune des colonnes. Notamment le nombre de lignes à injecter et, pour chaque colonne, le type de données à insérer. Bien entendu, si vous souhaitez n’injecter que quelques lignes dans une seule des tables, il suffit de nettoyer le fichier en ne laissant que la table en question.
L’important est de conserver les premières lignes de description :
<?xml version="1.0"?>
<!DOCTYPE dbmonster-schema PUBLIC "-//kernelpanic.pl//DBMonster Database Schema DTD 1.1//EN" "http://dbmonster.kernelpanic.pl/dtd/dbmonster-schema-1.1.dtd">
<dbmonster-schema>
<name>Fill Product Table</name>
Ainsi que la balise fermante en fin de fichier :
</dbmonster-schema>
Modifier le nombre de lignes à injecter
Le fichier XML contient l’attribut rows qui, pour chaque table, décrit le nombre de lignes à injecter. Pour modifier cette valeur, cherchez tous les tags table décrivant une table (attribut name) pour laquelle vous souhaitez injecter plus ou moins de lignes :
<table name="product" rows="100">…</table>
Par exemple, pour injecter 1000 lignes dans la table des produits, modifier l’attribut rows comme suit :
<table name="product" rows="1000">…</table>
Régler les problèmes de contraintes
Dans la base de données que nous utilisons en exemple, il y a des contraintes relationnelles. Notamment la clé étrangère dans la table de description des produits qui interdit l’insertion d’une description qui ne serait attachée à aucun produit. Autrement dit, une ligne contenant un product_id doit exister dans la table product et ce champ doit avoir la même valeur que le champ product_id que vous souhaitez insérer dans la table product_description.
Or si on regarde de plus près le fichier d’injection, on constate que la colonne de l’identifiant de la table serait remplie avec un nombre aléatoire. Il y a donc de très grandes chances de provoquer une erreur de violation de contrainte.
<table name="product_description" rows="100">
…
<column name="product_id" databaseDefault="false">
<generator type="pl.kernelpanic.dbmonster.generator.NumberGenerator">
<property name="maxValue" value="127"/>
<property name="minValue" value="0"/>
<property name="nulls" value="0"/>
<property name="returnedType" value="integer"/>
<property name="scale" value="0"/>
</generator>
</column>
Il faut donc changer le type de générateur. Dans l’exemple ci-dessous, j’ai choisi un générateur de clé étrangère et je l’ai configuré de manière à vérifier la table des produits avant de faire l’injection :
<table name="product_description" rows="100">
…
<column name="product_id" databaseDefault="false">
<generator type="pl.kernelpanic.dbmonster.generator.ForeignKeyGenerator">
<property name="tableName" value="product"/>
<property name="columnName" value="product_id"/>
</generator>
</column>
Prendre en compte les règles métier
Votre application contient très certainement des règles de gestion basées sur les valeurs de la base de données et correspondant à une logique métier. Dans le cas d’OpenCart, on peut noter les règles suivantes concernant la table des produits :
- Seuls les produits actifs sont affichés aux clients.
- On ne peut commander que des articles qui sont en stock.
Voyons comment adapter le fichier décrivant l’injection des données en y configurant des générateurs spécifiques.
Générer une valeur toujours égale à TRUE
Comme je le disais précédemment, seuls les articles dont le champ status est égal à TRUE sont affichés dans la boutique. Si vous souhaitez tester le comportement de la boutique en ligne lorsqu’il y a beaucoup d’articles, il faudra donc remplir la table des produits avec beaucoup d’articles au status TRUE. Avec le générateur de nombre binaire, on peut jouer sur la probabilité que le nombre inséré soit égal à TRUE. Si vous souhaitez que tous les articles soient visibles, changez l’attribut value de la propriété probability à 100. Et à la valeur 80, si vous souhaitez que 80% des articles soient visibles.
<column name="status" databaseDefault="false">
<generator type="pl.kernelpanic.dbmonster.generator.BooleanGenerator">
<property name="nulls" value="0"/>
<property name="probability" value="50"/>
</generator>
</column>
Générer une valeur toujours supérieure à X
Une autre règle de gestion pourrait être que les articles ne peuvent être commandés que lorsqu’ils sont en stock. D’un point de vue données, cela signifie que l’on ne peut commander que les articles dont le champ quantity est strictement supérieur à 0. Il faudrait donc modifier le fichier d’injection en choisissant une valeur supérieure à 0 pour l’attribut value de la propriété minValue.
<column name=”quantity” databaseDefault=”false”>
<generator type=”pl.kernelpanic.dbmonster.generator.NumberGenerator”>
<property name=”maxValue” value=”127”/>
<property name=”minValue” value=”0”/>
<property name=”nulls” value=”0”/>
<property name=”returnedType” value=”integer”/>
<property name=”scale” value=”0”/>
</generator>
</column>
Injecter les données
Une fois que votre fichier de description d’injection est prêt, il suffit de lancer la commande suivante :
java -classpath mysql-connector-java-5.1.21-bin.jar;dbmonster-core-1.0.3.jar pl.kernelpanic.dbmonster.Launcher -s dbmonster-schema.xml -c dbmonster.properties
Temps d’exécution
L’insertion de centaines de milliers de lignes dans une table de données n’est pas très longue (moins de cinq minutes). Ce qui est le plus long, c’est l’injection de données dans une table contenant une contrainte sur une clé étrangère. Pour vous donner une idée, sur un Intel i5, l’injection de 100.000 lignes sur une telle table prendra plus de 10 minutes.
Conclusion
Dans certains cas on peut charger une base de données très tôt dans le cycle de vie d’une application. De manière à ce que les développeurs se sentent concernés par les problèmes de performance de leur code le plus tôt possible. Dans d’autres cas, il sera trop coûteux de décrire un schéma cohérent d’injection, voire impossible, si l’application est trop sensible aux valeurs contenues dans la base de données et que les valeurs aléatoires engendrent un nombre trop important d’erreurs. Dans ces cas-là, on peut tout de même faire un test de volume en prenant les précautions suivantes :
- Utiliser un environnement dédié.
- Préparer un jeu de données cohérent, puis charger la base avec dbmonster. On naviguera dans l’application en utilisant la partie cohérente du jeu de données ; la partie aléatoire étant présente en base seulement pour charger la base.
Vous avez pu découvrir les bases de l’utilisation de dbmonster ainsi que les principaux concepts :
- Scanner la base de données afin de découvrir sa structure.
- Configurer une injection
- Utiliser les générateurs de valeurs (nombre aléatoire, clé étrangère, nombre binaire)
- Réaliser une injection
Pour aller plus loin
- Télécharger dbmonster (dbmonster-core-XXX).
- Consulter la documentation de l’outil. Cette documentation – au format HTML – est livrée avec l’outil dans le sous-répertoire docs.