Bien qu'TIBCO EBX® soit conçu pour prendre en charge de grands volumes de données, plusieurs facteurs courants peuvent engendrer de faibles performances. Les points clé abordés dans cette section permettront de résoudre les éventuels problèmes de performance.
Pour information, la table ci-dessous détaille les extensions programmatiques qui peuvent être implémentées.
Cas d'utilisation | Extensions programmatiques pouvant être impliquées |
---|---|
Validation | |
Accès table | |
Affichage du contenu EBX® | |
Mise à jour des données |
Pour les grands volumes de données, les algorithmes lourds ont un impact important sur les performances. Par exemple, la complexité d'un algorithme de contrainte est O(n 2 ). Si la taille des données est 100, le coût résultant sera proportionnellement de 10 000 (produisant généralement un résultat immédiat). Cependant, si la taille des données est 10 000, le coût résultant sera proportionnellement de 10 000 000.
Une autre cause de baisse des performances est l'appel à des ressources externes. L'utilisation d'un cache local résout généralement ce type de problèmes.
Si l'un des cas d'utilisation ci-dessus mène à une baisse des performances, il est recommandé d'identifier le problème soit via l'analyse du code, soit en utilisant un outil de profiling Java.
L'authentification et la gestion des permissions font appel à l'annuaire des rôles et utilisateurs.
Si l'implémentation spécifique d'un annuaire est déployée et accède à un annuaire externe, il peut être utile de mettre en place un cache local. En particulier, une des méthodes les plus fréquemment appelées est Directory.isUserInRole
.
Dans un modèle de données, lorsque la contrainte de cardinalité d'un élément maxOccurs
est supérieure à 1 et qu'aucune osd:table
n'est déclarée pour cet élément, elle est implémentée en tant que List
Java. Ce type d'élément est appelé liste agrégée, par opposition à une table.
Il est important de prendre en compte qu'il n'existe aucune optimisation spécifique lors de l'accès aux listes agrégées en termes d'itérations, d'affichage utilisateur, etc. En dehors des questions de performance, les listes agrégées sont limitées en ce qui concerne les nombreuses fonctionnalités prises en charge par les tables. Voir introduction aux tables pour une liste de ces fonctionnalités.
Pour les raisons indiquées ci-dessus, les listes agrégées devraient être utilisées uniquement sur de petits volumes de données simples (une ou deux douzaines d'enregistrements), sans exigence spécifique concernant leur identification, résolution, permissions, etc. Pour des volumes de données plus importants (ou des fonctionnalités plus avancées), il est conseillé d'utiliser les déclarations osd:table
.
Les espaces de données disponibles en mode sémantique sont des outils précieux pour gérer les cycles de vie des données complexes. Bien que cette fonctionnalité apporte une grande flexibilité, elle implique aussi un coût supplémentaire, qui doit être pris en compte pour l'optimisation des processus métiers.
Cette section résume les problèmes de performance les plus courants qui peuvent apparaître dans le cas d'une utilisation intensive de plusieurs espaces de données contenant de grandes tables ; et de quelle façon les éviter.
Parfois, l'utilisation d'espaces de données n'est pas strictement nécessaire. Citons le cas extrême où chaque transaction déclencherait les actions suivantes :
Un espace de données est créé.
La transaction modifie certaines données.
L'espace de données est fusionné, fermé, puis supprimé.
Dans ce cas, aucune référence à l'espace de données ne sera nécessaire par la suite ; son utilisation pour modifier de données isolées n'est donc pas nécessaire. Ainsi, l'utilisation de Procedure
fournit déjà une isolation suffisante pour éviter les conflits entre opérations concurrentes. Il serait alors plus efficace de réaliser les modifications directement dans l'espace de données cible, et de ne pas conserver les étapes de branche et de fusion.
Voici une analogie à l'attention des développeurs, portant sur un outil de gestion de code source (CVS, SVN, etc.) : pour réaliser une modification simple n'impactant que quelques fichiers, il suffit de le faire directement dans la branche principale. En fait, ce ne serait ni pratique ni durable, en ce qui concerne le tag/la copie des fichiers, si chaque modification de fichier impliquait de brancher l'ensemble du projet, de modifier les fichiers, puis de fusionner la branche dédiée.
Lorsqu'une table est en mode sémantique (par défaut), le cache de la mémoire Java d'EBX® est utilisé. Cela garantit un accès aux données bien plus efficace lorsque ces données sont déjà chargées dans le cache. Toutefois, s'il n'y a pas assez d'espace pour les données courantes, des rechargements intempestifs de données depuis la base de données sous-jacente vers le cache de la JVM peuvent avoir un lourd impact sur les performances globales.
Cette surcharge d'échanges de mémoire (swap) ne peut se produire que pour les tables dans un espace de données ayant une stratégie de chargement à la demande.
Ce type de problème peut être détecté via le fichier log de monitoring. Si cela se produit, différentes actions peuvent être envisagées :
réduire le nombre d'espaces de données enfants contenant des tables de grande taille ;
réduire le nombre d'index spécifiquement définis pour les tables de grande taille ;
utiliser le mode relationnel au lieu du mode sémantique ;
ou (de manière évidente) allouer davantage de mémoire, ou optimiser la mémoire utilisée par les applications pour les objets non-EBX®.
En mode sémantique, lorsqu'une transaction a réalisé des mises à jour dans l'espace de données courant et qu'elle est interrompue, les index chargés des tables modifiées sont réinitialisés. Si les mises à jour d'une table de grande taille sont souvent annulées et, qu'en même temps, les accès à cette table sont fréquents, alors le travail relatif à la reconstruction de l'index provoquera un ralentissement de l'accès à la table. Par ailleurs, l'allocation de mémoire induite et l'activité de "garbage collector" peuvent mener à une réduction globale des performances.
Comme avec toute base de données, l'insertion et la suppression de volumes de données importants peuvent mener à une fragmentation des données, qui peut détériorer les performances avec le temps. Pour résoudre ce problème, la réorganisation des tables impactées de la base de données est nécessaire. Voir Monitoring and cleanup of the relational database.
Une spécificité d' EBX® est que la création d'espaces de données et d'images ajoute de nouvelles entrées aux tables HTA
et ATB
. Si l'on constate une baisse des performances, il peut donc être nécessaire de planifier une réorganisation de ces tables, pour les référentiels de taille importante dans lesquels de nombreux espaces de données sont créés et supprimés.
L'administrateur peut préciser la stratégie de chargement de l'espace de données ou de l'image dans ses informations. La stratégie par défaut est de charger et de décharger les ressources à la demande. Pour les ressources fréquemment utilisées, une stratégie de chargement forcé est généralement recommandée.
La table suivante détaille les modes de chargement disponibles en mode sémantique. Le serveur d'applications doit être redémarré afin de prendre en compte tout changement de stratégie de chargement.
Chargement et déchargement à la demande | Dans ce mode par défaut, chaque ressource d'un espace de données est uniquement chargée ou construite si nécessaire. Les ressources de l'espace de données sont référencées par le logiciel via la classe Java standard L'avantage principal de ce mode est sa capacité à libérer de la mémoire lorsque cela est nécessaire. En contrepartie, cela implique un coût de chargement/construction lorsque la ressource accédée n'a pas encore été chargée depuis le démarrage du serveur, ou si elle a été déchargée depuis. |
Chargement forcé | Si la stratégie de chargement forcé est activée pour un espace de données ou une image, ses ressources sont chargées de manière asynchrone au démarrage du serveur. Chaque ressource de l'espace de données est conservée en mémoire jusqu'à l'arrêt du serveur ou la fermeture de l'espace de données. Ce mode est particulièrement recommandé pour les espaces de données ayant une longue durée de vie et/ou ceux qui sont fréquemment utilisés, à savoir tout espace de données utilisé comme référence. |
Chargement forcé et pré-validation | Cette stratégie est similaire à la stratégie de chargement forcé, la différence étant que le contenu de l'espace de données chargé ou de l'image sera aussi validé au démarrage du serveur. |
Les indications relatives au chargement/déchargement du cache d'EBX® sont fournies par le monitoring de la base de données sous-jacente, ainsi que par la catégorie de logging du 'monitoring'.
Si le nombre d'objets cleared et built demeure élevé sur une longue période, cela signifie qu'EBX® effectue de nombreux chargements et déchargements d'objets dans le cache.
Afin de faciliter l'analyse des journaux générés par EBX®, il est possible d'utiliser la feuille de calcul OpenOffice fournie (clic-droit et sauvegarder).
La mémoire maximum pouvant être allouée à la JVM est habituellement spécifiée en utilisant l'option de ligne de commande Java -Xmx
. Comme c'est le cas pour tout processus intensif, il est important que la taille spécifiée par cette option n'excède pas la mémoire physique disponible, ainsi le processus Java n'utilisera pas le disque pour des échanges mémoire (swap) au niveau du système d'exploitation.
Le réglage du "garbage collector" peut aussi améliorer les performances globales. Ce réglage doit être adapté au cas d'utilisation ainsi qu'à l'environnement spécifique Java Runtime utilisé.
Le mécanisme de validation incrémentale permet d'optimiser le travail nécessaire lors des mises à jour. Le processus de validation incrémentale a le comportement suivant :
Le premier appel au rapport de validation d'un jeu de données effectue une validation complète du jeu de données. La stratégie de chargement peut aussi spécifier qu'un espace de données doit être prévalidé au démarrage du serveur.
Les mises à jour de données maintiendront le rapport de validation de manière transparente et asynchrone, dans la mesure où les noeuds mis à jour définiront des dépendances explicites. Par exemple, les facettes standard et statiques, les contraintes de clé étrangère, les facettes dynamiques, les noeuds de sélection définissent des dépendances explicites.
Si une mise à jour de masse est effectuée ou s'il y a trop de messages de validation, le processus de validation incrémentale est interrompu. Le prochain appel au rapport de validation déclenchera alors une validation complète.
Si une transaction est annulée, l'état de validation du jeu de données mis à jour est réinitialisé. Le prochain appel au rapport de validation déclenchera aussi une validation complète.
Certains noeuds sont systématiquement revalidés, et ce, même si aucune mise à jour n'a eu lieu depuis la dernière validation. Ce sont des noeuds à dépendances inconnues. Un noeud a des dépendances inconnues si :
Il définit une contrainte programmatique dans le mode dépendances inconnues par défaut,
Il déclare une valeur calculée, ou il déclare une facette dynamique qui dépend d'un noeud qui est lui-même une valeur calculée.
Il est un Champs hérités, ou il déclare une facette dynamique qui dépend d'un noeud qui est lui-même un Champs hérités.
Par conséquent, pour les tables de grande taille (au delà de l'ordre de grandeur 10 5 ), il est recommandé d'éviter les noeuds ayant des dépendances inconnues (ou au moins d'en minimiser le nombre). Pour les contraintes programmatiques, le développeur peut définir deux modes alternatifs qui réduiront de manière drastique les coûts de validation incrémentale : mode dépendance locale et dépendances explicites. Pour plus d'informations, voir Dépendances et validation.
Il est possible, pour un utilisateur administrateur, de réinitialiser manuellement le rapport de validation d'un jeu de données. Cette option est disponible dans la section du rapport de validation dans EBX®.
Les mises à jour de masse peuvent inclure plusieurs centaines de milliers d'insertions, de modifications et de suppressions. Ces mises à jour sont assez rares (habituellement imports de données initiales), ou sont réalisées de manière non-interactive (traitements par lot de nuit). Ainsi, l'importance des performances pour ces mises à jour est moins critique que pour des opérations interactives ou fréquentes. Toutefois, tout comme le traitement par lot classique, elles peuvent donner lieu à certains problèmes spécifiques.
Pour les tables en mode relationnel, l'implémentation des insertions, mises à jours et suppressions s'appuie sur la fonction JDBC
de traitement par lot (batch). Pour des procédures à fort volume, ceci améliore grandement les performances, en limitant le nombre d'allers-retours entre le serveur d'applications et la base de données.
Afin d'exploiter totalement cette fonctionnalité, il est possible d'activer le mode batch sur des procédures à fort volume. Voir ProcedureContext.setBatch
. Ceci désactive le test d'existence avant insertion d'un enregistrement, et réduit ainsi le nombre de requêtes émises vers la base de données. Le traitement par lot en est rendu encore plus efficace.
Il n'est généralement pas recommandé d'utiliser une seule transaction lorsque le nombre de mises à jour atomiques dans la transaction est supérieur à l'ordre de grandeur 10 4 . Les transactions de taille importante nécessitent beaucoup de ressources, particulièrement mémoire, d'EBX® ainsi que de la base de données sous-jacente.
Afin de réduire la taille de la transaction, il est possible de :
Définir la propriété ebx.manager.import.commit.threshold. Cependant, cette propriété n'est utilisée que pour les imports d'archive interactifs réalisés à partir de l'interface utilisateur d'EBX®.
Définir explicitement un commit threshold à l'intérieur de la procédure batch.
Limiter structurellement le périmètre de la transaction en implémentant Procedure
pour une partie de la tâche et l'exécuter aussi souvent que nécessaire.
D'autre part, définir une taille de transaction très basse peut aussi altérer les performances à cause des tâches persistantes qui doivent être exécutées sur chaque commit.
Si des commits intermédiaires posent problème du fait que l'atomicité transactionnelle n'est plus garantie, il est recommandé d'exécuter la mise à jour de masse à l'intérieur d'un espace de données dédié. Cet espace de données sera créé juste avant la mise à jour de masse. Si la mise à jour ne s'achève pas avec succès, l'espace de données doit être fermé, et il faudra rententer la mise à jour après avoir corrigé la cause de l'échec initial. Si la mise à jour aboutit avec succès, l'espace de données peut alors être fusionné sans risque avec l'espace de données d'origine.
Si nécessaire, les triggers peuvent être désactivés en utilisant la méthode ProcedureContext.setTriggerActivation
.
On accède généralement aux tables via EBX® et aussi via l'API Request
et les services de données. Cet accès implique un ensemble unique de fonctions, incluant un processus de résolution dynamique. Ce processus a le comportement suivant :
Héritage : L'héritage dans l'arborescence du jeu de données prend en compte les enregistrements et les valeurs définies dans le jeu de données parent, en utilisant un processus récursif. De même, dans un jeu de données racine, un enregistrement peut hériter de certaines de ses valeurs à partir des valeurs par défaut du modèle de données, définies par l'attribut xs:default
.
Calcul de valeur : Un noeud déclaré en tant que osd:function
est toujours calculé à la volée lorsque l'on accède à la valeur. Voir ValueFunction.getValue
.
Filtrage : Un prédicat XPath, un filtre programmatique, ou une règle de permission niveau enregistrement nécessitent une sélection d'enregistrements.
Tri : Un tri des enregistrements qui en résultent peut être réalisé.
Afin d'améliorer la vitesse des opérations sur les tables, des index sont gérés par le moteur d'EBX®.
Les fonctions avancées d'EBX®, telles que le cycle de vie avancé (images et espaces de données), l'héritage de jeu de données, et la modélisation souple de schéma XML, ont conduit à une conception spécialisée des mécanismes d'indexation. Cette conception peut être résumée de la façon suivante :
Les index maintiennent une structure de données en mémoire, relative à une table complète.
Un index n'est pas sauvegardé et sa création nécessite le chargement de tous les blocs de table de la base de données.
Un accès plus rapide aux tables est assuré si les index sont prêts et maintenus dans le cache mémoire. Comme mentionné ci-dessus, il est important pour la Machine Virtuelle Java d'avoir un espace alloué suffisant pour qu'elle ne libère pas les index trop rapidement.
L'optimiseur de requêtes préfère l'utilisation des index lors du calcul du résultat d'une requête.
Seuls les filtres XPath sont pris en compte pour l'optimisation de l'index.
Les index des clés non-primaires ne sont pas pris en compte pour les jeux de données enfants.
En supposant que les index soient déjà construits, les impacts sur les performances sont les suivants :
Si la requête n'inclut ni filtrage, ni règles programmatiques, ni tri, alors l'accès à ses premières lignes (celles récupérées par une vue paginée) est presque instantané.
Si la requête peut être résolue sans étape de tri supplémentaire (c'est le cas s'il n'y a pas de critère de tri, ou si ses critères de tri se rapportent à ceux de l'index utilisé pour le calcul de la requête), l'accès aux premières lignes d'une table devrait être rapide. Plus précisément, cela dépend du coût de l'algorithme de filtrage spécifique exécuté lors de la récupération d'au moins 2000 enregistrements.
Les deux cas ci-dessus garantissent un temps d'accès indépendant de la taille de la table, tout en fournissant une vue triée par l'index utilisé. Si un tri supplémentaire est requis, le temps nécessaire au premier accès dépend de la taille de la table, conformément à une fonction Nlog(N)
, où N
est le nombre d'enregistrements dans la vue résolue.
Les requêtes paginées ajoutent automatiquement la clé primaire à la fin du critère spécifié, afin d'assurer un tri cohérent. Ainsi, les champs de clé primaire devraient aussi être ajoutés à la fin de tout index destiné à améliorer les performances des requêtes paginées. Celles-ci incluent les vues tabulaires et hiérarchiques, ainsi que les menus déroulants pour les clés étrangères.
Si les index ne sont pas encore construits, ou ont été déchargés, un temps supplémentaire est nécessaire. Le temps de construction est O(Nlog(N))
.
L'accès aux blocs de données de la table est nécessaire lorsque la requête ne peut pas être calculée sur un index unique (que ce soit pour résoudre une règle, filtrer ou trier), ainsi que pour la construction de l'index. Si les blocs de table ne sont pas présents en mémoire, un temps supplémentaire est nécessaire afin d'aller les chercher dans la base de données.
Il est possible d'obtenir des informations via les logs de catégorie "request" et "monitoring".
La création de nouveaux enregistrements ou l'insertion d'enregistrements dépend de l'index de la clé primaire. Ainsi, une création devient quasi immédiate si cet index est déjà chargé.
Les informations de fusion dans la table d'historique (le champ merge_info
) a un coût d'accès potentiellement élevé. Pour améliorer les performances et si le code client n'a pas besoin de ce champ, le paramètre includeMergeInfo
doit être défini à false
.
Pour plus d'informations, voir Historique.
Lors du calcul du résultat d'une requête, le moteur d'EBX® délègue ce qui suit au SGBDR :
Le traitement de tous les critères de tri des requêtes, en les traduisant en clause ORDER BY
.
Dans la mesure du possible, le traitement des filtres de requêtes, en les traduisant en clause WHERE
.
Seuls les filtres XPath sont pris en compte pour l'optimisation de l'index. Si la requête comprend des filtres non optimisables, les lignes de la table seront récupérées dans la base de données et filtrées dans la mémoire Java par EBX®, jusqu'à ce que la taille de page demandée soit atteinte. Ceci n'est pas aussi efficace que le filtrage côté base de données (en particulier en ce qui concerne les E/S).
L'information sur la requête SQL transmise est enregistrée dans les logs de catégorie persistance. Voir Configuring the EBX® logs.
Afin d'améliorer la vitesse des opérations sur les tables, des index peuvent être déclarés sur une table au niveau du modèle de données. Cela déclenchera la création d'un index de la table correspondante en base de données.
Lors de la conception d'un index visant à améliorer les performances d'une requête donnée, les mêmes règles que pour la conception d'un index de base de données classique s'appliquent.
Afin d'améliorer les performances, la taille de récupération (fetch size) doit être définie en fonction de la taille attendue pour le résultat de la requête sur une table. Si aucune taille de récupération n'est définie, la valeur par défaut sera utilisée.
En mode sémantique, la valeur par défaut est 2000.
En mode mappé, la valeur par défaut est attribuée par le pilote JDBC : 10 pour Oracle et 0 pour PostgreSQL.
Dans PostgreSQL, la valeur par défaut de 0 charge le pilote JDBC de récupérer l'ensemble des résultats en une seule fois, ce qui pourrait causer une OutOfMemoryError
lors de la récupération de grandes quantités de données. D'autre part, l'utilisation de fetchSize sur PostgreSQL invalidera les curseurs côté serveur en fin de transaction. Si, dans le même thread, on récupère d'abord un résultat défini avec un fetchSize et que l'on exécute ensuite une procédure qui committe la transaction, alors l'accès au résultat suivant générera une erreur.