Clusters : une utilisation performante des ensembles enregistrés
Transcription
Clusters : une utilisation performante des ensembles enregistrés
Clusters : une utilisation performante des ensembles enregistrés Partie 2 Par Kent D. WILBUR, Manager of Information System, 4D Inc. Note technique 4D-200601-02-FR Version 1 Date 1 janvier 2006 Résumé Voici la seconde note sur l’utilisation des clusters (ensembles enregistrés) pour améliorer radicalement les performances d’une base. La première traitait de la création et de la maintenance des clusters. Celle-ci traite l’utilisation des clusters pour la recherche. 4D Notes techniques Copyright © 1985-2006 4D SA - Tous droits réservés Tous les efforts ont été faits pour que le contenu de cette note technique présente le maximum de fiabilité possible. Néanmoins, les différents éléments composant cette note technique, et le cas échéant, le code, sont fournis sans garantie d'aucune sorte. L'auteur et 4D S.A. déclinent donc toute responsabilité quant à l'utilisation qui pourrait être faite de ces éléments, tant à l'égard de leurs utilisateurs que des tiers. Les informations contenues dans ce document peuvent faire l'objet de modifications sans préavis et ne sauraient en aucune manière engager 4D SA. La fourniture du logiciel décrit dans ce document est régie par un octroi de licence dont les termes sont précisés par ailleurs dans la licence électronique figurant sur le support du Logiciel et de la Documentation afférente. Le logiciel et sa documentation ne peuvent être utilisés, copiés ou reproduits sur quelque support que ce soit et de quelque manière que ce soit, que conformément aux termes de cette licence. Aucune partie de ce document ne peut être reproduite ou recopiée de quelque manière que ce soit, électronique ou mécanique, y compris par photocopie, enregistrement, archivage ou tout autre procédé de stockage, de traitement et de récupération d'informations, pour d'autres buts que l'usage personnel de l'acheteur, et ce exclusivement aux conditions contractuelles, sans la permission explicite de 4D SA. 4D, 4D Calc, 4D Draw, 4D Write, 4D Insider, 4ème Dimension ®, 4D Server, 4D Compiler ainsi que les logos 4e Dimension, sont des marques enregistrées de 4D SA. Windows,Windows NT,Win 32s et Microsoft sont des marques enregistrées de Microsoft Corporation. Apple, Macintosh, Power Macintosh, LaserWriter, ImageWriter, QuickTime sont des marques enregistrées ou des noms commerciaux de Apple Computer,Inc. Mac2Win Software Copyright © 1990-2002 est un produit de Altura Software,Inc. 4D Write contient des éléments de "MacLink Plus file translation", un produit de DataViz, Inc,55 Corporate drive,Trumbull,CT,USA. XTND Copyright 1992-2002 © 4D SA. Tous droits réservés. XTND Technology Copyright 1989-2002 © Claris Corporation.. Tous droits réservés ACROBAT © Copyright 1987-2002, Secret Commercial Adobe Systems Inc.Tous droits réservés. ACROBAT est une marque enregistrée d'Adobe Systems Inc. Tous les autres noms de produits ou appellations sont des marques déposées ou des noms commerciaux appartenant à leurs propriétaires respectifs. 1 / 11 Clusters : une utilisation performante des ensembles enregistrés Partie 2 Introduction Voici la seconde note sur l’utilisation des clusters (ensembles enregistrés) pour améliorer radicalement les performances d’une base. La première traitait de la création et de la maintenance des clusters. Celle-ci traite l’utilisation des clusters pour la recherche. Brièvement résumé, l’objectif est de permettre une recherche rapide de mots-clés ou même d’expressions situées n’importe où dans le titre ou le champ texte de la table [BlocsTextes] dans la base exemple. Chaque mot isolé qui ne figure pas dans la liste des exclusions est analysé et enregistré dans la table [Mots]. Les clusters sont des tableaux booléens qui indiquent quels enregistrements comportent un mot particulier. Ils sont sauvegardés dans des BLOBs de la table [Mots]. Les clusters sont-ils vraiment plus rapides ? La réponse est simple, oui ! Mais pour ceux d’entre vous qui en doutent encore, la base exemple propose plusieurs tests. Ouvrez la base et, dans le menu Fichier, choisissez Blocs de texte. Ceci affichera tous les enregistrements de la table [BlocsTexte] et activera la ligne Cluster démo du menu Démo. Le premier enregistrement de la base a pour titre « Upgrading to 4D Server 6.5.1 ». (Oui, je sais, les données sont obsolètes.). Dans cet enregistrement se trouve l’expression « upgrading and converting ». Cette occurrence n’existe que pour ce seul enregistrement. Faisons quelques tests. Si vous avez ouvert l’enregistrement pour vérifier le texte, quittez et rouvrez la base pour vous assurer qu’il n’y a rien dans le cache pour le premier test. Voici le tableau des résultats que j’ai obtenus en recherchant l’expression « upgrading and converting ». 2 / 11 La recherche par cluster a donné le même temps avec ou sans cache. C’est une base relativement réduite, de moins de 6000 enregistrements. Dans une base plus importante, la recherche par « contient » prendrait encore plus de temps, alors que le temps de recherche par cluster resterait virtuellement identique. Pour la recherche par « est égal à », ceci représente un gain de 99,67 %. Donc, ma réponse aujourd’hui est : les clusters peuvent vraiment accélérer significativement ma base avec un peu plus de codage et un plus gros disque dur. Note : Les valeurs ci-dessus sont des moyennes sur plusieurs tests. Elles varient d’une machine à l’autre et selon les critères de recherche. Mais elles sont bien représentatives des résultats attendus. Que se passe-t-il dans le code ? La première requête est une simple recherche par contenu directement dans les champs. C’est quelque chose qui se pratique souvent dans les bases sans clusters. Pour les autres requêtes, les mots cherchés ont été analysés dans un tableau et comparés à la liste des mots exclus (Voir partie 1 pour une explication sur les mots exclus). Après le tri, on construit des tableaux de mots, chaque élément de tableau, un mot unique, est trouvé dans la table [Mots]. Le tableau booléen stocké dans la table [Mots] est chargé en mémoire et un ensemble est créé à partir du tableau booléen. Puis, selon la recherche par OU, ET ou Contient, un nouvel ensemble est créé par la commande REUNION ou INTERSECTION. Dans le cas de la recherche par Contient, les enregistrements obtenus avec l’INTERSECTION sont ensuite explorés par une recherche traditionnelle sur les champs eux-mêmes. La différence est que cette recherche ne concerne que les 7 enregistrements qui contiennent à la fois « upgrading » et « converting » au lieu de toute la table. Le résultat est un gain de temps de plus de 99%. La méthode M_ClusterQuery est attachée au menu Demo de la barre de menu Demo. Elle affiche le dialogue et passe les bons paramètres au code correspondant au choix dans le dialogue. Si (Faux) ` Méthode: M_ClusterQuery ` Created by: Kent Wilbur ` Objet: Démonstration de la recherche par cluster Fin de si ` Déclaration des variables locales C_ENTIER LONG($LEndTime) C_ENTIER LONG($LPid) C_ENTIER LONG($LStartTime) C_POINTEUR($pTable) 3 / 11 $LWindowID:=Creer fenetre formulaire([zDialogs];"QueryDemo";Dialogue modal déplaçable ; Centrée horizontalement ;Centrée verticalement ;*) DIALOGUE([zDialogs];"QueryDemo") FERMER FENETRE($LWindowID) Si (OK=1) $LStartTime:=Nombre de millisecondes Au cas ou : (Longueur(tQueryText)=0) ALERTE("Vous devez entrer une valeur à rechercher.") : (rb1=1) ` Ancienne manière de faire une recherche par contenu CHERCHER([BlocsTextes];[BlocsTextes]Titre="@"+tQueryText+"@";*) CHERCHER([BlocsTextes]; | ;[BlocsTextes]ZoneTexte="@"+tQueryText+"@") Sinon $pTable:=->[BlocsTextes] ` C'est la seule table pour cette base, mais on pourrait en gérer d'autres ici Au cas ou : (rb3=1) ` Nouvelle manière de faire une recherche par contenu CLUSTER_DoQuery ($pTable;tQueryText;"Contains") : (sb1=1) ` Recherche ET CLUSTER_DoQuery ($pTable;tQueryText;"And") Sinon CLUSTER_DoQuery ($pTable;tQueryText;"OR") Fin de cas Au cas ou : (Taille tableau(atQueryValues)=0) ALERTE("La valeur Entrée ne contient que des mots non indexés\rMerci d'entrer une autre ex"+"pression.") : (Enregistrements trouves($pTable->)=0) ALERTE("Aucun enregistrement trouvé.") Fin de cas Fin de cas $LEndTime:=Nombre de millisecondes ALERTE("Temps de recherche : "+Chaine($LEndTime-$LStartTime)+" milliseconde(s) pour " +Chaine(Enregistrements trouves([BlocsTextes]))+" enregistrement(s) trouvé(s.") WIN_OutputWindowTitle Fin de si ` Fin méthode La méthode Cluster_DoQuery a été écrite dès l’origine pour permettre la gestion de plus d’une table. Il suffirait de rajouter des Au cas ou pour chaque table qui utilise la table des clusters. Il faut, bien sûr, un BLOB différent dans la table [Mots] pour chaque table qui utilise la technique des clusters. C’est nécessaire parce qu’on ne peut pas avoir un cluster valide pour un ensemble sur deux tables à la fois. La base centrale 4D Partner a actuellement trois BLOBs stockés dans la table [Mots] pour trois tables principales différentes. Notez que la méthode Cluster_ProcessWordFinds utilise un booléen pour déterminer la condition ET/OU. Ce booléen est passé en paramètre à Cluster_ProcessWordFinds sous forme d’une équation ( $tQueryType= "AND" ) Si (Faux) ` Méthode : CLUSTER_DoQuery(ptr;text;text) ` Created by: Kent Wilbur ` Objet: Effectuer une recherche par cluster Fin de si ` Déclaration des paramètres C_POINTEUR($1;$pTable) 4 / 11 C_TEXTE($2;$tQueryText) C_TEXTE($3;$tQueryType) ` Réaffectation pour plus de lisibilité $pTable:=$1 $tQueryText:=$2 $tQueryType:=$3 CLUSTER_Text2Array ($tQueryText;->atQueryValues) Au cas ou : (Taille tableau(atQueryValues)=0) ` Rien à faire, rien à chercher : ($pTable=(->[BlocsTextes])) ` Faire une recherche ET Au cas ou : ($tQueryType#"Contains") ` Faire une recherche ET/OU CLUSTER_ProcessWordFinds ($pTable;->[Mots]EnsembleBlocTexte; ->atQueryValues;($tQueryType="And")) Sinon `Méthode pour chercher par contenu en utilisant les clusters CLUSTER_ProcessWordFinds ($pTable;->[Mots]EnsembleBlocTexte;->atQueryValues;Vrai) ` Faire une recherche ET CHERCHER DANS SELECTION($pTable->;[BlocsTextes]Titre="@"+$tQueryText+"@";*) ` Chercher dans ce qui reste CHERCHER DANS SELECTION($pTable->; | ;[BlocsTextes]ZoneTexte="@"+$tQueryText+"@") Fin de cas Fin de cas ` Fin méthode La méthode Cluster_ProcessWordFinds gère le passage en sélection courante des enregistrements trouvés dans les clusters. Dans le cas où aucun mot n’est valide, elle réduit cette sélection à la sélection vide. Si un seul mot-clé est à traiter, au lieu de créer et manipuler des ensembles, elle modifie directement la sélection courante. Si (Faux) ` Method: CLUSTER_ProcessWordFinds(ptr;ptr;ptr;bool) ` Created by: Kent Wilbur ` Objet : Traite le tableau de mots à chercher ` $1 = pointeur de table `$2 = pointeur sur le champ blob qui contient les booléens ` $3 = pointeur sur le tableau des mots à rechercher ` $4 = indicateur ET / OU Vrai = ET Fin de si ` Déclaration des paramètres C_POINTEUR($1;$pTable) C_POINTEUR($2;$pBLOBField) C_POINTEUR($3;$pArray) C_BOOLEEN($4;$fAndQuery) ` Déclaration des variables locales C_ENTIER LONG($LSizeOfArray) ` Réaffectation pour plus de lisibilité $pTable:=$1 $pBLOBField:=$2 $pArray:=$3 5 / 11 $fAndQuery:=$4 $LSizeOfArray:=Taille tableau($pArray->) Au cas ou : ($LSizeOfArray=0) REDUIRE SELECTION($pTable->;0) : ($LSizeOfArray=1) CHERCHER([Mots];[Mots]Mot=$pArray->{1}) CLUSTER_LoadFromBLOB ($pTable;$pBLOBField) Sinon CHERCHER([Mots];[Mots]Mot=$pArray->{1}) CLUSTER_LoadFromBLOB ($pTable;$pBLOBField;"TempSet") ENREGISTREMENT SUIVANT([Mots]) Boucle ($i;2;$LSizeOfArray) CHERCHER([Mots];[Mots]Mot=$pArray->{$i}) CLUSTER_LoadFromBLOB ($pTable;$pBLOBField;"TempSet2") Si ($fAndQuery) INTERSECTION("TempSet";"TempSet2";"TempSet") Sinon REUNION("TempSet";"TempSet2";"TempSet") Fin de si ENREGISTREMENT SUIVANT([Mots]) Fin de boucle UTILISER ENSEMBLE("TempSet") EFFACER ENSEMBLE("TempSet") EFFACER ENSEMBLE("TempSet2") Fin de cas ` Fin méthode La méthode Cluster_LoadFromBLOB charge le tableau booléen contenu dans le BLOB et soit le convertit en un ensemble, soit passe simplement en sélection courante le contenu du BLOB sans créer un ensemble, ce qui rend le code plus efficace lorsqu’une seule valeur est recherchée. Si (Faux) ` Method: CLUSTER_LoadFromBLOB(ptr;ptr{;str}) ` Created by: Kent Wilbur ` Objet: Charge le blob dans un ensemble `$1 = pointeur de table `$2 = pointeur sur le champ blob qui contient les booléens `$3 = nom de l'ensemble, si longueur = 0, prendre la sélection courante Fin de si ` Déclaration des paramètres C_POINTEUR($1;$pTable) C_POINTEUR($2;$pBLOBField) C_ALPHA(31;$3;$tSetName) ` Déclaration des variables locales TABLEAU BOOLEEN($afBoolean;0) $pTable:=$1 $pBLOBField:=$2 6 / 11 ` Réaffectation pour plus de lisibilité $tSetName:="" Si (Nombre de parametres>2) $tSetName:=$3 Fin de si BLOB VERS VARIABLE($pBLOBField->;$afBoolean) ` Lire le tableau booléen dans l'enregistrement Si (Longueur($tSetName)=0) ` Nous ne créons pas un ensemble, mais une sélection courante TABLEAU BOOLEEN($afBoolean;Taille tableau($afBoolean)+1) CREER SELECTION SUR TABLEAU($pTable->;$afBoolean;"") Sinon CREER ENSEMBLE SUR TABLEAU($pTable->;$afBoolean;$tSetName) Fin de si ` Fin méthode Une interface Web Il faut très peu de travail supplémentaire pour offrir les mêmes fonctionnalités de recherche via le web. En fait, il suffit de deux méthodes et quelques pages HTML. La page par défaut du site, index.html, est un simple formulaire, presque le même que le formulaire de recherche ci-dessus. Pour charger cette page html, entrez http://127.0.0.1 dans la fenêtre de votre navigateur. Dans la méthode Cluster_DoQuery ci-dessus, le type de recherche est déterminé par les mots ET, OU ou Contient. Donc, les boutons radio du formulaire envoient le mot pour lequel chaque bouton radio est sélectionné. <input type="radio" name="tQueryType" value="And" checked> <strong> Recherche ET</strong><br> <input type="radio" name="tQueryType" value="Or"> <strong> Recherche OU</strong><br> <input type="radio" name="tQueryType" value="Contains"> <strong> Recherche par contenu</strong><br> Le bouton d’envoi utilise 4DACTION et appelle la méthode WEB_ClusterQuery. Du fait du mécanisme d’affectation des variables dans le 4D Web Server intégré, la seule chose à faire est de déclarer toutes les variables passées à 4D via le Web dans une méthode COMPILER_WEB 7 / 11 C_TEXTE(tQueryText) C_TEXTE(tQueryType) C_ENTIER LONG(LRecordNumber) C_TEXTE(tCGI) tQueryText:="" tQueryType:="" LRecordNumber:=0 Une fois les variables définies dans la méthode COMPILER_WEB, 4D affecte automatiquement les variables d’un formulaire HTML aux variables correspondantes de 4D. Les déclarations de COMPILER_WEB sont absolument nécessaires pour assurer le fonctionnement des formulaires envoyés à 4D via le web. La méthode WEB_ClusterQuery réplique les fonctionnalités de la méthode M_ClusterQuery utilisée dans l’interface 4D. Si (Faux) ` Method: WEB_ClusterQuery ` Created by: Kent Wilbur ` Objet: Montrer la recherche par cluster via le web Fin de si CLUSTER_DoQuery (->[BlocsTextes];tQueryText;tQueryType) TABLEAU ENTIER LONG(aLRecordNumber;0) TABLEAU TEXTE(atTitle;0) SELECTION VERS TABLEAU([BlocsTextes];aLRecordNumber;[BlocsTextes]Titre;atTitle) ENVOYER FICHIER HTML("DisplayRecords.shtml") ` Fin méthode Il ya deux manières d’afficher des enregistrements dans une page web avec une boucle. 1) En utilisant les enregistrements eux-mêmes ou 2) en utilisant des tableaux. Nous voulons que l’utilisateur puisse voir l’information du formulaire détaillé s’ils le souhaitent. Pour cela, avec les enregistrements eux-mêmes, nous aurions besoin d’une clé unique pour chaque enregistrement [BlocsTextes]. Mais, pour garder cette base aussi simple que possible, je n’ai pas créé de champ clé. C’est pourquoi nous allons utiliser le numéro d’enregistrement et, de ce fait, il sera plus simple de boucler sur des tableaux plutôt que sur les enregistrements eux-mêmes. Deux tableaux sont créés, l’un pour les numéros d’enregistrement et l’autre pour les titres. Note : Vous ne pouvez pas utiliser des tableaux locaux pour l’affichage sur le web. Enfin, le fichier HTML est envoyé. Notez l’extension .shtml. Ceci force 4D à analyser la page pour rechercher les balises 4dvar qui pourraient se trouver dans la page. Sans l’extension .shtml, la page est considérée comme statique et pourrait ne pas être analysée. 8 / 11 Comme cette méthode est appelée directement par 4DACTION, il est nécessaire d’activer la propriété de la méthode qui l’autorise à être appelée par 4DACTION. Pour protéger votre base des accès indésirables, toute méthode non appelée par 4DACTION devrait avoir cette option de sécurité décochée. (C’est l’option par défaut pour les nouvelles méthodes, mais il faut vérifier cette option pour les bases converties depuis des versions antérieures à 4D 2003 qui ont probablement cette option activée. C’était une nécessité pour la compatibilité descendante.) La page DisplayRecords.shtml C’est le seul élément complexe créé pour afficher correctement les données. C’est complexe parce que cela doit non seulement afficher les données obtenues avec des liens pour montrer les enregistrements trouvés, mais aussi gérer toutes les erreurs d’entrée des utilisateurs. Note : Toutes les pages html fournies utilisent des feuilles de style css. Je ne vais pas exposer ici comment on les utilise ni comment elles fonctionnent. C’est un sujet sur lequel il existe des livres entiers. Je dirai seulement que les feuilles de style css sont utilisées dans les pages html pour donner à la page une apparence générale en matière de couleurs, bordures et polices. Cette note technique ne traite que la part active du corps des pages .shtml. <body> <div id="wrapper"> <!--#4dif (Size of array(atTitle)=0)--> <p><!--#4dif (Length(tQueryText)=0)-->You must enter someting to search if you expect to find anything! <!--#4delse--><!--#4dif (Size of array(atQueryValues)=0)-->Sorry the value entered contains only non-indexed words. Please try another phrase. <!--#4delse-->Sorry, no records were found matching your request. Please try different words.<!--#4dendif--> <!--#4dendif--><br><br><br><br></p> <!--#4delse--> <!--#4DLoop atTitle--> <h1>Title:<a href="<!--#4dvar tCGI-->/4daction/Web_ShowRecord?LRecordNumber= <!--#4dvar aLRecordNumber{atTitle}-->"><!--#4DVAR atTitle{atTitle }--></a></h1> <!--#4DEndLoop--> <!--#4dendif--> </div> 9 / 11 <a href="http://www.4d.com/" id="logo"><img src="/images/4dlogo.gif" width="39" height="53" alt="4D Logo" /></a> </body> Il y a trois conditions d’erreur. Premièrement, aucun critère de recherche n’a été entré ; deuxièmement, tous les mots entrés figurent dans la liste des exclusions ; troisièmement, aucun enregistrement n’a été trouvé. Quoique ces conditions soient mutuellement exclusives, il n’y a pas de balise « Au cas où » pour l’affichage de données dans une page 4d.shtml. De ce fait, le code utilise des Si en cascade, tous basés sur le fait qu’aucun enregistrement n’a été trouvé. Comme dans d’autres technologies .shtml, les balises 4d sont incluses dans des commentaires html : < !--quelque chose-> . Quoiqu’une balise puisse techniquement fonctionner sans le # devant la fonction 4d, il est vivement recommandé de l’inclure systématiquement, pour qu’il soit correctement interprété par les différents éditeurs en cas de modification des documents html. Si des enregistrements sont trouvés, une balise #4dloop est exécutée. Cette balise < !--#4DLoop atTitle--> doit utiliser un tableau avec au moins un élément ou le nom d’une table dont la sélection contient au moins un enregistrement et elle se comporte comme une boucle Boucle (atTitle ;1 ;Taille tableau (atTitle )). Dans cette boucle, le titre est affiché et un lien <a href> est créé à la volée pour chaque enregistrement du tableau. < !--4dvar tCGI/4daction/Web_ShowRecord ?LRecordNumber=< !--#4dvar aLRecordNumber{atTitle}--> Dans cette base, tCGI n’a pas de valeur, rendant le lien relatif à l’URL dans le navigateur. Après analyse, cela pourrait produire quelque chose comme /4daction/Web_ShowRecord ?LRecordNumber=5553. Affichage détaillé via HTML Lorsque l’utilisateur clique sur un lien affiché avec la page DisplayRecords.shtml, la méthode WEB_ShowRecord est appelée. Une fois de plus, assurez-vous que l’option de sécurité 4DACTION est activée dans toutes les bases que vous créez. Si (Faux) ` Method: WEB_ShowRecord ` Created by: Kent Wilbur ` Objet: Affiche l'enregistrement cherché Fin de si ALLER A ENREGISTREMENT([BlocsTextes];LRecordNumber) ENVOYER FICHIER HTML("ShowRecord.shtml") Ceci trouve l’enregistrement et envoie la page ShowRecord.shtml, laquelle affiche simplement les informations sur l’enregistrement courant en utilisant directement les champs de la table. 10 / 11 Résumé Dans cette série de notes techniques, j’ai tenté de montrer à quel point l’implémentation de clusters peut améliorer les requêtes dans votre base. Nos tests sur la base exemple ont renvoyé des résultats en un temps de l’ordre de la milliseconde. Avec requête à un seul mot-clé via une page web, les résultats étaient renvoyés avant que j’aie le temps de bouger ma souris après le clic sur le bouton de recherche. C’est rapide ! En y réfléchissant, vous pouvez adapter les clusters pratiquement à n’importe quoi. Ce ne sont pas obligatoirement des champs textes. N’importe quelle recherche multicritères fréquente est candidate à l’utilisation de clusters. Si vous avez des recherches fréquentes qui prennent plusieurs minutes, pensez aux clusters. Imaginez le passage des minutes aux millisecondes. Lorsqu’on parle d’optimisation pour une base, on répond compilation. C’est généralement une bonne réponse, à moins que la base puisse utiliser les clusters. Rien n’améliore les performances comme le bon usage des clusters. (Mais, vous pouvez bien sûr améliorer la création et la maintenance des clusters grâce à la compilation.) Maintenant que vous les connaissez, si vous en implémentiez dans votre propre base vous pourriez savourer les félicitations de vos utilisateurs finaux pour une si notable accélération. 11 / 11