Recherche

Effacer le filtre
Article
Irène Mykhailova · Oct 5, 2022

Comment profiter au maximum de vos publications sur "Dev Community"

Salut la communauté, Souhaitez-vous bénéficier de soutien, discuter d'une fonctionnalité intéressante, faire une annonce ou partager vos connaissances ? Dans cet article, nous allons vous expliquer comment faire tout cela. Pour faciliter la navigation dans ce "mode d'emploi", suivez simplement le contenu : * [Instructions générales](#GenGuide) * [Question](#Question) * [Article ou annonce](#Article) * [Discussion](#Discussion) ## **Instructions générales** Pour commencer, il faut cliquer sur le bouton "Nouveau message" dans le menu principal du site de la Communauté des développeurs : ![](/sites/default/files/inline/images/images/image(4666).png) Ensuite, vous verrez apparaître l'éditeur qui vous donnera le choix de créer une **Question**, une **Annonce**, un **Article**, ou une **Discussion**. Les différents types de messages ont leurs propres ensembles de champs obligatoires et facultatifs.  ![](/sites/default/files/inline/images/images/image(3998).png) Tout d'abord, parlons des champs communs à tous les types de messages, pour ensuite passer aux particularités. En principe, chaque message comporte un titre (Title\*), un corps (Body\*), un groupe (Group\*), des balises et quelques options supplémentaires permettant d'ajouter un sondage ou de joindre un ou plusieurs fichiers PDF. Tous les champs de texte marqués d'un astérisque (\*) sont obligatoires. Tout d'abord, vous devez choisir le type de message, par exemple, une **Question**, une **Annonce**, un **Article** ou une **Discussion**. Ensuite, veuillez formuler l'idée principale de votre question de la manière la plus précise et la plus concrète possible et la noter sous forme de titre : _Title_.   Ensuite, dans le champ Corps, vous écrivez ce que vous voulez partager avec les autres. Vous avez le choix entre deux options pour rédiger le texte de votre post. Vous pouvez utiliser l'éditeur WYSIWYG ou Markdown. Bien entendu, le résultat sera le même dans les deux cas. ![](/sites/default/files/inline/images/images/image(3999).png)   vs. ![](/sites/default/files/inline/images/images/image(4000).png) Une fois que vous avez fini de rédiger le texte, vous devez choisir le champ Groupe, qui correspond généralement à la technologie, au produit ou au service fourni par InterSystems.  ![](/sites/default/files/inline/images/images/image(4002).png) Après le champ Groupe, voici le champ Tags où vous pouvez ajouter des balises liées au contenu de votre message. Il y a pas mal de balises, donc il faut les choisir de manière responsable car d'autres personnes utilisent ces balises pour chercher ou trier les informations en cas de recherche. ![](/sites/default/files/inline/images/images/image(4003).png) Sous les balises, il y a un lien pour voir plus d'options. Il est possible d'y attacher un document pdf Attach a pdf document (par exemple, l'horaire de l'événement au format pdf) et de fournir le nom que vous souhaitez voir apparaître. ![](/sites/default/files/inline/images/images/image(4004).png) Une autre chose que vous pouvez faire par le biais de plus d'options, c'est le champ "Ajouter un questionnaire" Add a poll. Remplissez les champs avec une question, des réponses possibles, choisissez la durée, etc. Une fois que vous avez terminé, vous pouvez prévisualiser Preview votre message pour voir à quoi il ressemblera pour les autres, vous pouvez le sauvegarder Save pour le modifier plus tard, ou le publier Publish immédiatement. ![](/sites/default/files/inline/images/images/image(4005).png) En outre, vous pouvez programmer la publication de votre message. Il suffit de cliquer sur la flèche vers le bas, de choisir le champ "Programmer le message" Schedule post, puis de définir la date et l'heure.. ![](/sites/default/files/inline/images/images/image(4006).png)   Une fois que tout est prêt, il suffit de cliquer sur "Programmer le message" (Schedule post) et c'est tout!  ![](/sites/default/files/inline/images/images/image(4007).png) Il s'agit essentiellement de la fonctionnalité commune de création d'un message. ## **Question** D'après le nom qu'il porte, il est évident que vous devriez choisir ce type de message si vous avez besoin de l'aide de quelqu'un. Ici, dans la Communauté des développeurs, il y a beaucoup de spécialistes et quelqu'un a peut-être déjà été confronté à la même situation. N'hésitez donc pas à poser des questions ou à y répondre ;) Pour demander de l'aide, veuillez formuler l'idée principale de votre question et la noter sous forme de titre. Ensuite, veuillez sélectionner la version du produit Product version que vous utilisez, car les différentes versions ont des fonctionnalités et des classes différentes et certains conseils pourraient être utiles pour certaines versions et inutiles pour d'autres. ![](/sites/default/files/inline/images/images/image(4008).png) Pour être encore plus précis, vous pouvez fournir une version complète que vous utilisez actuellement dans la zone de texte _$ZV_. Pour obtenir votre version complète, vous pouvez ouvrir Terminal et exécuter la commande : write $ZV ![](/sites/default/files/inline/images/images/image(3991).png)    La même chose peut être faite dans l'EDI que vous utilisez ou vous pouvez le voir dans le portail de gestion : ![](/sites/default/files/inline/images/images/image(3992).png) Les autres champs sont les mêmes que ceux décrits précédemment. ## **Article ou annonce** Pour partager vos connaissances ou faire une annonce, vous devez choisir un type de message **Article** ou **Annonce** respectivement. Ces types de messages, en plus des champs communs, ont également des champs supplémentaires. Il s'agit du Message précédent, le Message suivant, et du _Lien de demande d'échange ouvert_. ![](/sites/default/files/inline/images/images/image(4011).png) Donc, en résumé, si cet article/cette annonce (ou une branche d'une discussion) est lié(e) à un autre, vous pouvez ajouter le lien dans le champ Article précédent (Previous post), et, par conséquent, les autres membres de la communauté verront le bloc Articles connexes suivant à la fin de l'article ![](/sites/default/files/inline/images/images/image(4010).png) Vous n'avez pas besoin d'ouvrir le message précédent et d'y ajouter le lien vers un message suivant, cette opération est effectuée automatiquement. Si votre message est lié à un projet sur Open Exchange, vous pouvez ajouter le lien vers ce projet dans un champ correspondant. ## **Discussion** Pour entamer une conversation sur une des fonctionnalités ou partager votre expérience de l'utilisation de la technologie et demander un retour d'information, vous pouvez lancer une **Discussion**. Ce type de message comporte tous les champs communs ainsi que les liens vers le Message précédent et Message suivant. Et voilà ! C'est ce que vous devez savoir pour commencer un nouveau fil de discussion sur la Communauté. Bonne chance ! Et nous avons hâte de connaître vos impressions ;)
Article
Lorenzo Scalese · Juil 15, 2022

ECP Avec Docker

Salut la communauté, Ceci est le troisième article de la série traitant sur l’initialisation d’instances IRIS avec Docker. Cette fois, on s’intéresse à *E*nterprise *C*ache *P*rotocol (ECP). De manière très simplifiée, ECP permet de configurer certaines instances IRIS avec le rôle de serveur d’applications et d’autres avec le rôle de serveur de données. Vous trouverez toutes les informations techniques détaillées dans la [documentation officielle](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSCALE_ecp). Le but de cet article est de décrire comment nous pouvons scripter l'initialisation d’un serveur de données, d’un ou plusieurs serveurs d’applications et d’établir une connexion cryptée entre ces nœuds avec Docker. Pour cela, nous utilisons certains outils déjà rencontrés dans les articles précédents traitant de la mise en miroir la webgateway tel que OpenSSL, envsubst et Config-API. ## Prérequis ECP n’est pas disponible dans la version IRIS Community. Il est donc nécessaire d’avoir un accès au World Response Center afin de pouvoir télécharger une licence “container” ainsi que pour se connecter au registry containers.intersystems.com. ## Préparation du système Le système doit partager certains fichiers locaux avec les containers. Il est donc nécessaire de créer certains utilisateurs et groupes afin d’éviter l’erreur “access denied”. ```bash sudo useradd --uid 51773 --user-group irisowner sudo useradd --uid 52773 --user-group irisuser sudo groupmod --gid 51773 irisowner sudo groupmod --gid 52773 irisuser ```bash Si vous n’avez pas encore votre licence “iris.key”, téléchargez-là sur le WRC ensuite mettez-là dans votre répertoire home. ## Récupérer le repository d’exemple Tous les fichiers dont vous avez besoin sont disponible sur un [repository public](https://github.com/lscalese/ecp-with-docker), commencez par le cloner: ```bash git clone https://github.com/lscalese/ecp-with-docker.git cd ecp-with-docker ``` ## Certificats SSL Afin de crypter les communications entre les serveurs d’applications et le serveur de données, nous avons besoin de certificats SSL. Le script “gen-certificates.sh” prêt à l’emploi est disponible, toutefois, n’hésitez pas à le modifier pour que les paramètres des certificats soient cohérents avec votre localisation, compagnie, etc… Exécutez: ```bash sh ./gen-certificates.sh ``` Les certificats générés sont dans le répertoire “./certificates”. | Fichier | Container | Description | |--- |--- |--- | | ./certificates/CA_Server.cer | Serveur d’applications et serveur de données | Authority server certificate| | ./certificates/app_server.cer | Serveur d’applications | Certificat pour l’instance IRIS | | ./certificates/app_server.key | Serveur d’applications| Clé privée associée | | ./certificates/data_server.cer | Serveur de données | Certificat pour l’instance IRIS | | ./certificates/data_server.key | Serveur de données | Clé privée associée | ## Construire l’image Avant toute chose, loggez-vous au docker registry d’Intersystems. Lors de la construction, l’image de base sera téléchargée sur le registry: docker login -u="YourWRCLogin" -p="YourICRToken" containers.intersystems.com Si vous ne connaissez pas votre token, vous pouvez le récupérer sur cette page [https://containers.intersystems.com](https://containers.intersystems.com) en vous connectant avec votre compte WRC. Lors de cette construction, nous allons ajouter à l’image de base de IRIS quelques logiciels utilitaires: * gettext-base : qui permettra de substituer les variables d’environnements dans nos fichiers de configuration avec la commande “envsubst”. * iputils-arping : qui est requis dans le cas ou l’on souhaite mettre le serveur de données en miroir. * ZPM: gestionnaire de paquet ObjectScript. [Dockerfile](https://github.com/lscalese/ecp-with-docker/blob/master/Dockerfile) ```Dockerfile ARG IMAGE=containers.intersystems.com/intersystems/iris:2022.2.0.281.0 # Don't need to download the image from WRC. It will be pulled from ICR at build time. FROM $IMAGE USER root # Install iputils-arping to have an arping command. It's required to configure Virtual IP. # Download the latest ZPM version (ZPM is included only with community edition). RUN apt-get update && apt-get install iputils-arping gettext-base && \ rm -rf /var/lib/apt/lists/* USER ${ISC_PACKAGE_MGRUSER} WORKDIR /home/irisowner/demo RUN --mount=type=bind,src=.,dst=. \ iris start IRIS && \ iris session IRIS < iris.script && \ iris stop IRIS quietly ``` Il n’y a rien de particulier dans ce Dockerfile, excepté la dernière ligne. Celle-ci configure l’instance IRIS serveur de données pour accepter jusqu’à 3 serveurs d’applications. Attention, cette configuration nécessite un redémarrage de IRIS. Nous affectons la valeur de ce paramètre pendant la construction afin d’éviter de devoir scripter un redémarrage plus tard. Démarrez la construction: ```bash docker-compose build –no-cache ``` ## Les fichiers de configurations Pour la configuration des instances IRIS (serveurs d’applications et serveur de données) nous utilisons des fichiers format JSON config-api. Vous remarquerez que ceux-ci contiennent des variables d’environnements “${variable_name}”. Leurs valeurs sont définies dans les sections “environment” du fichier docker-compose.yml que nous verrons plus tard dans ce document. Ces variables seront substituées juste avant le chargement des fichiers à l’aide de l’utilitaire “envsubst”. ### Serveur de données Pour le serveur de données, nous allons: * Activer le service ECP et définir la liste des clients autorisés (serveurs d’applications). * Créer la configuration “SSL %ECPServer” nécessaire pour le cryptage des communications. * Créer une base de données “myappdata”. Celle-ci sera utilisée comme base de données distante depuis les applications serveurs. [data-server.json](https://github.com/lscalese/ecp-with-docker/blob/master/config-files/data-server.json) ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true, "ClientSystems":"${CLIENT_SYSTEMS}", "AutheEnabled":"1024" } }, "Security.SSLConfigs": { "%ECPServer": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_SERVER}", "Name": "%ECPServer", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "1", "VerifyPeer": 3 } }, "Security.System": { "SSLECPServer":1 }, "SYS.Databases":{ "/usr/irissys/mgr/myappdata/" : {} }, "Databases":{ "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/" } } } ``` Ce fichier de configuration est chargé au démarrage du container serveur de données par le script “init_datasrv.sh”. Tous les serveurs d’applications qui se connectent au serveur de données doivent être approuvés, ce script validera automatiquement toutes les connexions dans un délai de 100 secondes afin de limiter les actions manuelles dans le portail d’administration. Bien sûr, cela peut être amélioré pour renforcer la sécurité. ### Serveur d’applications Pour les serveurs d’applications, nous allons: * Activer le service ECP. * Créer la configuration SSL “%ECPClient” nécessaire pour le cryptage des communications. * Configurer les informations de connexion au serveur de données. * Créer la configuration de la base de données distante “myapp” * Créer un mapping de globale “demo.*” dans le namespace “USER” a destination la base de données “myappdata”. Ceci nous permettra de tester le fonctionnement de ECP plus tard. [app-server.json](https://github.com/lscalese/ecp-with-docker/blob/master/config-files/app-server.json) ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true } }, "Security.SSLConfigs": { "%ECPClient": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_CLIENT}", "Name": "%ECPClient", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "0" } }, "ECPServers" : { "${DATASERVER_NAME}" : { "Name" : "${DATASERVER_NAME}", "Address" : "${DATASERVER_IP}", "Port" : "${DATASERVER_PORT}", "SSLConfig" : "1" } }, "Databases": { "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/", "Name" : "${REMOTE_DB_NAME}", "Server" : "${DATASERVER_NAME}" } }, "MapGlobals":{ "USER": [{ "Name" : "demo.*", "Database" : "myappdata" }] } } ``` Le fichier de configuration est chargé au démarrage d’un container serveur d’applications par le script “init_appsrv.sh”. ### Démarrer les containers Nous pouvons maintenant démarrer les containers: * 2 serveurs d’applications. * 1 serveur de données. Pour cela, exécutez: ```bash docker-compose up –scale ecp-demo-app-server=2 ``` Voir le fichier docker-compose pour les détails: [Docker-compose](https://github.com/lscalese/ecp-with-docker/blob/master/docker-compose.yml) ```yaml # Les variables sont définies dans le fichier .env file # Pour afficher le fichier de configuration résolu, exécutez : # docker-compose config version: '3.7' services: ecp-demo-data-server: build: . image: ecp-demo container_name: ecp-demo-data-server hostname: data-server networks: app_net: environment: # Liste des serveurs d’applications autorisés. - CLIENT_SYSTEMS=ecp-with-docker_ecp-demo-app-server_1;ecp-with-docker_ecp-demo-app-server_2;ecp-with-docker_ecp-demo-app-server_3 # Chemin vers le certificat du serveur d’autorité. - CA_ROOT=/certificates/CA_Server.cer # Chemin vers le certificat du serveur de données - CA_SERVER=/certificates/data_server.cer # Chemin vers la clé privée du certificat du serveur de données - CA_PRIVATE_KEY=/certificates/data_server.key # Chemin vers le fichier de Configuration Config-API utilisé pour configurer cette instance - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/data-server.json ports: - "81:52773" volumes: # Post start script pour initialiser l’instance du serveur de données. - ./init_datasrv.sh:/home/irisowner/demo/init_datasrv.sh # Montage des certificats - ./certificates/app_server.cer:/certificates/data_server.cer - ./certificates/app_server.key:/certificates/data_server.key - ./certificates/CA_Server.cer:/certificates/CA_Server.cer # Montage du fichier de configuration Config-API - ./config-files/data-server.json:/home/irisowner/demo/data-server.json # Montage du fichier de licence IRIS - ./iris.key:/dur/iris.key command: -a /home/irisowner/demo/init_appsrv.sh ecp-demo-app-server: image: ecp-demo networks: app_net: environment: # Nom d’hôte ou adresse ip du serveur de données. - DATASERVER_IP=data-server - DATASERVER_NAME=data-server - DATASERVER_PORT=1972 # Chemin vers le certificat du serveur d’autorité. - CA_ROOT=/certificates/CA_Server.cer # Chemin vers le certificat du serveur d’applications. - CA_CLIENT=/certificates/app_server.cer # Chemin vers la clé privée du certificat du serveur d’applications. - CA_PRIVATE_KEY=/certificates/app_server.key - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/app-server.json ports: - 52773 volumes: # Post start script pour initialiser l’instance du serveur de données. - ./init_appsrv.sh:/home/irisowner/demo/init_appsrv.sh # Montage des certificats. - ./certificates/CA_Server.cer:/certificates/CA_Server.cer - ./certificates/app_server.cer:/certificates/app_server.cer - ./certificates/app_server.key:/certificates/app_server.key # Montage du fichier de configuration Config-API. - ./config-files/app-server.json:/home/irisowner/demo/app-server.json # Montage du fichier de licence IRIS. - ./iris.key:/dur/iris.key command: -a /home/irisowner/demo/init_appsrv.sh networks: app_net: ipam: driver: default config: # APP_NET_SUBNET variable is defined in .env file - subnet: "${APP_NET_SUBNET}" ``` ## Testons-le! ### Accès au portail d’administration du serveur de données Les containers sont démarrés, vérifions l’état à partir du serveur de données. Le port 52773 est mappé sur le port local 81, vous pouvez donc y accéder avec cette adresse [http://localhost:81/csp/sys/utilhome.csp](http://localhost:81/csp/sys/utilhome.csp). Connectez-vous avec le login\password par défaut ensuite allez dans Système -> Configuration -> Params ECP. Cliquez sur “Serveurs d’application ECP”. Si tout fonctionne bien, vous devriez voir 2 serveurs d’applications avec le statut “Normal”. La structure du nom du client est "data server name":"application server hostname":"IRIS instance name". Dans notre cas, nous n’avons pas paramétré les hostnames des serveurs d’applications, nous avons donc des hostnames auto-générés. ![application server list](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/app-server-list-fr.png) ### Accès au portail d’administration des serveurs d’applications Pour vous connecter au portail d’administration des serveurs d’applications, vous devez d’abord récupérer le numéro de port. Etant donné que nous avons utilisé l’option “--scale”, nous n’avons pas pu fixer les ports dans le fichier docker-compose. Il faut donc les retrouver à l’aide de la commande “docker ps”: ``` docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1844f38939f ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:81->52773/tcp, :::81->52773/tcp ecp-demo-data-server 4fa9623be1f8 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49170->52773/tcp, :::49170->52773/tcp ecp-with-docker_ecp-demo-app-server_1 ecff03aa62b6 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49169->52773/tcp, :::49169->52773/tcp ecp-with-docker_ecp-demo-app-server_2 ``` Dans cet exemple, les ports sont: * 49170 pour le premier serveur d’applications http://localhost:49170/csp/sys/utilhome.csp * 49169 pour le second serveur d’applications http://localhost:49169/csp/sys/utilhome.csp ![](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/data-server-status-fr.png) ### Test de lecture\écriture sur la base de données distante Effectuons maintenant quelques tests de lecture\écriture en terminal. Ouvrons un terminal IRIS sur le premier serveur d’applications: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_1 iris session iris Set ^demo.ecp=$zdt($h,3,1) _ “ écriture à partir du premier serveur d’applications” ``` Ouvrons maintenant un terminal sur le second serveur d’applications: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_2 iris session iris Set ^demo.ecp(2)=$zdt($h,3,1) _ " écriture à partir du second serveur d’applications." zw ^demo.ecp ``` Vous devriez visualiser les écritures effectuées depuis les deux serveurs: ``` ^demo.ecp(1)="2022-07-05 23:05:10 écriture à partir du premier serveur d’applications." ^demo.ecp(2)="2022-07-05 23:07:44 écriture à partir du second serveur d’applications." ``` Pour terminer, ouvrons un terminal IRIS sur le serveur de données et effectuons une lecture de la globale demo.ecp : ``` docker exec -it ecp-demo-data-server iris session iris zw ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(1)="2022-07-05 23:05:10 écriture à partir du premier serveur d’applications." ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(2)="2022-07-05 23:07:44 écriture à partir du second serveur d’applications." ``` Voilà, c’est tout pour aujourd’hui, j’espère que cet article vous a plu. N’hésitez pas à laisser vos commentaires.
Article
Lorenzo Scalese · Mai 18, 2022

Personalisation des index SQL avec des fonctions Python

La recherche d'images comme celle de [Google](https://images.google.com/) est une fonctionnalité intéressante qui m'émerveille - comme presque tout ce qui est lié au traitement des images. Il y a quelques mois, InterSystems a publié un aperçu de [Python Embedded](https://learning.intersystems.com/course/view.php?id=1572&ssoPass=1). Comme Python dispose de nombreuses librairies pour le traitement d'images, j'ai décidé de lancer ma propre tentative pour jouer avec une sorte de recherche d'images - une version beaucoup plus modeste en fait :-) --- --- #### Un peu de théorie 🤓 Pour réaliser un système de recherche d'images, il faut d'abord sélectionner un ensemble de caractéristiques à extraire des images - ces caractéristiques sont également appelées descripteurs. L'étendue de chaque composante de ces descripteurs crée ce qu'on appelle un espace de caractéristiques, et chaque instance de cet espace est appelée un vecteur. Le nombre d de composantes nécessaires pour décrire les vecteurs, définit la dimension de l'espace des caractéristiques et des vecteurs, appelé d-dimensionnel. --- Figure 1 - Un espace caractéristique tridimensionnel et un vecteur descripteur dans cet espace. Credits: https://tinyurl.com/ddd76dln --- Une fois l'ensemble des descripteurs définis, tout ce que vous avez à faire pour rechercher une image dans la base de données est d'extraire les mêmes descripteurs d'une image à rechercher et de les comparer aux descripteurs des images de la base de données - qui ont été précédemment extraits. Dans ce travail, on a simplement utilisé la [couleur dominante de l'image](https://pypi.org/project/fast-colorthief/) comme descripteur (j'ai dit que c'était une version modeste...). Comme une représentation RVB des couleurs a été utilisée, l'espace caractéristique est un espace tridimensionnel - 3d en abrégé. Chaque vecteur dans un tel espace a 3 composantes - (r,g,b), dans la gamme [0, 255]. --- Figure 2 - L'espace tridimensionnel des caractéristiques RVB Credits: https://www.baslerweb.com/fp-1485687434/media/editorial/content_images/faqs/faq_RGB_1.gif --- En traitement du signal, il est très fréquent d'avoir des espaces à n dimensions avec des valeurs de n bien supérieures à 3. En fait, vous pouvez combiner un grand nombre de descripteurs dans un même vecteur afin d'obtenir une meilleure précision. C'est ce qu'on appelle la sélection de caractéristiques et c'est une étape très importante dans les tâches de classification/reconnaissance. Il est également courant de normaliser la plage de dimensions en [0, 1], mais pour des raisons de simplicité, ce travail utilise la plage par défaut [0, 255]. L'avantage de modéliser des caractéristiques sous forme de vecteurs est la possibilité de les comparer à travers des métriques de distance. Il existe de nombreuses distances, chacune ayant ses avantages et ses inconvénients, selon que l'on recherche la performance ou la précision. Dans ce travail, j'ai choisi des distances faciles à calculer - [manhattan et chebyshev](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html), qui sont essentiellement des différences absolues avec une précision raisonnable. --- Figure 3 - Représentation de certains paramètres de distance Credits: https://i0.wp.com/dataaspirant.com/wp-content/uploads/2015/04/cover_post_final.png --- #### Index fonctionnel Mais il ne s'agit que des outils nécessaires pour comparer les images en fonction de leur contenu. Si vous ne disposez pas d'un langage de requête comme SQL, vous vous retrouverez avec des méthodes et des paramètres de recherche fastidieux... De plus, en utilisant SQL, vous pouvez combiner cet index avec d'autres opérateurs bien connus, créant ainsi des requêtes complexes. C'est ici où [Functional Index](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_indices) d'InterSystems est très utile. Un index fonctionnel est une classe qui implémente la classe abstraite [%Library.FunctionalIndex](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&PRIVATE=1&CLASSNAME=%25Library.FunctionalIndex) qui implémente certaines méthodes afin de gérer la tâche d'indexation dans une instruction SQL. Ces méthodes traitent essentiellement les insertions, les suppressions et les mises à jour. ``` /// Indexation fonctionnelle permettant d'optimiser les requêtes sur les données d'image Class dc.multimodel.ImageIndex.Index Extends %Library.FunctionalIndex [ System = 3 ] { /// Cardinalité de l'espace des caractéristiques /// Comme cette classe est destinée à indexer l'image dans l'espace RVB, sa cardinalité est de 3 Paramètre Cardinalité = 3 ; /// Cette méthode est invoquée lorsqu'une instance existante d'une classe est supprimée. ClassMethod DeleteIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Delete(pID, pArg...)") } Return $$$OK } ClassMethod Find(pSearch As %Binary) As %Library.Binary [ CodeMode = generator, ServerOnly = 1, SqlProc ] { If (%mode '= "method") { $$$GENERATE("Set result = """"") $$$GENERATE("Set result = ##class(dc.multimodel.ImageIndex.SQLFind).%New()") $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Set result.Indexer = indexer") $$$GENERATE("Do result.PrepareFind(pSearch)") $$$GENERATE("Return result") } Return $$$OK } /// Cette méthode est invoquée lorsqu'une nouvelle instance d'une classe est insérée dans la base de données. ClassMethod InsertIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Insert(pID, pArg...)") } Return $$$OK } ClassMethod PurgeIndex() [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Set indexGbl = indexer.GetIndexLocation()") $$$GENERATE("Do indexer.Purge()") } Return $$$OK } /// Cette méthode est invoquée lorsqu'une instance existante d'une classe est mise à jour. ClassMethod UpdateIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Update(pID, pArg...)") } Return $$$OK } } ``` J'ai caché une partie du code d'implémentation pour des raisons de lisibilité ; vous pouvez consulter le code dans le lien OpenExchange. Une autre classe abstraite doit être implémentée, c'est [%SQL.AbstractFind](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SQL.AbstractFind), afin de rendre disponible l'utilisation de l'opérateur %FIND pour demander au moteur SQL d'utiliser votre index personnalisé. Une explication beaucoup plus détaillée et conviviale des index fonctionnels est donnée par [@alexander-koblov](https://community.intersystems.com/post/creating-custom-index-type-cach%C3%A9) qui constitue également un excellent exemple d'index fonctionnel. Je vous recommande vivement de le lire. Si vous souhaitez aller plus loin, vous pouvez jouer avec le code source des index [%iFind](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&PRIVATE=1&PACKAGE=%25iFind.Index) et [%UIMA](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&CLASSNAME=%25UIMA.Index) index. Dans ce travail, j'ai configuré une classe de test de persistance simple, où le chemin des images est stocké, et un index personnalisé pour la recherche d'images est défini pour ce champ. ``` Class dc.multimodel.ImageIndex.Test Extends %Persistent { Property Name As %String; Property ImageFile As %String(MAXLEN = 1024); Index idxName On Name [ Type = bitmap ]; Index idxImageFile On (ImageFile) As dc.multimodel.ImageIndex.Index; ``` Notez que idxImageFile est un index personnalisé (dc.multimodel.ImageIndex.Index) pour le champ Image (qui stocke le chemin de l'image). #### Le tour de Python (et COS) ! Ainsi, les classes abstraites d'index fonctionnel vous donneront les points d'entrée où vous pourrez effectuer l'extraction de caractéristiques et la recherche lors de l'exécution des instructions SQL. Maintenant, c'est au tour de Python ! Vous pouvez importer et exécuter le code Python dans un contexte COS en utilisant Python intégré. Par exemple, pour extraire la couleur dominante d'une image : ``` Method GetDominantColorRGB(pFile As %String, ByRef pVector) As %Status { Set sc = $$$OK Try { Set json = ##class(%SYS.Python).Import("json") Set fastcolorthief = ##class(%SYS.Python).Import("fast_colorthief") Set imagepath = pFile Set dominantcolor = fastcolorthief."get_dominant_color"(imagepath, 1) Set vector = {}.%FromJSON(json.dumps(dominantcolor)) Set n = ..Cardinality - 1 For i = 0:1:n { Set pVector(i) = vector.%Get(i) } } Catch(e) { Set sc = e.AsStatus() } Return sc } ``` Dans cette méthode, deux librairies Python sont importées (json et fast_colorthief). La librairie fast_colorthief renvoie une représentation Python de type tableau 3-d avec les valeurs de RGB ; l'autre librairie - json, sérialise ce tableau dans un %DynamicArray. La couleur dominante est extraite pour chaque enregistrement qui est inséré ou mis à jour - une fois que l'index fonctionnel lève les appels aux méthodes InsertIndex et UpdateIndex en réponse aux insertions et mises à jour dans le tableau. Ces caractéristiques sont stockées dans l'index global du tableau : ``` Method Insert(pID As %CacheString, pArgs... As %Binary) { // pArgs(1) has the image path $$$ThrowOnError(..GetDominantColor(pArgs(1), .rgb)) Set idxGbl = ..GetIndexLocation() Set @idxGbl@("model", pID) = "" Merge @idxGbl@("model", pID, "rgb") = rgb Set @idxGbl@("last-modification") = $ZTIMESTAMP } Method Update(pID As %CacheString, pArg... As %Binary) { // pArgs(1) has the image path Set idxGbl = ..GetIndexLocation() Do ..GetDominantColor(pArg(1), .rgb) Kill @idxGbl@("model", pID) Set @idxGbl@("model", pID) = "" Merge @idxGbl@("model", pID, "rgb") = rgb Set @idxGbl@("last-modification") = $ZTIMESTAMP } ``` De la même manière, lorsque des enregistrements sont supprimés, l'index fonctionnel lance des appels aux méthodes DeleteIndex et PurgeIndex. À leur tour, les fonctionnalités doivent être supprimées de l'index global du tableau : ``` Method Delete(pID As %CacheString, pArg... As %Binary) { Set idxGbl = ..GetIndexLocation() Kill @idxGbl@("model", pID) Set @idxGbl@("last-modification") = $ZTIMESTAMP } Method Purge(pID As %CacheString, pArg... As %Binary) { Set idxGbl = ..GetIndexLocation() Kill @idxGbl Set @idxGbl@("last-modification") = $ZTIMESTAMP } ``` L'index global est récupéré par introspection dans la classe persistante : ``` Method GetIndexLocation() As %String { Set storage = ##class(%Dictionary.ClassDefinition).%OpenId(..ClassName).Storages.GetAt(1).IndexLocation Return $NAME(@storage@(..IndexName)) } ``` Lorsque les utilisateurs utilisent l'index dans les clauses WHERE, la méthode Find() est activée par l'index de la fonction. Les instructions de la requête sont transmises afin que vous puissiez les analyser et décider de ce qu'il faut faire. Dans ce travail, les paramètres sont sérialisés en JSON afin de faciliter leur analyse. Les paramètres de la requête ont la structure suivante : ```sql SELECT ImageFile FROM dc_multimodel_ImageIndex.Test WHERE ID %FIND search_index(idxImageFile, '{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}') ``` Dans cette instruction, vous pouvez voir l'utilisation de l'opérateur %FIND et de la fonction search_index. C'est ainsi que SQL accède à notre index personnalisé. Les paramètres de search_index définissent l'index à rechercher - idxImageFile, dans ce cas ; et la valeur à envoyer à l'index. Ici, l'index attend un objet JSON, avec une configuration d'objet définissant : (i) le chemin de l'image, (ii) une limite pour les résultats, et (iii) une stratégie de recherche. Une stratégie de recherche est simplement l'algorithme à utiliser pour effectuer la recherche. Actuellement, deux stratégies sont mises en œuvre : (i) fullscan et (ii) knn, qui correspond à k-proches voisins. La stratégie fullscan consiste simplement en une recherche exhaustive mesurant la distance entre l'image recherchée et chaque image stockée dans la base de données. ``` Method FullScanFindStrategy(ByRef pSearchVector, ByRef pResult) As %Status { Set sc = $$$OK Try { Set idxGbl = ..Indexer.GetIndexLocation() Set rankGbl = ..Indexer.GetRankLocation() Set id = $ORDER(@idxGbl@("model", "")) While (id '= "") { If ($ISVALIDNUM(id)) { Merge vector = @idxGbl@("model", id, "rgb") Set distance = ..Indexer.GetL1Distance(.pSearchVector, .vector) Set result(distance, id) = "" } Set id = $ORDER(@idxGbl@("model", id)) } Kill @rankGbl@(..ImagePath, ..FindStrategy) If (..First '= "") { Set c = 0 Set distance = $ORDER(result("")) While (distance '= "") && (c < ..First) { Merge resultTmp(distance) = result(distance) Set id = $ORDER(result(distance, "")) While (id '= "") { Set @rankGbl@(..ImagePath, ..FindStrategy, id) = distance Set id = $ORDER(result(distance, id)) } Set c = c + 1 Set distance = $ORDER(result(distance)) } Kill result Merge result = resultTmp } Merge pResult = result } Catch ex { Set sc = ex.AsStatus() } Return sc } ``` La stratégie KNN utilise une approche plus sophistiquée. Elle utilise une librairie Python pour créer une structure arborescente appelée [Ball Tree](https://en.wikipedia.org/wiki/Ball_tree). Une telle arborescence convient à une recherche efficace dans un espace à n dimensions. ``` Method KNNFindStrategy(ByRef pSearchVector, ByRef pResult) As %Status { Do ..Log(" ------ KNNFindStrategy ------ ") Set sc = $$$OK Try { Set idxGbl = ..Indexer.GetIndexLocation() Set rankGbl = ..Indexer.GetRankLocation() Set json = ##class(%SYS.Python).Import("json") Set knn = ##class(%SYS.Python).Import("knn") Set first = ..First Set k = $GET(first, 5) Set n = ..Indexer.Cardinality - 1 Set x = "" For i = 0:1:n { Set $LIST(x, * + 1) = pSearchVector(i) } Set x = "[["_$LISTTOSTRING(x, ",")_"]]" $$$ThrowOnError(..CreateOrUpdateKNNIndex()) Set ind = knn.query(x, k, idxGbl) Set ind = {}.%FromJSON(json.dumps(ind.tolist())) Set ind = ind.%Get(0) Kill result Kill @rankGbl@(..ImagePath, ..FindStrategy) Set n = k - 1 For i=0:1:n { Set id = ind.%Get(i) Set result(i, id) = "" Set @rankGbl@(..ImagePath, ..FindStrategy, id) = i } Merge pResult = result } Catch ex { Set sc = ex.AsStatus() } Return sc } ``` Le code Python pour générer une arborescence Ball Tree est présenté ci-dessous : ```python from sklearn.neighbors import BallTree import numpy as np import pickle import base64 import irisnative def get_iris(): ip = "127.0.0.1" port = 1972 namespace = "USER" username = "superuser" password = "SYS" connection = irisnative.createConnection(ip,port,namespace,username,password) dbnative = irisnative.createIris(connection) return (connection, dbnative) def release_iris(connection): connection.close() def normalize_filename(filename): filename = filename.encode('UTF-8') return base64.urlsafe_b64encode(filename).decode('UTF-8') def create_index(index_global, cardinality): connection, dbnative = get_iris() X = get_data(dbnative, index_global, cardinality) tree = BallTree(X, metric = "chebyshev") filename = f"/tmp/${normalize_filename(index_global)}.p" pickle.dump(tree, open(filename, "wb")) release_iris(connection) return tree def get_data(dbnative, index_global, cardinality): X = [] iter_ = dbnative.iterator(index_global, "model") for subscript, value in iter_.items(): id_ = subscript v = [] for i in range(cardinality): v.append( dbnative.get(index_global, "model", id_, "rgb", i) / 255 ) X.append(v) return X def query(x, k, index_global): filename = f"/tmp/${normalize_filename(index_global)}.p" tree = pickle.load(open(filename, "rb")) x = eval(x) x_ = [xi / 255 for xi in x[0]] dist, ind = tree.query([x_], k) return ind ``` Lorsqu'une image est recherchée, l'index personnalisé appelle la méthode de requête de l'objet Ball Tree en Python. Vous pouvez également noter l'utilisation de l'API native d'IRIS afin d'accéder aux valeurs RVB globales de l'index pour la construction de l'arborescence Ball Tree. Pour ordonner les images par similarité, il a été développé une procédure SQL qui traverse une globale stockant les distances précédemment calculées pour chaque image recherchée : ``` Method DiffRank(pSearch As %Binary, pId As %String) As %Float { Set search = {}.%FromJSON(pSearch) If (search.%IsDefined("color_similarity")) { Set config = search.%Get("color_similarity") Set imagePath = config.%Get("image") If (config.%IsDefined("strategy")) { Set findStrategy = config.%Get("strategy") } Set rankGbl = ..Indexer.GetRankLocation() Set rank = $GET(@rankGbl@(imagePath, findStrategy, pId)) Return rank } Return "" } ``` Vous pouvez donc modifier l'instruction SQL pour classer les résultats par similarité : ```sql SELECT ImageFile, dc_multimodel_ImageIndex.Test_idxImageFileDiffRank('{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}', id) AS DiffRank FROM dc_multimodel_ImageIndex.Test WHERE ID %FIND search_index(idxImageFile, '{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}') ORDER BY DiffRank ``` #### Conclusion L'objectif de ce travail était de montrer comment combiner la définition d'index fonctionnels dans COS avec des appels au code Python utilisant leurs étonnantes bibliothèques. De plus, en utilisant cette technique, vous pouvez accéder à des fonctionnalités complexes fournies par les librairies Python dans des instructions SQL, ce qui vous permet d'ajouter de nouvelles fonctionnalités à vos applications.
Article
Guillaume Rongier · Avr 13, 2022

Structure interne des blocs de la base de données Caché, partie 2

Ce texte est la suite de mon [article](https://community.intersystems.com/post/internal-structure-cach%C3%A9-database-blocks-part-1) où j'ai expliqué la structure d'une base de données Caché. Dans cet article, j'ai décrit les types de blocs, les connexions entre eux et leur relation avec les globales. L'article est purement théorique. J'ai fait un [projet](https://github.com/daimor/CacheBlocksExplorer) qui aide à visualiser l'arbre des blocs - et cet article explique comment il fonctionne en détail. [![](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png)](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png) Pour les besoins de la démonstration, j'ai créé une nouvelle base de données et l'ai débarrassée des globales que Caché initialise par défaut pour toutes les nouvelles bases de données. Créons une globale simple : set ^colors(1)="red" set ^colors(2)="blue" set ^colors(3)="green" ​ set ^colors(4)="yellow" ![](https://lh3.googleusercontent.com/uTlHS8bdRsFrQ1v0kfywcJp-6kQeq6uGas7NOLAuV24e7sSZx2EnFb0WQRPZRYE-0mVBB08Fzf-0lRBCLVbWmTGblVlMzQkydztrDJTNiqLhw4ja1PfHGbaTVFApmL_-Gl-fxb8o) Notez l'image illustrant les blocs du global créé. Celui-ci est simple, c'est pourquoi nous voyons sa description dans le bloc de type 9 (bloc catalogue des globales). Il est suivi par le bloc "pointeur supérieur et inférieur" (type 70), car l'arbre des globales n'est pas encore profond, et vous pouvez utiliser un pointeur vers un bloc de données qui tient encore dans un seul bloc de 8 Ko. Maintenant, écrivons tant de valeurs dans une autre globale qu'elles ne peuvent pas être placées dans un seul bloc - et nous verrons de nouveaux nœuds dans le bloc de pointeurs pointant vers de nouveaux blocs de données qui ne pouvaient pas être placés dans le premier. Écrivons 50 valeurs, de 1000 caractères chacune. Rappelez-vous que la taille du bloc dans notre base de données est de 8192 octets. set str="" for i=1:1:1000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str } ​ quit Regardez l'image suivante : ![](https://lh3.googleusercontent.com/tm6axITzAxWACgl5d2V53xHfdWw5MFCRqm92stdMlPjVSSnGvXZH2cRNNoStJQfZUxyCtPk-x9ClbT8xl-lMGJ3qht0zyfs5MPXLjyPt7lSR6_dltexWP1TgbRj2SL4Ot11CQuDY) Nous avons plusieurs nœuds au niveau du bloc de pointeurs pointant vers des blocs de données. Chaque bloc de données contient des pointeurs vers le bloc suivant ("lien correct"). Offset - pointe vers le nombre d'octets occupés dans ce bloc de données. Essayons de simuler une division de bloc. Ajoutons tellement de valeurs au bloc que la taille totale du bloc dépasse 8 Ko, ce qui provoquera la division du bloc en deux. Exemple de code set str="" for i=1:1:1000 { set str=str_"1" } set ^test(3,1)=str set ^test(3,2)=str ​ set ^test(3,3)=str Le résultat est présenté ci-dessous : ![](https://lh6.googleusercontent.com/bk_JK4MZaZxevS1Sd3egpWJD9kgfcP7lo1ueN_1t9uvcrI6KhJxtcr9J2mqGfAgKWHuCQ-t-QE20CWR0WcifhKxv8huKCrSYYhYxppViIA2Q80nFRSKZry_UAcUfln6vxYpzOzwY) Le bloc 50 est divisé et rempli de nouvelles données. Les valeurs remplacées se trouvent maintenant dans le bloc 58 et un pointeur vers ce bloc apparaît maintenant dans le bloc des pointeurs. Les autres blocs sont restés inchangés. **Un exemple avec de longues chaînes de caractères** Si nous utilisons des chaînes plus longues que 8 Ko (la taille du bloc de données), nous obtiendrons des blocs de "données longues". Nous pouvons simuler une telle situation en écrivant des chaînes de caractères de 10000 octets, par exemple. Exemple de code set str="" for i=1:1:10000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str ​ } Voyons le résultat : ![](https://lh3.googleusercontent.com/pPRJxZ_lRjQz6LXzmvPKBNU76XwiAO0xlenWDKR9fYrjmgovPz4dhtbbMx-qiBOZzeNQ49IoaXVf9f3ANsWf_xmZrhZzcFY4Af9VqoChDCXTHKLfcRqtYFGan2ak40dhQ-XPVzLY) En conséquence, la structure des blocs de l'image est restée la même, puisque nous n'avons pas ajouté de nouveaux nœuds globaux, mais seulement modifié les valeurs. Cependant, la valeur Offset (nombre d'octets occupés) a changé pour tous les blocs. Par exemple, la valeur Offset du bloc #51 est maintenant 172 au lieu de 7088. Il est clair que maintenant, lorsque la nouvelle valeur ne peut pas être insérée dans le bloc, le pointeur vers le dernier octet de données devrait être différent, mais où sont nos données ? Pour le moment, mon projet ne supporte pas la possibilité d'afficher des informations sur les "grands blocs". Utilisons l'outil ^REPAIR pour obtenir des informations sur le nouveau contenu du bloc #51. ![](https://lh5.googleusercontent.com/jnJfjd1eF2lwoM_Zk9N4TUL7XithZu92Z05o-lMrLBVmJfXtmZmHkDqIpdE6MxWBCNWVCXUdJQoyXgnXSQdjKZSJmQc5a3SR0ymXckZQxtGMxd9bcCfCX7EdcXPuFj4oYBYz15c0) Laissez-moi vous expliquer le fonctionnement de cet outil. Nous voyons un pointeur sur le bloc correct #52, et le même numéro est spécifié dans le bloc du pointeur parent dans le noeud suivant. Le collatéral de la globale est défini sur le type 5. Le nombre de noeuds avec des chaînes longues est de 7. Dans certains cas, le bloc peut contenir à la fois des valeurs de données pour certains noeuds et des chaînes longues pour d'autres, le tout dans un seul bloc. Nous voyons également quelle référence de pointeur suivante doit être attendue au début du bloc suivant. Concernant les blocs de longues chaînes de caractères : nous voyons que le mot clé "BIG" est spécifié comme valeur du global. Cela nous indique que les données sont en fait stockées dans des "gros blocs". La même ligne contient la longueur totale de la chaîne contenue, et la liste des blocs stockant cette valeur. Jetons un coup d'oeil au "bloc de chaînes longues", le bloc #73. ![](https://lh4.googleusercontent.com/J8ywd3WylH74L_xk0rQQ7z227FHQmvq9-MPI8L1fjN1ZUy8g2XQ8AdvHMErFisYBxHYzC-2y0ho9k_ZgrUayyxNMtl3r7uO7D4SiWpl_6uTTnf1VY7S9VJ0a093MegSSbznmwjnr) Malheureusement, ce bloc est montré encodé. Cependant, nous pouvons remarquer que les informations de service de l'en-tête du bloc (qui font toujours 28 octets) sont suivies de nos données. Connaître le type de données rend le décodage du contenu de l'en-tête assez facile : Position Value Description Comment 0-3 E4 1F 00 00 Offset pointant vers la fin des données Nous avons 8164 octets, plus 28 octets d'en-tête pour un total de 8192 octets, le bloc est plein. 4 18 Type de bloc Comme on s'en souvient, 24 est l'identifiant de type pour les longues chaînes de caractères. 5 05 Collate Collate 5 signifie "Caché standard" 8-11 4A 00 00 00 Lien correct Nous obtenons 74 ici, car nous nous souvenons que notre valeur est stockée dans les blocs 73 et 74 Je vous rappelle que les données du bloc 51 n'occupent que 172 octets. Cela s'est produit lorsque nous avons enregistré de grandes valeurs. Il semble donc que le bloc soit devenu presque vide avec seulement 172 octets de données utiles, et pourtant il occupe 8ko ! Il est clair que dans une telle situation, l'espace libre sera rempli de nouvelles valeurs, mais Caché nous permet également de compresser une telle globale. Pour cela, la classe [%Library.GlobalEdit](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Library.GlobalEdit) dispose de la méthode CompactGlobal. Pour vérifier l'efficacité de cette méthode, utilisons notre exemple avec un grand volume de données - par exemple, en créant 500 noeuds. Voici ce que nous avons obtenu. kill ^test for l=1000,10000 { set str="" for i=1:1:l { set str=str_"1" } for i=1:1:500 { set ^test(i)=str } } quit Nous n'avons pas montré tous les blocs ci-dessous, mais le résultat devrait être clair. Nous avons beaucoup de blocs de données, mais avec un petit nombre de noeuds. ![](https://lh5.googleusercontent.com/98r7ANOIJcjjh1inIP3OB-j_MxqS1eIqIr0SoDKaEMP896Wd5VimDtVwCMEu35wIL8HDojM11hgL2xU_s5Iudjki3VN-yZpfZ-aMwnseuWknFL-dEWwfUzED0cYMfxQ81J6_61nr) Exécution de la méthode CompactGlobal : write ##class(%GlobalEdit).CompactGlobal("test","c:\intersystems\ensemble\mgr\test") Jetons un coup d'oeil au résultat. Le bloc des pointeurs ne compte plus que 2 nœuds, ce qui signifie que toutes nos valeurs sont allées à deux nœuds, alors que nous avions initialement 72 nœuds dans le bloc des pointeurs. Nous nous sommes donc débarrassés de 70 nœuds et avons ainsi réduit le temps d'accès aux données lors du passage par la globale, puisque cela nécessite moins d'opérations de lecture de bloc. ![](https://lh5.googleusercontent.com/Q44fAWxJ7NNuUIoL7Gibf-dnNf3GS34_CmspfoXNzy3uKV4Z24Ge2uwgfByFxK15AnUIUHtvJtsYBL4wRbEHUMnoqYACJzL2nEC0miX3uNadIRC2_sVeDtt8Vg_2qnmuqLX2WZV7) CompactGlobal accepte plusieurs paramètres, comme le nom du global, la base de données et la valeur de remplissage cible, 90% par défaut. Et maintenant nous voyons que Offset (le nombre d'octets occupés) est égal à 7360, ce qui est autour de ces 90%. Quelques paramètres de sortie de la fonction : le nombre de mégaoctets traités et le nombre de mégaoctets après compression. Auparavant, les globales étaient compressés à l'aide de l'outil ^GCOMPACT qui est maintenant considéré comme obsolète. Il convient de noter qu'une situation où les blocs ne sont que partiellement remplis est tout à fait normale. De plus, la compression des globales peut parfois être indésirable. Par exemple, si votre globale est principalement lue et rarement modifiée, la compression peut s'avérer utile. Mais si la globale change tout le temps, une certaine sparsité dans les blocs de données permet d'éviter de diviser les blocs trop souvent, et l'enregistrement de nouvelles données sera plus rapide.
Article
Guillaume Rongier · Juin 10, 2022

Stockage des données - Informations à connaître pour prendre de bonnes décisions lors du développement

Cette publication est le résultat direct d'une collaboration avec un client d'InterSystems qui est venu me consulter pour le problème suivant : SELECT COUNT(*) FROM MyCustomTable Cela prend 0,005 secondes, pour un total de 2300 lignes.  Cependant : SELECT * FROM MyCustomTable Prenait des minutes.  La raison en est subtile et suffisamment intéressante pour que j'écrive un article à ce sujet.  Cet article est long, mais si vous faites défiler la page jusqu'en bas, je vous donnerai un résumé rapide. Si vous êtes arrivé jusqu'ici et que vous pensez en avoir lu assez, faites défiler la page jusqu'à la fin pour connaître l'essentiel.  Vérifiez la phrase en **gras**. * * * Lors de la création de vos classes, il faut tenir compte de la question du stockage.  Comme beaucoup d'entre vous le savent, toutes les données dans Caché sont stockées dans des Globales.     Si vous ne le savez pas, je pense que cet article sera un peu trop long.  Je vous recommande de consulter un excellent tutoriel dans notre documentation : http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS Si vous n'avez jamais utilisé Caché/Ensemble/HealthShare, le tutoriel ci-dessus est très utile, et même si vous l'avez fait, il vaut la peine de le consulter !   Maintenant, comme toutes les données sont stockées dans des globales, il est important de comprendre comment les définitions de vos classes correspondent aux globales.  Construisons une application ensemble !  Nous allons examiner certains pièges courants et discuter de la façon dont le développement de vos classes affecte vos stratégies de stockage, avec un regard particulier sur les performances SQL.   Imaginons que nous soyons le Bureau du recensement des États-Unis et que nous voulions disposer d'une base de données pour stocker les informations concernant tous les habitants des États-Unis.  Nous construisons donc une classe de la manière suivante : Class USA.Person extends %Persistent { Property Name as %String; Property SSN as %String; Property Address as %String; Property DateOfBirth as %Date; } SSN est l'abréviation de "Social Security Number" (numéro de sécurité sociale) qui, bien qu'il n'ait pas été conçu à l'origine pour être un numéro d'identification des personnes, est leur numéro d'identification de facto.  Cependant, comme nous sommes traditionalistes, nous ne l'utiliserons pas pour l'identification.  Cela dit, nous tenons à ce que cet élément soit indexé, car c'est un excellent moyen de rechercher une personne.  Nous savons que nous devrons parfois rechercher des personnes par le nom, c'est pourquoi nous voulons également un index des noms.  Et parce que notre patron aime ses rapports basés sur des tranches d'âge, nous pensons qu'un index des dates de naissance pourrait également être utile.  Ajoutons-les donc à notre classe Class USA.Person extends %Persistent { Property Name as %String; Property SSN as %String; Property Address as %String; Property DateOfBirth as %Date; Index NameIDX On Name; Index SSNIDX On SSN [Unique]; Index DOBIDX on DateOfBirth; } Très bien.  Alors ajoutons une ligne et voyons à quoi ressemblent nos globales.  Notre instruction INSERT est la suivante : INSERT INTO USA.Person (Name,SSN,Address,DateOfBirth) VALUES   ('Baxter, Kyle','111-11-1111','1 Memorial Drive, Cambridge, MA 02142','1985-07-20') Et la globale: USER>zw ^USA.PersonD ^USA.PersonD=1 ^USA.PersonD(1)=$lb("","Baxter, Kyle","111-11-1111","1 Memorial Drive, Cambridge, MA 02142",52796) Le stockage par défaut d'une classe stocke vos données dans ^Package.ClassD.  Si le nom de la classe est trop long, il peut être haché, et vous pouvez le trouver dans la définition de stockage au bas de votre définition de classe.  Les index, à quoi ressemblent-ils ? USER>zw ^USA.PersonI ^USA.PersonI("DOBIDX",52796,1)="" ^USA.PersonI("NameIDX"," BAXTER, KYLE",1)="" ^USA.PersonI("SSNIDX"," 111-11-1111",1)="" Excellent, notre stockage est plutôt bon pour l'instant.  Donc on ajoute nos 320 millions de personnes et on peut trouver des gens assez rapidement.  Mais maintenant nous avons un problème, car nous voulons traiter le président et tous les ex-présidents avec une considération spéciale.  Nous ajoutons donc une classe spéciale pour le président : Class USA.President extends USA.Person { Property PresNumber as %Integer; Index PresNumberIDX on PresNumber; } Bien.  En raison de l'héritage, nous récupérons toutes les propriétés de USA.Person, et nous en ajoutons une pour nous permettre de savoir quel numéro de président il était.  Puisque je veux faire un peu de politique, je vais INSÉRER notre PROCHAIN président.  Voici l'instruction : INSERT INTO USA.President (Name,SSN,DateOfBirth,Address,PresNumber) VALUES ('McDonald,Ronald','221-18-7518','01-01-1963','1600 Pennsylvania Ave NW, Washington, DC 20006',45) Note : Son numéro de sécurité sociale s'écrit 'Burger'.  Désolé si c'est le vôtre. Alors c'est génial !  Regardons votre Globale du Président : USER>zw ^USA.PresidentD Pas de données !  Et c'est là que nous arrivons à l'essentiel de cet article.  Parce que nous avons décidé d'hériter de USA.Person FIRST, nous avons hérité non seulement de ses propriétés et index, mais aussi de son stockage !  Donc pour localiser le président McDonald, nous devons regarder dans ^USA.PersonD.  Et nous pouvons voir ce qui suit : ^USA.PersonD(2)=$lb("~USA.President~","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560) ^USA.PersonD(2,"President")=$lb(45&) Deux choses à noter ici.  La première est que nous pouvons voir que le nœud (2) possède toutes les informations déjà stockées dans USA.Person.  Alors que le noeud (2, "President") ne contient que les informations spécifiques à la classe USA.President.   Qu'est-ce que cela signifie en pratique ?  Eh bien, si nous voulons faire une opération de type : SELECT * FROM USA.President, nous aurons BESOIN de parcourir l'ensemble du tableau des personnes.  Si nous pensons que le tableau des personnes contient 320 000 000 lignes et que le tableau des présidents en contient 45, alors nous devons faire plus de 320 000 045 références globales pour extraire 45 lignes !  En effet, si l'on regarde le plan de requête : * Lire la carte maîtresse USA.President.IDKEY, en bouclant sur ID. * Pour chaque ligne: *  Résultat de la ligne. Nous observons ce que nous attendons.  Cependant, nous avons déjà vu que cela signifie qu'il faut nécessairement regarder dans la globale ^USA.PersonD.  Donc, cela va être une référence globale de 320 000 000+ car nous devons tester CHAQUE ^USA.PersonD pour vérifier s'il y a des données dans ^USA.PersonD(i, "Président") puisque nous ne savons pas quelles personnes seront présidents.  Eh bien, c'est mauvais ! Ce n'est pas du tout ce que nous voulions !  Que pouvons-nous faire ?  Eh bien, nous avons deux options : Option 1 Ajouter un index d'éxtent.  Si nous faisons cela, nous obtenons une liste d'identifiants qui nous permet de savoir quelles personnes sont des présidents et nous pouvons utiliser cette information pour lire des nœuds spécifiques de la globale ^USA.Person.  Comme je dispose d'un stockage par défaut, je peux utiliser un index bitmap, ce qui rendra l'opération encore plus rapide.  Nous ajoutons l'index comme suit : Index Extent [Type=Bitmap, Extent]; Et quand nous regardons notre plan de requête pour SELECT * FROM USA.President nous pouvons voir : ![](http://localhost:57772/csp/sys/images/spacer.gif) * Lecture de l'extent du bitmap USA.President.Extent, en bouclant sur l'ID. * Pour chaque ligne : *  Lecture de la carte maîtresse USA.President.IDKEY, en utilisant la valeur idkey donnée. Résultat de la ligne. *   Ah, maintenant ça va être sympa et rapide.  Une référence globale pour lire l'Extent et ensuite 45 autres pour les présidents.  C'est plutôt efficace.   Les inconvénients ?  La connexion à ce tableau devient un peu plus compliquée et peut impliquer un plus grand nombre de tableaux temporaires que vous ne le souhaiteriez.   Option 2 Changement de la définition de la classe en :: Class USA.President extends (%Persistent, USA.Person) En faisant de %Persistent la première classe étendue, USA.President aura sa propre définition de stockage.  Ainsi, les présidents seront stockés de la manière suivante : USER>zw ^USA.PresidentD ^USA.PresidentD=1 ^USA.PresidentD(1)=$lb("","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560,45) C'est donc une bonne chose, car choisir USA.President signifie simplement lire les 45 membres de cette globale.  C'est facile et agréable, et le design est clair. Les inconvénients ?  Eh bien maintenant, les présidents ne sont PAS dans le tableau des personnes Person.  Donc si vous voulez des informations sur les présidents ET les non-présidents, vous devez faire _SELECT ... FROM USA.Person UNION ALL SELECT ... FROM USA.President_ * * * **Si vous avez arrêté de lire au début, recommencez ici !** Lors de la création d'un héritage, nous avons deux options Option 1: L'héritage de la superclasse est le premier.  Cela permet de stocker les données dans la même globale que la superclasse.  Utile si vous voulez avoir toutes les informations ensemble, et vous pouvez atténuer les problèmes de performance dans la sous-classe en ayant un index extent. Option 2: Héritage de %Persistent first.  Cela permet de stocker les données dans une nouvelle globale.  C'est utile si vous interrogez beaucoup la sous-classe, mais si vous voulez voir les données de la super-classe et de la sous-classe, vous devez utiliser une requête UNION. Laquelle de ces solutions est la meilleure ?  Cela dépend de la façon dont vous allez utiliser votre application.  Si vous souhaitez effectuer un grand nombre de requêtes sur l'ensemble des données, vous opterez probablement pour la première approche.  En revanche, si vous ne pensez pas interroger les données dans leur ensemble, vous opterez probablement pour la seconde approche.  Les deux approches sont tout à fait acceptables, à condition de ne pas oublier l'index extent de l'option 1. Des questions ? Des commentaires ? De longues pensées contradictoires ?  Laissez-les ci-dessous !
Article
Lorenzo Scalese · Oct 3, 2022

Créons un profil FHIR à l'aide de SUSHI Partie 1

Bonjour, chers développeurs ! Dans cette série, je ne vous montrerai pas comment utiliser IRIS for Health, mais plutôt comment utiliser SUSHI, un outil de création de profils FHIR, en tant que technologie associée. Quand on a les outils adéquats, les informations du profil (spécifications, limitations, extensions, etc.) d'un projet FHIR peuvent être bien organisées et publiées. Avant de commencer, qu'est-ce que le SUSHI ? Je vais l'expliquer brièvement. ## Qu'est-ce que le SUSHI ? **FHIR** est un acronyme pour Fast Healthcare Interoperability Resources et est défini comme une **norme pour l'l'échange de dossiers médicaux électroniques** qui peut être mise en œuvre dans un court délai. REST est une technologie standard de communication web permettant d'échanger un ensemble de données (ressources) au format JSON/XML, très lisible et facile à manipuler. En simplifiant, l'idée est d'utiliser un format commun pour représenter les données médicales afin de faciliter le transfert et l'échange d'informations entre les systèmes et les établissements. Il existe plusieurs [ressources](http://hl7.org/fhir/resourcelist.html) définies dans FHIR. Par exemple, les informations sur les patients ont une définition appelée [Ressource Patient](http://hl7.org/fhir/patient.html), qui représente les informations sur les patients. Il existe de nombreux [modèles](http://hl7.org/fhir/patient-examples.html) sur le site officiel de FHIR, j'ai donc pensé que je pourrais en extraire quelques-uns. Par exemple, les données sont exprimées au format JSON de la manière suivante. Le numéro du patient (identifiant), son nom, son sexe, etc. sont représenté. ```JSON { "resourceType": "Patient", "id": "pat1", "texte": { "statut": "généré", "div": "\n \n Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321\n \n " }, "identifiant": [ { "utilisation": "commune", "type": { "codage": [ { "système": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "MR" } ] }, "système": "urn:oid:0.1.2.3.4.5.6.7", "valeur": "654321" } ], "active": true, "nom": [ { "utilisation": "officiel", "nom de famille": "Donald", "prénom": [ "Duck" ] } ], "sex": "masculin", "* snip *" } ``` ## Qu'est-ce que le profil FHIR ? Dans FHIR, il existe des formats de représentation JSON et XML et des règles concernant les informations à répertorier avec le nom de clé JSON, le type de code à utiliser et le type de structure à utiliser. C'est ce qu'on appelle le [Profile FHIR](http://hl7.org/fhir/profiling.html). Le profil est un terme utilisé de différentes manières. Au sens le plus large : Un ensemble de définitions pour les contraintes sur les ressources FHIR et les serveurs FHIR. Un artefact qui le représente. Au sens plus restreint : Au sens plus restreint, une ressource de conformité est une ressource à laquelle des contraintes spécifiques ont été appliquées. Dans ce cas, un profil existe pour chaque ressource (par exemple, Profil de Patient, Profil d'observation).. Pour plus de détails, veuillez regarder la vidéo du [Sommet virtuel japonais 2021] (https://youtu.be/B-B6ge_0nHg) sur les profils FHIR ici. (environ 20 minutes). Bien que le site officiel FHIR fournisse des spécifications par défaut pour chaque ressource, le degré de liberté dans l'utilisation de chaque ressource est considérable. Malheureusement, le degré de liberté dans l'utilisation de chaque ressource est très important. Il est difficile de réaliser des échanges de données interopérables avec les ressources telles qu'elles sont. Il est donc important de définir de nouvelles règles pour la description des ressources sur la base de l'"accord" préalable. Cet "accord" et ces "règles" sont équivalents au Guide de mise en œuvre et au Profil. Les directives de mise en œuvre sont pour la plupart rédigées en texte à l'aide de Word, Excel, HTML, etc. L'une des caractéristiques de FHIR est que le profil FHIR lui-même peut également être exprimé à l'aide de ressources FHIR. Par exemple, il est possible d'exprimer les spécifications au format JSON afin que des produits tels que IRIS for Health puissent intégrer les définitions et étendre les fonctions. D'autre part, le profil est écrit au format JSON, qui peut être traité à l'aide de la ressource StructureDefinition de FHIR. L'Association HL7 des États-Unis a publié IG Publisher, qui génère automatiquement des directives de mise en œuvre à partir de profils. Grâce à cet outil, vous pouvez générer des fichiers HTML de directives de mise en œuvre au format publié par l'Association HL7. La seconde partie de cet article vous montrera comment procéder. À titre d'exemple, il s'agit d'une ressource appelée "StructureDefinition" qui représente les conventions de notation pour les Ressources Patient suggérées pour une utilisation standard aux États-Unis, appelée US Core. ([Référence](https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-patient.json)) ```json { "resourceType" : "StructureDefinition", "id" : "us-core-patient", "texte" : { "statut" : "extensions", "div" : "(snip)" }, "url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient", "version" : "3.1.1", "nom" : "USCorePatientProfile", "titre" : "Profil du patient de base aux États-Unis", "statut" : "active", "expérimental" : false, "date" : "2020-06-27", "éditeur" : "Comité de pilotage HL7 US Realm", "contact" : [ { "telecom" : [ { "systéme" : "url", "valeur" : "http://www.healthit.gov" } ] } ], "(snip)" ``` Il existe également de nombreuses autres ressources qui décrivent le profil FHIR, comme l' [ImplementationGuide] (http://hl7.org/fhir/implementationguide.html) et le [CapabilityStatement] (http://hl7.org/fhir/capabilitystatement.html) qui résume la série de fonctions du serveur FHIR ## Qu'est-ce que FHIR Shorthand? Pour créer un profil FHIR, il suffit de compléter la structure JSON présentée ci-dessus ! Cependant, il serait difficile et fastidieux de le faire manuellement. Je ferais probablement une erreur. Il existe des applications et des outils pour faciliter ce processus, notamment plusieurs produits commerciaux et des options à code source ouvert. Voir [cette] (https://confluence.hl7.org/pages/viewpage.action?pageId=35718864#ProfileTooling-Editing&AuthoringProfiles) page. Un exemple est célèbre Forge de Firely aux Pays-Bas, bien que, plus récemment, une ** langue spécifique au domaine pour définir les artefacts FHIR** appelée **FHIR Shorthand** ([lien](https://build.fhir.org/ig/HL7/fhir-shorthand/index.html)) soit également devenue largement utilisée. FHIR Shorthand est une langue qui vous permet de créer un profil FHIR tout en créant un fichier de définition = fichier FSH(fish) (exemple) Un exemple de fichier FSH est présenté ci-dessous. Le paramètre [quote](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#fsh-line-by-line-walkthrough) contient le nom de ce profil (Profil : Cance. ``` Alias: LNC = http://loinc.org Alias: SCT = http://snomed.info/sct Profil: CancerDiseaseStatus Parent: Observation Id: mcode-cancer-disease-status Titre: "Statut de la maladie cancéreuse" Description: ""Le jugement qualitatif d'un clinicien sur la tendance actuelle du cancer, par exemple, s'il est stable, s'il s'aggrave (progression) ou s'il s'améliore (répond)." * ^statut = #ébauche * l'extension contient un EvidenceType nommé evidenceType 0..* * extension[evidenceType].valueCodeableConcept de CancerDiseaseStatusEvidenceTypeVS (requis) * statut et code et sujet et effectif[x] et valeurCodeableConcept MS * bodySite 0..0 * modèle 0..0 * device 0..0 * referenceRange 0..0 * hasMember 0..0 * composant 0..0 * interprétation 0..1 * sujet 1..1 * basé sur la seule référence (ServiceRequest ou MedicationRequest) (snip) ``` ## Qu'est-ce que SUSHI? Après avoir expliqué le profil FHIR/FHIR/FHIR Shorthand, il est temps d'expliquer SUSHI. SUSHI (acronyme de "SUSHI Unshortens SHorthand Inputs") (4) est une implémentation de référence d'un compilateur FSH qui traduit FSH en artefacts FHIR tels que des profils, des extensions et des ensembles de valeurs. SUSHI est installé sur votre propre ordinateur et s'exécute localement à partir de la ligne de commande ([Reference](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#sushi)) En bref, **lorsque SUSHI traite le fichier FSH(fish) décrivant le FHIR Shorthand mentionné précédemment, des fichiers tels que StructureDefinition seront générés** Voici une illustration, à la fois évidente et quelque peu obscure, montrant comment cela fonctionne ! ([Citation](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#fsh-in-practice)) ![image](https://build.fhir.org/ig/HL7/fhir-shorthand/Workflow.png) (Dans cette image, il est décrit le processus (c'est-à-dire la compilation) de traitement du poisson pour faire du sushi, mais le compilateur SUSHI fait la compilation, et le produit fini est constitué d'artefacts FHIR tels que des profils, ce qui est un peu différent.) J'espère qu'après cette introduction, vous comprenez mieux en quoi consiste le SUSHI. ## Allons à l'école FSH Au lieu d'expliquer ici comment installer et utiliser fondamentalement SUSHI, je vous recommande de visiter le site officiel où vous trouverez une introduction très précise. Le site s'appelle [École FSH] (https://fshschool.org/). Les ressources (fichiers JSON) telles que StructureDefinition sont générées en utilisant SUSHI, et en utilisant l'outil "IG Publisher" également présenté sur ce site, vous pouvez générer la source HTML qui les combine. Vous pouvez également utiliser l'outil "IG Publisher", également présenté sur ce site, pour générer la source HTML pour l'ensemble d'entre eux. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss1.jpg) Tout d'abord, nous vous recommandons de suivre le contenu de ce [Tutoriel SUSHI] (https://fshschool.org/docs/tutorials/basic/) pour vérifier les fonctions de base. Si vous avez des problèmes, vous pouvez vous référer au répertoire FishExampleComplete, qui est la version complète du FSH Tank( !) inclus dans le dossier téléchargeable. Je l'ai testé dans un environnement Windows, et comme je n'avais pas installé Node.js, j'ai utilisé les informations du [site](https://blog.katsubemakito.net/nodejs/install-windows10) pour m'aider ! De plus, comme le stipule le tutoriel ci-dessous, vous aurez besoin d'un outil appelé Jekyll pour produire des fichiers HTML à l'aide d'IG Publisher. >Avertissement > >Avant de passer à la commande suivante : Si vous n'avez jamais exécuté l'IG Publisher, il vous faudra peut-être d'abord installer Jekyll. Consultez la section Installation d'IG Publisher pour plus de détails. Vous pouvez vous procurer le kit Jekyll sur le [site](http://jekyll-windows.juthilo.com/1-ruby-and-devkit/). ## Exemple d'exécution de SUSHI Voici les résultats de l'exécution de la commande SUSHI en utilisant la version complète du tutoriel dans mon environnement. Pour plus d'informations sur la commande, etc., veuillez consulter ce site ([Running SUSHI](https://fshschool.org/docs/sushi/running/)). ``` >sushi . info Running SUSHI v1.2.0 (implémentation de la spécification FHIR Shorthand v1.1.0) info Arguments: info C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\fsh-tutorial-master\FishExampleComplete info Aucune route de sortie spécifiée. Sortie vers . info Utilisation du fichier de configuration : C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\fsh-tutorial-master\FishExampleComplete\sushi-config.yaml info Importation du texte FSH... info Prétraitement de 2 documents avec 3 alias. info Importation de 4 définitions et 1 instance. info Vérification du cache local pour hl7.fhir.r4.core#4.0.1... info Trouver hl7.fhir.r4.core#4.0.1 dans le cache local info Paquet téléchargé hl7.fhir.r4.core#4.0.1 (node:26584) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire (Utilisez `node --trace-warnings ...` pour montrer où l'avertissement a été créé) (node:26584) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire info Conversion des ressources FSH en ressources FHIR... info Conversion de 3 StructureDefinitions FHIR. info Conversion de 1 ValueSets FHIR. info Conversion de 1 instance FHIR info Exportation des ressources FHIR en JSON... info Exportation de 5 ressources FHIR en JSON. info Assemblage des sources du Guide de mise en œuvre... info Génération d' ImplementationGuide-fish.json info Sources du guide de mise en œuvre assemblées ; prêtes pour l'IG Editeur. ╔════════════════════════ RÉSULTATS DE SUSHI ══════════════════════════╗ ║ ╭──────────┬────────────┬───────────┬─────────────┬───────────╮ ║ ║ │ Profils │ Extensions │ ValueSets │ CodeSystems │ Instances │ ║ ║ ├──────────┼────────────┼───────────┼─────────────┼───────────┤ ║ ║ │ 2 │ 1 │ 1 │ 0 │ 1 │ ║ ║ ╰──────────┴────────────┴───────────┴─────────────┴───────────╯ ║ ║ ║ ╠═════════════════════════════════════════════════════════════════╣ ║ Il n'y a pas mieux que ça ! 0 Erreurs 0 Avertissements ║ ╚═════════════════════════════════════════════════════════════════╝ ``` Une fois exécuté, il compilera le fichier FSH et affichera le nombre de profils et d'extensions générés au cours du processus. Si tout va bien, seul "info" sera imprimé, sinon " Avertissement et erreur " sera affiché. Les messages d'erreur sont relativement simples et permettent de déterminer facilement l'origine du problème. Après son exécution, vous pouvez confirmer que le fichier JSON de StructureDefinition a été généré avec succès dans le dossier fsh-generated du dossier du projet. Ensuite, essayons d'obtenir l'outil IG Publisher à l'aide de la commande "_updatePublisher", de lancer IG Publisher à l'aide de la commande "_genonce", et aussi de générer un groupe de fichiers HTML. Comme le journal d'exécution est long, je vais le sauter. Après l'exécution, vérifiez le dossier de sortie dans le même dossier de projet, et vous verrez que de nombreux fichiers ont été générés. Et si vous ouvrez le fichier index.html, vous verrez que la page suivante a été développée. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss2.jpg) Les pages de description des ressources que vous avez l'habitude de voir sur le site officiel de FHIR sont également générées automatiquement. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss3.jpg) ## Rédigeons un guide de mise en œuvre Bien que je ne travaille avec cet ensemble d'outils que depuis peu, je voudrais vous montrer comment rédiger un guide de mise en œuvre pour commencer. Pour plus de détails sur la façon de les utiliser, veuillez vous reporter aux informations figurant sur le site Web de l'école FSH. Les [images FHIR DevDays et autres informations] (https://fshschool.org/downloads/) peuvent également vous être très utiles. Pour commencer, utilisez la commande sushi --init pour créer un modèle pour la structure de votre projet. ``` C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\TestProject>sushi --init ╭───────────────────────────────────────────────────────────╮ │ Cet outil interactif utilisera vos réponses pour créer │ │ un projet SUSHI fonctionnel configuré avec les │ │ informations de base de votre projet. │ ╰───────────────────────────────────────────────────────────╯ Nom (par défaut : ExampleIG) : MonPremierProjetSUSHIP Id (Par default: fhir.example): myfirstsushi Canoniquel (Par default: http://example.org): http://example.org/myfirstsushi Statut (Par default: ébauche): Version (Par default: 0.1.0): Initialiser le projet SUSHI en C :\Users\kaminaka\Documents\Work\FHIR\SUSHI\TestProject\MyFirstSUSHIProject? [y/n]: y Téléchargement des scripts de l'éditeur à partir de https://github.com/HL7/ig-publisher-scripts (node:13972) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire. (Utilisez `node --trace-warnings ...` pour montrer où l'avertissement a été créé) (node:13972) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire. ╭───────────────────────────────────────────────────────────╮ │ Le projet a été initialisé à : ./MyFirstSUSHIProject │ ├───────────────────────────────────────────────────────────┤ │ Maintenant, essayez ceci : │ │ │ │ > cd MyFirstSUSHIProject │ │ > sushi . │ │ │ │ Pour des conseils sur la structure et la configuration │ │ du projet, passez à la documentation SUSHI : │ │ https://fshschool.org/docs/sushi │ ╰───────────────────────────────────────────────────────────╯ ``` Après l'avoir exécuté, il générera le minimum de fichiers de configuration et de FSH requis. Maintenant, apportons quelques modifications. Ensuite, apportons quelques modifications. Avant cela, je voudrais présenter l'éditeur ; je recommande d'utiliser Visual Studio Code, car il y a une [extension] (https://marketplace.visualstudio.com/items?itemName=kmahalingam.vscode-language-fsh) disponible pour modifier les fichiers fsh. Puisque c'est ce que je vais faire, j'aimerais entrer quelques informations japonaises et voir comment elles sont reflétées. Tout d'abord, modifiez sushi-config.yaml. Le titre du guide d'implémentation est ajouté, l'écran de menu est changé en japonais, et la page de la liste des contenus (tuc.html) et la page personnalisée (mycustompage.html) sont ajoutées. sushi-config.yaml ``` # ╭──────────────────────────────────────Guide d'implémentation────────────────────────────────────╮ # │ Les propriétés ci-dessous sont utilisées pour créer la ressource ImplementationGuide. Pour une│ # │ liste des propriétés prises en charge, consultez : https://fshschool.org/sushi/configuration/ │ # ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ id: myfirstsushi canonique: http://example.org/myfirstsushi nom: MyFirstSUSHIProject # Ajoutez un titre qui sera affiché en haut de la page. Titre: ○○FHIR Guide d'implémentation du projet statut: ébauche publisher: InterSystems Japan/S.Kaminaka description: Il s'agit d'un exemple de guide d'implémentation pour le projet FHIR utilisant SUSHI. version: 0.1.0 fhirVersion: 4.0.1 copyrightYear: 2021+ releaseLabel: ci-build # ╭────────────────────────────────────────────menu.xml────────────────────────────────────────────────╮ # │ Pour utiliser un fichier input/includes/menu.xml fourni, supprimez la propriété "menu" ci-dessous.│ # ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ # Configurez le menu pour qu'il s'affiche en japonais. menu: Guide d'implémentation Accueil: index.html Liste de contenu: toc.html Résumé des artefacts FHIR: artifacts.html Page personnalisée: mycustompage.html ``` Les pages d'index et les pages personnalisées peuvent être écrites en markdown : index.md ``` # MyFirstSUSHIProject N'hésitez pas à modifier cette page d'index avec votre propre contenu ! ### Historique du projet pagecontent/index.md Vous pouvez modifier le fichier pour changer la description dans le fichier html. Pour décrire une page, vous pouvez utiliser la notation markdown. ### Liens vers des informations de référence (omission) ``` mycustompage.md ``` ## C'est une page personnalisée. Si vous fournissez un fichier markdown, un fichier html sera généré. Des pages spécifiques au projet peuvent être produites et incluses dans le guide d'implémentation. ``` Enfin, nous allons modifier le fichier FSH le plus essentiel. Ce modèle contient le fichier FSH pour le profil Patient, nous l'avons donc modifié légèrement. patient.fsh ``` // Voici un exemple simple d'un fichier FSH. // Ce fichier peut être renommé, et des fichiers FSH supplémentaires peuvent être ajoutés. // SUSHI recherchera les définitions dans tout fichier utilisant la terminaison .fsh. Profil: MyPatient Parent: Patient Titre: "Profil du patient pour le projet ○○" * nom 1..* MS // La partie descriptive de la liste peut être modifiée en changeant le ^short. * name ^short = "Contient le nom du patient.。" * name ^definition = "Élément permettant de stocker le nom du patient. Kanji et Katakana basés sur NeXEHRS JP CORE" ``` Exécutez maintenant la commande suivante pour voir la page du guide d'implémentation générée. >sushi. > >_updatePublisher > >_genonce Vous pouvez facilement générer une page qui contient les informations suivantes. Il génère également des fichiers JSON tels que StructureDefinition et ImplementationGuide. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss4.jpg) ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss5.jpg) ## Résumé Les résultats vous ont-ils satisfait ? Cet outil génère des fichiers JSON de profils FHIR et peut créer des fichiers HTML. Ainsi, si cet outil est bien utilisé, il sera facile de créer un contenu capable de communiquer les spécifications FHIR d'une manière facile à comprendre. Bien que cet outil ne soit pas directement lié à InterSystems, nous avons pensé qu'il serait utile de le présenter à la communauté des développeurs pour échanger des informations sur le projet FHIR. Pour ceux d'entre vous qui l'ont essayé après avoir lu cet article, ou qui le connaissent déjà, je serais heureux d'entendre vos réflexions sur la façon de l'utiliser, ainsi que toute fonctionnalité ou syntaxe utile que vous souhaiteriez partager. Dans l'article suivant de cette série, je voudrais étendre les paramètres de recherche en lisant le fichier de définition du paramètre de recherche généré par SUSHI dans IRIS for Health. (2021/4/20 Nous avons corrigé la partie ambiguë de l'explication, notamment en ce qui concerne le profil. Merci de nous l'avoir signalé.)
Article
Lorenzo Scalese · Juin 23, 2023

VIP dans l'AWS

Si vous utilisez IRIS dans une configuration miroir pour HA dans AWS, la question de la fourniture d'un [Miroir VIP](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_virtualip) (IP virtuelle) devient pertinente. L'IP virtuelle permet aux systèmes en aval d'interagir avec IRIS en utilisant une seule adresse IP. Même en cas de basculement, les systèmes en aval peuvent se reconnecter à la même adresse IP et continuer à travailler. Le principal problème, lors du déploiement sur AWS, est qu'un VIP IRIS exige que les deux membres du miroir soient dans le même sous-réseau, d'après les [docs](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set#GHA_mirror_set_comm_vip) : > Pour utiliser un miroir VIP, les deux membres du basculement doivent être configurés dans le même sous-réseau et le VIP doit appartenir au même sous-réseau que l'interface réseau sélectionnée sur chaque système. Cependant, pour obtenir l'HA, les membres du miroir IRIS doivent être déployés dans des zones de disponibilité différentes, ce qui signifie des sous-réseaux différents (car les sous-réseaux ne peuvent être que dans un seul az). L'une des solutions pourrait être les équilibreurs de charge, mais ils (A) coûtent cher, et (B) si vous devez acheminer du trafic non-HTTP (comme TCP pour HL7), vous devrez utiliser des équilibreurs de charge de réseau avec une limite de 50 ports au total. Dans cet article, je voudrais fournir un moyen de configurer un Mirror VIP sans utiliser l'équilibrage de la charge du réseau suggéré dans la plupart des autres [architectures de référence AWS](https://aws-quickstart.github.io/quickstart-intersystems-iris/#_architecture). En production, nous avons trouvé des limitations qui entravaient les solutions avec le coût, les limites de 50 auditeurs, les dépendances DNS et la nature dynamique des deux adresses IP qu'AWS fournit à travers les zones de disponibilité. # Architecture ![Architecture(4)](https://user-images.githubusercontent.com/5127457/207903851-77d73195-a43a-4b49-adec-ae6afdf097a3.png) Nous avons un VPC avec trois sous-réseaux privés (je simplifie ici - bien sûr, vous aurez probablement des sous-réseaux publics, un arbitre dans une autre AZ, et ainsi de suite, mais c'est un minimum absolu suffisant pour démontrer cette approche). Le VPC se voit allouer des IPs : `10.109.10.1` à `10.109.10.254` ; les sous-réseaux (dans différents AZs) sont : `10.109.10.1` à `10.109.10.62`, `10.109.10.65` à `10.109.10.126`, et `10.109.10.224` à `10.109.10.254`. # Implementation d'un VIP 1. Sur chaque instance EC2 ([SourceDestCheck](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-sourcedestcheck) doit être définie sur false), nous allons allouer la même adresse IP sur l'interface réseau `eth0:1`. Cette adresse IP se trouve dans la plage CIDR du VPC - dans une zone VIP spéciale. Par exemple, nous pouvons utiliser la dernière IP d'une plage - `10.109.10.254` : ``` cat > /etc/sysconfig/network-scripts/ifcfg-eth0:1 DEVICE=eth0:1 ONPARENT=on IPADDR=10.109.10.254 PREFIX=27 EOFVIP sudo chmod -x /etc/sysconfig/network-scripts/ifcfg-eth0:1 sudo ifconfig eth0:1 up ``` En fonction du système d'exploitation, vous pouvez avoir besoin d'exécuter : ``` ifconfig eth0:1 systemctl restart network ``` 2. En cas de basculement du miroir, mettre à jour la table de routage pour qu'elle pointe vers l'eni sur le nouveau primaire. Nous utiliserons un rappel [ZMIRROR](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_tunable_params_zmirror_routine) pour mettre à jour le tableau de routage après que le membre du miroir actuel soit devenu le primaire. Ce code utilise Python intégré pour : - Obtenir l'IP sur eth0:1 - Obtenir l'InstanceId et la Région à partir des métadonnées de l'instance - Trouver le tableau des routes principales pour le VPC EC2 - Supprimer l'ancienne route, s'il y en a une - Ajouter une nouvelle route pointant vers elle-même Le code: ```py import os import urllib.request import boto3 from botocore.exceptions import ClientError PRIMARY_INTERFACE = 'eth0' VIP_INTERFACE = 'eth0:1' eth0_addresses = [ line for line in os.popen(f'ip -4 addr show dev {PRIMARY_INTERFACE}').read().split('\n') if line.strip().startswith('inet') ] VIP = None for address in eth0_addresses: if address.split(' ')[-1] == VIP_INTERFACE: VIP = address.split(' ')[5] if VIP is None: raise ValueError('Échec de la récupération d'un VIP valide !') # Recherche de l'ID de l'instance du membre du miroir actuel instanceid = ( urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id') .read() .decode() ) region = ( urllib.request.urlopen('http://169.254.169.254/latest/meta-data/placement/region') .read() .decode() ) session = boto3.Session(region_name=region) ec2Resource = session.resource('ec2') ec2Client = session.client('ec2') instance = ec2Resource.Instance(instanceid) # Recherche de l'ID du tableau de routage principal pour ce VPC vpc = ec2Resource.Vpc(instance.vpc.id) for route_table in vpc.route_tables.all(): # Mise à jour du tableau de routage principal pour pointer vers cette instance try: ec2Client.delete_route( DestinationCidrBlock=VIP, RouteTableId=str(route_table.id) ) except ClientError as exc: if exc.response['Error']['Code'] == 'InvalidRoute.NotFound': print('Rien à supprimer, continuer') else: raise exc # Ajout de la nouvelle route ec2Client.create_route( DestinationCidrBlock=VIP, NetworkInterfaceId=instance.network_interfaces[0].id, RouteTableId=str(route_table.id), ) ``` et le même code comme routine ZMIRROR : ```objectscript NotifyBecomePrimary() PUBLIC { try { set dir = $system.Util.ManagerDirectory()_ "python" do ##class(%File).CreateDirectoryChain(dir) try { set boto3 = $system.Python.Import("boto3") } catch { set cmd = "pip3" set args($i(args)) = "install" set args($i(args)) = "--target" set args($i(args)) = dir set args($i(args)) = "boto3" set sc = $ZF(-100,"", cmd, .args) // pour python précédant la version 3.7, installer également dataclasses set boto3 = $system.Python.Import("boto3") } kill boto3 set code = "import os" _ $c(10) _ "import urllib.request" _ $c(10) _ "import boto3" _ $c(10) _ "from botocore.exceptions import ClientError" _ $c(10) _ "PRIMARY_INTERFACE = 'eth0'" _ $c(10) _ "VIP_INTERFACE = 'eth0:1'" _ $c(10) _ "eth0_addresses = [" _ $c(10) _ " line" _ $c(10) _ " for line in os.popen(f'ip -4 addr show dev {PRIMARY_INTERFACE}').read().split('\n')" _ $c(10) _ " if line.strip().startswith('inet')" _ $c(10) _ "]" _ $c(10) _ "VIP = None" _ $c(10) _ "for address in eth0_addresses:" _ $c(10) _ " if address.split(' ')[-1] == VIP_INTERFACE:" _ $c(10) _ " VIP = address.split(' ')[5]" _ $c(10) _ "if VIP is None:" _ $c(10) _ " raise ValueError('Échec de la récupération d'un VIP valide !')" _ $c(10) _ "# Recherche de l'ID de l'instance du membre du miroir actuel" _ $c(10) _ "instanceid = (" _ $c(10) _ " urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id')" _ $c(10) _ " .read()" _ $c(10) _ " .decode()" _ $c(10) _ ")" _ $c(10) _ "region = (" _ $c(10) _ " urllib.request.urlopen('http://169.254.169.254/latest/meta-data/placement/region')" _ $c(10) _ " .read()" _ $c(10) _ " .decode()" _ $c(10) _ ")" _ $c(10) _ "session = boto3.Session(region_name=region)" _ $c(10) _ "ec2Resource = session.resource('ec2')" _ $c(10) _ "ec2Client = session.client('ec2')" _ $c(10) _ "instance = ec2Resource.Instance(instanceid)" _ $c(10) _ "# Recherche de l'ID du tableau de routage principal pour ce VPC" _ $c(10) _ "vpc = ec2Resource.Vpc(instance.vpc.id)" _ $c(10) _ "for route_table in vpc.route_tables.all():" _ $c(10) _ " # Mise à jour du tableau de routage principal pour pointer vers cette instance" _ $c(10) _ " try:" _ $c(10) _ " ec2Client.delete_route(" _ $c(10) _ " DestinationCidrBlock=VIP, RouteTableId=str(route_table.id)" _ $c(10) _ " )" _ $c(10) _ " except ClientError as exc:" _ $c(10) _ " if exc.response['Error']['Code'] == 'InvalidRoute.NotFound':" _ $c(10) _ " print('Rien à supprimer, continuer')" _ $c(10) _ " else:" _ $c(10) _ " raise exc" _ $c(10) _ " # Ajout de la nouvelle route" _ $c(10) _ " ec2Client.create_route(" _ $c(10) _ " DestinationCidrBlock=VIP," _ $c(10) _ " NetworkInterfaceId=instance.network_interfaces[0].id," _ $c(10) _ " RouteTableId=str(route_table.id)," _ $c(10) _ " )" set rc = $system.Python.Run(code) set sc = ##class(%SYS.System).WriteToConsoleLog("Attribution VIP " _ $case(rc, 0:"successful", :"error"), , $case(rc, 0:0, :1), "NotifyBecomePrimary:ZMIRROR") } catch ex { #dim ex As %Exception.General do ex.Log() set sc = ##class(%SYS.System).WriteToConsoleLog("Une exception a été détectée lors de I'attribution d'un VIP : " _ ex.DisplayString(), , 1, "NotifyBecomePrimary:ZMIRROR") } quit 1 } ``` # Démarrage initial `NotifyBecomePrimary` est aussi appelé automatiquement au démarrage du système (après la reconnexion des miroirs), mais si vous voulez que vos environnements non-miroirs acquièrent VIP de la même manière, utilisez la routine [ZSTART](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU_customize_startstop) routine: ```objectscript SYSTEM() PUBLIC { if '$SYSTEM.Mirror.IsMember() { do NotifyBecomePrimary^ZMIRROR() } quit 1 } ``` # Suppression Si vous utilisez des outils de provisionnement automatique, comme CloudFormation, cette route doit être supprimée avant de pouvoir supprimer le sous-réseau. Vous pouvez ajouter le code de suppression à `^%ZSTOP`, mais n'oubliez pas de vérifier `$SYSTEM.Mirror.IsPrimary()` parce que lorsque le miroir primaire s'arrête, pendant `^%ZSTOP` il est toujours primaire. De manière générale, je recommanderais la suppression des routes externes dans le cadre d'un script d'outils de provisionnement. # Conclusion Et c'est tout ! Dans le tableau de routage, nous obtenons une nouvelle route pointant vers un miroir primaire Primary actuel lorsque l'événement `NotifyBecomePrimary` se produit. ![image](https://user-images.githubusercontent.com/5127457/195042101-32f7851a-0ba7-462d-8e0d-0a68c75097aa.png) L'auteur tient à remercier Jared Trog et @Ron.Sweeney1582 pour la création de cette approche. L'auteur tient à remercier @Tomohiro.Iwamoto pour avoir testé cette approche et déterminé toutes les conditions requises pour qu'elle fonctionne.
Article
Guillaume Rongier · Avr 12, 2022

Structure interne des blocs de la base de données Caché, partie 1

Les globales d'InterSystems Caché offrent des fonctionnalités très pratiques pour les développeurs. Mais pourquoi les globales sont-elles si rapides et efficaces ? ### Théorie Fondamentalement, la base de données Caché est un catalogue portant le même nom que la base de données et contenant le fichier CACHE.DAT. Sur les systèmes Unix, la base de données peut également être une partition de disque ordinaire. Toutes les données dans Caché sont stockées dans des blocs qui, à leur tour, sont organisés sous forme d'un arbre B\* équilibré. En tenant compte du fait que tous les globales sont fondamentalement stockées dans un arbre, les indices des globales seront représentés comme des branches, tandis que les valeurs des indices des globales seront stockées comme des feuilles. La différence entre un arbre B\* équilibré et un arbre B ordinaire est que ses branches ont également des liens corrects qui peuvent aider à itérer à travers les souscripts (c'est-à-dire les globales dans notre cas) en utilisant rapidement les fonctions [$Order](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_forder) et [$Query](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fquery) sans revenir au tronc de l'arbre.  Par défaut, chaque bloc du fichier de la base de données a une taille fixe de 8 192 octets. Vous ne pouvez pas modifier la taille du bloc pour une base de données déjà existante. Lorsque vous créez une nouvelle base de données, vous pouvez choisir des blocs de 16 Ko, 32 Ko ou même 64 Ko, en fonction du type de données que vous allez stocker. Cependant, gardez toujours à l'esprit que toutes les données sont lues bloc par bloc - en d'autres termes, même si vous demandez une valeur unique d'un octet, le système lira plusieurs blocs parmi lesquels le bloc de données demandé sera le dernier. Vous devez également vous rappeler que Caché utilise des buffers globaux pour stocker les blocs de base de données en mémoire pour une seconde utilisation, et que les buffers ont la même taille que les blocs. Vous ne pouvez pas monter une base de données existante ou en créer une nouvelle si un buffer global avec la taille de bloc correspondante est absent du système. Vous devez définir la taille de la mémoire que vous souhaitez allouer pour la taille spécifique des blocs. Il est possible d'utiliser des blocs de buffer plus grands que les blocs de base de données, mais dans ce cas, chaque bloc de buffer ne stockera qu'un seul bloc de base de données, voire plus petit. ![](/sites/default/files/inline/images/pasted_image_0.png) Dans cette image, une mémoire pour le buffer global d'une taille de 8 ko est allouée pour l'utilisation avec des bases de données constituées de blocs de 8 ko. Les blocs qui ne sont pas vides dans cette base de données sont définis dans des cartes, de sorte qu'une des cartes définit 62 464 blocs (pour des blocs de 8 ko).  ### Types de blocs Le système prend en charge plusieurs types de blocs. À chaque niveau, les liens corrects d'un bloc doivent pointer vers un bloc du même type ou vers un bloc nul qui définit la fin des données. * **Type 9**: Le système prend en charge plusieurs types de blocs. À chaque niveau, les liens corrects d'un bloc doivent pointer vers un bloc du même type ou vers un bloc nul qui définit la fin des données. * **Type 66**: Bloc de pointeurs de haut niveau. Seul un bloc d'un catalogue global peut se trouver au-dessus de ces blocs. * **Type 6**: Bloc de pointeurs de bas niveau. Seuls les blocs de pointeurs de haut niveau peuvent se trouver au-dessus de ces blocs et seuls les blocs de données peuvent être placés plus bas. * **Type 70**: Bloc de pointeurs de haut niveau et de bas niveau. Ces blocs sont utilisés lorsque la globale correspondante stocke un petit nombre de valeurs et que plusieurs niveaux de blocs ne sont donc pas nécessaires. Ces blocs pointent généralement vers des blocs de données, tout comme le font les blocs d'un catalogue global. * **Type 2**: Bloc de pointeurs pour le stockage de globales relativement grandes. Afin de répartir uniformément les valeurs entre les blocs de données, vous pouvez créer des niveaux supplémentaires de blocs de pointeurs. Ces blocs sont généralement placés entre les blocs de pointeurs. * **Type 8**: Bloc de données. Ces blocs stockent généralement les valeurs de plusieurs nœuds globaux plutôt que celles d'un seul nœud. * **Type 24**: Bloc pour les grandes chaînes de caractères. Lorsque la valeur d'une seule globale est plus grande qu'un bloc, cette valeur est enregistrée dans un bloc spécial pour les grandes chaînes de caractères, tandis que le nœud de bloc de données stocke les liens vers la liste des blocs pour les grandes chaînes de caractères ainsi que la longueur totale de cette valeur. * **Type 16**: Bloc de carte. Ces blocs sont conçus pour stocker des informations sur les blocs non alloués. Ainsi, le premier bloc d'une base de données Caché typique contient des informations de service sur le fichier de base de données lui-même, tandis que le deuxième bloc fournit une carte des blocs. Le premier bloc de catalogue va sur la troisième place (bloc #3), et une seule base de données peut avoir plusieurs blocs de catalogue. Les blocs suivants sont des blocs de pointeurs (branches), des blocs de données (feuilles) et des blocs de grandes chaînes de caractères. Comme je l'ai mentionné ci-dessus, les blocs de catalogues globaux stockent des informations sur tous les globales existants dans la base de données ou les paramètres globaux (si aucune donnée n'est disponible dans une telle globale). Dans ce cas, un nœud qui décrit une telle globale aura un pointeur inférieur nul. Vous pouvez consulter la liste des globales existantes à partir du catalogue de globales sur le portail de gestion. Ce portail vous permet également de sauvegarder une globale dans le catalogue après sa suppression (par exemple, sauvegarder sa séquence de collationnement) ainsi que de créer une nouvelle globale avec un collationnement par défaut ou personnalisé. ![](/sites/default/files/inline/images/pasted_image_0_1.png) ![](/sites/default/files/inline/images/pasted_image_0_2.png) En général, l'arbre des blocs peut être représenté comme dans l'image ci-dessous. Notez que les liens vers les blocs sont représentés en rouge. ![](/sites/default/files/inline/images/pasted_image_0_3.png) ### Intégrité des bases de données Dans la version actuelle de Caché, nous avons résolu les questions et problèmes les plus importants concernant les bases de données, de sorte que les risques de dégradation des bases de données sont extrêmement faibles. Cependant, nous vous recommandons toujours d'exécuter régulièrement des contrôles d'intégrité automatiques à l'aide de notre outil ^Integrity - vous pouvez le lancer dans le terminal à partir de l'espace de noms %SYS, via notre portail de gestion, sur la page Database ou via le gestionnaire de tâches. Par défaut, le contrôle d'intégrité automatique est déjà configuré et prédéfini, de sorte que la seule chose que vous devez faire est de l'activer : ![](/sites/default/files/inline/images/pasted_image_0_4.png) ![](/sites/default/files/inline/images/pasted_image_0_5_0.png)  ​​​​​​​ Le contrôle d'intégrité comprend la vérification des liens aux niveaux inférieurs, la validation des types de blocs, l'analyse des bons liens et la mise en correspondance des nœuds globaux avec la séquence de collationnement appliquée. Si des erreurs sont détectées lors du contrôle d'intégrité, vous pouvez exécuter notre outil ^REPAIR à partir de l'espace de noms %SYS. Grâce à cet outil, vous pouvez visualiser n'importe quel bloc et le modifier si nécessaire, c'est-à-dire réparer votre base de données.  ### Pratique Cependant, ce n'était que de la théorie. Il est encore difficile de savoir à quoi ressemblent réellement une globale et ses blocs. Actuellement, la seule façon de visualiser les blocs est d'utiliser notre outil ^REPAIR mentionné ci-dessus. La sortie typique de ce programme est présentée ci-dessous : ![](/sites/default/files/inline/images/pasted_image_0_6.png)   Il y a un an, j'ai lancé un nouveau projet visant à développer un outil qui itère à travers un arbre de blocs sans risque d'endommager la base de données, qui visualise ces blocs dans une interface utilisateur Web et qui offre des options pour sauvegarder leur visualisation en SVG ou PNG. Le projet s'appelle CacheBlocksExplorer, et vous pouvez télécharger son code source sur [Github](https://github.com/daimor/CacheBlocksExplorer). ![](/sites/default/files/inline/images/pasted_image_0_7.png) Les fonctionnalités mises en œuvre comprennent : * Visualisation de toute base de données configurée ou simplement installée dans le système ; * Affichage des informations par bloc, type de bloc, pointeur droit, liste des nœuds avec liens ; * Afficher des informations détaillées sur tout nœud pointant vers un bloc inférieur ; * Masquer des blocs en supprimant les liens vers ceux-ci (sans aucun dommage pour les données stockées dans ces blocs). La liste des choses à faire : Affichage des liens droits : dans la version actuelle, les liens droits sont affichés dans les informations sur les blocs, mais il serait préférable de les afficher sous forme de flèches ; Affichage des blocs de grandes chaînes de caractères : ils ne sont tout simplement pas affichés dans la version actuelle ; Affichage de tous les blocs du catalogue global plutôt que du troisième seulement. Je voudrais également afficher l'intégralité de l'arbre, mais je ne trouve toujours pas de bibliothèque capable de rendre rapidement des centaines de milliers de blocs avec leurs liens - la bibliothèque actuelle les rend dans les navigateurs web bien plus lentement que Caché ne lit la structure entière. Dans mon prochain article, j'essaierai de décrire plus en détail son fonctionnement, de fournir quelques exemples d'utilisation et de montrer comment récupérer de nombreuses données exploitables sur les globales et les blocs à l'aide de mon Cache Block Explorer.
Article
Guillaume Rongier · Mai 21, 2022

Maîtriser la passerelle SQL JDBC

Comme nous le savons tous, Caché est une excellente base de données qui accomplit de nombreuses tâches en son sein. Cependant, que faites-vous lorsque vous avez besoin d'accéder à une base de données externe ? Une façon de le faire est d'utiliser la passerelle Caché SQL Gateway via JDBC. Dans cet article, mon objectif est de répondre aux questions suivantes pour vous aider à vous familiariser avec cette technologie et à déboguer certains problèmes courants. # Plan de travail - [Quels sont les paramètres de connexion dont vous avez besoin pour vous connecter à une base de données distante ?](#connection-parameters) - [Qu'est-ce que la passerelle JDBC Gateway et le service de passerelle Java Gateway Service dans Ensemble ?](#j-d-b-c-gateway-vs-java-gateway-) - [Quels outils et méthodes sont disponibles pour déboguer les problèmes ?](#tools-and-methods) - [Quels sont les types de problèmes courants et les approches pour les résoudre ?](#common-types-of-problems-and-app) Avant de se plonger dans ces questions, discutons rapidement de l'architecture de la passerelle JDBC SQL Gateway. Pour simplifier, vous pouvez considérer que l'architecture est la suivante : Cache établit une connexion TCP avec un processus Java, appelé processus de passerelle. Le processus de passerelle se connecte ensuite à une base de données distante, telle que Caché, Oracle ou SQL Server, en utilisant le pilote spécifié pour cette base de données. Pour plus d'informations sur l'architecture de la passerelle SQL Gateway, veuillez consulter la documentation sur *[Utilisation de la passerelle Caché SQL Gateway](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_gateway)*. ## Paramètres de connexion Lorsque vous vous connectez à une base de données distante, vous devez fournir les paramètres suivants : - nom d'utilisateur - mot de passe - nom du pilote - URL - chemin de classe ### Connexion à la base de données Caché Par exemple, si vous avez besoin de vous connecter à une instance de Caché en utilisant la passerelle SQL Gateway via JDBC, vous devez naviguer vers `[System Administration] -> [Configuration] -> [Connectivity] -> [SQL Gateway Connections]` dans le portail de gestion du système (SMP). Cliquez ensuite sur "Créer une nouvelle connexion" et spécifiez "JDBC" comme type de connexion. ![](https://community.intersystems.com/sites/default/files/post-associated-docs/connection_parameters_-_cache.png) Lors de la connexion à un système Caché, le nom du pilote doit toujours être `com.intersys.jdbc.CacheDriver`, comme indiqué dans la capture d'écran. Si vous vous connectez à une base de données tierce, vous devrez utiliser un nom de pilote différent (voir [Connexion à des bases de données tierces](#connecting-to-third-party-databa) ci-dessous). Lorsque vous vous connectez aux bases de données Caché, vous n'avez pas besoin de spécifier un chemin de classe car le fichier JAR est téléchargé automatiquement. Le paramètre URL varie également en fonction de la base de données à laquelle vous vous connectez. Pour les bases de données Caché, vous devez utiliser une URL de la forme suivante jdbc:Cache://[server_address]:[superserver_port]/[namespace] ### Connexion à des bases de données tierces Une base de données tierce courante est Oracle. Un exemple de configuration est présenté ci-dessous. ![](https://community.intersystems.com/sites/default/files/post-associated-docs/connection_parameters_-_oracle_-_final.png) Comme vous pouvez le constater, le nom du pilote et l'URL ont des caractéristiques différentes de celles que nous avons utilisées pour la connexion précédente. En outre, j'ai spécifié un chemin de classe dans cet exemple, car je dois utiliser le pilote d'Oracle pour me connecter à leur base de données. Comme vous pouvez l'imaginer, SQL Server utilise différents modèles d'URL et de noms de pilotes. ![](https://community.intersystems.com/sites/default/files/post-associated-docs/connection_parameters_sqlserver.png) Vous pouvez tester si les valeurs sont valides en cliquant sur le bouton " Testez la connexion ". Pour créer la connexion, cliquez sur "Enregistrer". ## JDBC Gateway vs le service Java Gateway Business Service Tout d'abord, la passerelle JDBC et le service de passerelle Java sont complètement indépendants l'un de l'autre. La passerelle JDBC peut être utilisée sur tous les systèmes basés sur Caché, alors que le service de passerelle Java n'existe que dans le cadre d'Ensemble. En outre, le service de passerelle Java utilise un processus différent de celui utilisé par la passerelle JDBC. Pour plus de détails sur le service commercial de passerelle Java, veuillez consulter [Le service commercial de passerelle Java](#the-java-gateway-business-servic). ## Méthodes et outils Vous trouverez ci-dessous 5 outils et méthodes couramment utilisés pour résoudre des problèmes avec la passerelle JDBC SQL Gateway. Je vais d'abord parler de ces outils et vous montrer quelques exemples de leur utilisation dans [la section suivante](#common-types-of-problems-and-app). ### 1. Journaux #### A. Journal du pilote et journal de la passerelle Lorsque vous utilisez la passerelle JDBC, le journal correspondant est le journal de la passerelle JDBC SQL. Comme nous l'avons vu précédemment, la passerelle JDBC est utilisée lorsque Caché doit accéder à des bases de données externes, ce qui signifie que Caché est _le client_. Le journal du pilote, par contre, correspond à l'utilisation du pilote JDBC d'InterSystems pour accéder à une base de données Caché à partir d'une application externe, ce qui signifie que Caché est _le serveur_. Si vous avez une connexion d'une base de données Caché à une autre base de données Caché, les deux types de journaux peuvent être utiles. Dans notre [documentation](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=BGJD_coding#BGJD_coding_logging_jdbc) la section relative à l'activation du journal du pilote est intitulée "Activation de la journalisation pour JDBC", et la section relative à l'activation du journal de la passerelle est intitulée "Activation de la journalisation pour la passerelle SQL JDBC". Même si les deux journaux comportent le mot "JDBC", ils sont totalement indépendants. L'objet de cet article est la passerelle JDBC, c'est pourquoi j'aborderai plus en détail le journal de la passerelle. Pour plus d'informations sur le journal du pilote, veuillez vous reporter à la section [Activation du journal du pilote](#enabling-driver-log). #### B. Activation du journal du pilote Si vous utilisez la passerelle Caché JDBC SQL Gateway, vous devez effectuer les opérations suivantes pour activer la journalisation : dans le portail de gestion, allez dans `[System Administration] > [Configuration] > [Connectivity] > [JDBC Gateway Settings]`. Indiquez une valeur pour le journal de la passerelle JDBC. Ce doit être le chemin complet et le nom d'un fichier journal (par exemple, `/tmp/jdbcGateway.log`). Le fichier sera automatiquement créé s'il n'existe pas, mais le répertoire ne le sera pas. Caché va démarrer la passerelle JDBC SQL Gateway avec journalisation pour vous. Si vous utilisez le service commercial Java Gateway dans Ensemble, veuillez consulter [Activation de la journalisation de la passerelle Java Gateway dans Ensemble](#enabling-java-gateway-logging-in) pour savoir comment activer la journalisation. #### C. Analyse du journal d'une passerelle Maintenant que vous avez collecté un journal de passerelle, vous vous posez peut-être la question suivante : quelle est la structure du journal et comment le lire ? Bonne question ! Je vais vous fournir ici quelques informations de base pour vous aider à démarrer. Malheureusement, il n'est pas toujours possible d'interpréter complètement le journal sans avoir accès au code source. Pour les situations complexes, n'hésitez pas à contacter le WRC (Centre de réponse global d'InterSystems) ! Pour démystifier la structure du journal, rappelez-vous qu'il s'agit toujours d'un morceau de données suivi d'une description de ce qu'il fait. Par exemple, voyez cette image avec une coloration syntaxique de base : ![](https://community.intersystems.com/sites/default/files/post-associated-docs/analyze_gateway_log.png) Afin de comprendre ce que `Received` signifie ici, vous devez vous rappeler que le journal de la passerelle enregistre les interactions entre la passerelle et la base de données descendante. Ainsi, `Received` signifie que la passerelle a reçu l'information de Caché/Ensemble. Dans l'exemple ci-dessus, la passerelle a reçu le texte d'une requête `SELECT`. Les significations des différentes valeurs de `msgId` peuvent être trouvées dans le code interne. Le `33` que nous voyons ici signifie " Preparer l'instruction ". Le journal lui-même fournit également des informations sur le pilote, ce qui est intéressant à vérifier lors du débogage des problèmes. Voici un exemple, ![](https://community.intersystems.com/sites/default/files/post-associated-docs/gateway_log_header.png) Comme nous pouvons le voir, le `Driver Name` est `com.intersys.jdbc.CacheDriver`, ce qui est le nom du pilote utilisé pour se connecter au processus de passerelle. Le `Jar File Name` est `cachejdbc.jar`, ce qui est le nom du fichier jar situé dans `\lib\`. ### 2. Trouver le processus de passerelle Pour trouver le processus de passerelle, vous pouvez exécuter la commande `ps`. Par exemple, ps -ef | grep java Cette commande `ps` affiche des informations sur le processus Java, notamment le numéro de port, le fichier jar, le fichier journal, l'ID du processus Java et la commande qui a lancé le processus Java. Voici un exemple du résultat de la commande : mlimbpr15:~ mli$ ps -ef | grep java 17182 45402 26852 0 12:12PM ?? 0:00.00 sh -c java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.log 2>&1 17182 45403 45402 0 12:12PM ?? 0:00.22 /usr/bin/java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.log 502 45412 45365 0 12:12PM ttys000 0:00.00 grep java Dans Windows, vous pouvez consulter le gestionnaire des tâches pour trouver des informations sur le processus de passerelle. ### 3. Lancement et arrêt de la passerelle Il y a deux façons de lancer et d'arrêter la passerelle : 1. [Par le biais du SMP](#a-through-the-s-m-p) 2. [Utilisation du terminal](#b-using-the-terminal) #### A. Par le biais du SMP Vous pouvez lancer et arrêter la passerelle dans le SMP en accédant à `[System Administration] -> [Configuration] -> [Connectivity] -> [JDBC Gateway Server]`. ![](https://community.intersystems.com/sites/default/files/post-associated-docs/start_stop_jdbc_gateway.png) #### B. Utilisation du terminal Sur les machines Unix, vous pouvez également démarrer la passerelle depuis le terminal. Comme nous l'avons vu dans [la section précédente](#2-finding-the-gateway-process), le résultat de `ps -ef | grep java` contient la commande qui a démarré le processus Java, qui dans l'exemple ci-dessus est le suivant: java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC.log Pour arrêter la passerelle depuis le terminal, vous pouvez tuer le processus. L'ID du processus Java est le deuxième chiffre de la ligne qui contient la commande ci-dessus, dans l'exemple ci-dessus c'est `45402`. Ainsi, pour arrêter la passerelle, vous pouvez exécuter : kill 45402 ### 4. Écrire un programme Java Exécuter un programme Java pour se connecter à une base de données descendante est un excellent moyen de tester la connexion, de vérifier la requête et d'aider à isoler la cause d'un problème donné. Je joins un exemple de programme Java qui établit une connexion avec SQL Server et imprime une liste de tous les tableaux. J'expliquerai pourquoi cela peut être utile dans [la section suivante](#common-types-of-problems-and-app). import java.sql.*; import java.sql.Date; import java.util.*; import java.lang.reflect.Method; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.math.BigDecimal; import javax.sql.*; // Auteur : Vicky Li // Ce programme établit une connexion avec le serveur SQL et récupère tous les tableaux. Le résultat est une liste de tableaux. public class TestConnection { public static void main(String[] args) { try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); //please replace url, username, and password with the correct parameters Connection conn = DriverManager.getConnection(url,username,password); System.out.println("connected"); DatabaseMetaData meta = conn.getMetaData(); ResultSet res = meta.getTables(null, null, null, new String[] {"TABLE"}); System.out.println("List of tables: "); while (res.next()) { System.out.println( " " + res.getString("TABLE_CAT") + ", " + res.getString("TABLE_SCHEM") + ", " + res.getString("TABLE_NAME") + ", " + res.getString("TABLE_TYPE") ); } } catch (Exception e) { e.printStackTrace(); } } } Pour exécuter ce programme Java (ou tout autre programme Java), vous devez d'abord compiler le fichier `.java`, qui dans notre cas s'appelle `TestConnection.java`. Ensuite, un nouveau fichier sera généré au même endroit, que vous pourrez ensuite exécuter avec la commande suivante sur un système UNIX : java -cp "/sqljdbc4.jar:lib/*:." TestConnection Dans Windows, vous pouvez exécuter la commande suivante : java -cp "/sqljdbc4.jar;lib/*;." TestConnection ### 5. Suivi d'une trace de `jstack` Comme son nom l'indique, `jstack` imprime l'arborescence des appels de procédure Java. Cet outil peut devenir pratique lorsque vous avez besoin de mieux comprendre ce que fait le processus Java. Par exemple, si vous voyez le processus de la passerelle s'accrocher à un certain message dans le journal des passerelles, vous pourriez vouloir recueillir une trace `jstack`. Je tiens à souligner que `jstack` est un outil de bas niveau qui ne devrait être utilisé que lorsque d'autres méthodes, comme l'analyse du journal des passerelles, ne résolvent pas le problème. Avant de collecter une trace `jstack`, vous devez vous assurer que le JDK est installé. Voici la commande pour collecter une trace jstack : jstack -F > //jstack.txt où le pid est l'ID du processus de la passerelle, qui peut être obtenu en exécutant la commande `ps`, telle que `ps -ef | grep java`. Pour plus d'informations sur la façon de trouver le pid, veuillez consulter [Lancement et arrêt de la passerelle](#3-starting-and-stopping-the-gate). Maintenant, voici quelques considérations spéciales pour les machines Red Hat. Dans le passé, il y a eu des problèmes pour attacher `jstack` au processus de la passerelle JDBC (ainsi qu'au processus du service métier de la passerelle Java lancé par Ensemble) sur certaines versions de Red Hat, donc la meilleure façon de collecter une trace `jstack` sur Red Hat est de lancer le processus de la passerelle manuellement. Pour les instructions, veuillez consulter [Collecter une trace `jstack` sur Red Hat](#collecting-a-jstack-trace-on-red). ## Types courants de problèmes et approches pour les résoudre ### 1. Problème : Java n'est pas installé correctement Dans cette situation, vérifiez la version de Java et les variables d'environnement. Pour vérifier la version de Java, vous pouvez exécuter la commande suivante à partir d'un terminal : java -version Si vous obtenez l'erreur `java : Command not found`, cela signifie que le processus Cache ne peut pas trouver l'emplacement des exécutables Java. Cela peut généralement être résolu en plaçant les exécutables Java dans le `PATH`. Si vous rencontrez des problèmes, n'hésitez pas à contacter le WRC (Centre de réponse global). ### 2. Problème : échec de la connexion Un bon diagnostic des échecs de connexion est la vérification du lancement du processus de la passerelle. Vous pouvez le faire en vérifiant le [journal de la passerelle](#b-enabling-gateway-log) ou [le processus de la passerelle](#2-finding-the-gateway-process). Sur les versions modernes, vous pouvez également aller sur le SMP et visiter `[System Administration] -> [Configuration] -> [Connectivity] -> [JDBC Gateway Server]`, et vérifier si la page affiche "JDBC Gateway is running". Si le processus de passerelle ne s'exécute pas, il est probable que Java n'est pas installé correctement ou que vous utilisez le mauvais port ; si le processus de passerelle s'exécute, il est probable que les paramètres de connexion sont incorrects. Dans le premier cas, veuillez vous reporter à [la section précédente](#1-problem-java-is-not-installed-) et vérifiez le numéro de port. Je discuterai plus en détail de la deuxième situation ici. Il est de la responsabilité du client d'utiliser les paramètres de connexion corrects : - nom d'utilisateur - mot de passe - nom du pilote - URL - chemin de classe Vous pouvez vérifier si vous avez les bons paramètres de l'une des trois façons suivantes : - Utilisez le bouton "Test Connection" après avoir sélectionné un nom de connexion dans `[System Administration] -> [Configuration] -> [Connectivity] -> [SQL Gateway Connections]`. Note : pour les systèmes modernes, "Test Connection" donne des messages d'erreur utiles ; pour les systèmes plus anciens, le [JDBC gateway log](#b-enabling-gateway-log) est nécessaire pour trouver plus d'informations sur l'échec. - Exécutez la ligne de commande suivante depuis un terminal Caché pour tester la connexion : d $SYSTEM.SQLGateway.TestConnection() - Exécutez un programme Java pour établir une connexion. Le programme que vous écrivez peut être similaire à l' [example](#4-writing-a-java-program) dont nous avons parlé précédemment. ### 3. Problème : décalage entre la façon dont Caché comprend JDBC et la façon dont la base de données distante comprend JDBC, par exemple : - problèmes de type de données - procédure stockée avec des paramètres de sortie - flux Pour cette catégorie, il est souvent plus utile de travailler avec le WRC (Centre de réponse global). Voici ce que nous faisons souvent pour déterminer si le problème se situe dans notre code interne ou dans la base de données distante (ou dans le pilote) : - regarder les journaux et analyser ce qui est envoyé - reproduire le problème en dehors de Caché en [écriture d'un programme java](#4-writing-a-java-program). # Remarque ## Le service commercial de la passerelle Java Le nom de la classe du Service Métier d' Ensemble est `EnsLib.JavaGateway.Service`, et la classe de l'adaptateur est `EnsLib.JavaGateway.ServiceAdapter`. La session Ensemble crée d'abord une connexion avec le serveur Java Gateway, qui est un processus Java. L'architecture est similaire à celle de la passerelle JDBC SQL, sauf que le processus Java est géré par l'opération commerciale. Pour plus de détails, veuillez consulter la [documentation](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EJVG_intro). ## Activation du journal du pilote Pour activer le journal du pilote, vous devez ajouter un nom de fichier journal à la fin de la chaîne de connexion JDBC. Par exemple, si la chaîne de connexion originale ressemble à czci : jdbc:Cache://127.0.0.1:1972/USER Pour activer la journalisation, ajoutez un fichier (`jdbc.log`) à la fin de la chaîne de connexion, de sorte qu'elle ressemble à ceci : jdbc:Cache://127.0.0.1:1972/USER/jdbc.log Le fichier journal sera enregistré dans le répertoire de travail de l'application Java. ## Activation de la journalisation de la passerelle Java dans Ensemble Si vous utilisez le service métier de la passerelle Java dans Ensemble pour accéder à une autre base de données, vous devez, pour activer la journalisation, spécifier le chemin et le nom d'un fichier journal (par exemple, `/tmp/javaGateway.log`) dans le champ "Log File" du service de la passerelle Java. Veuillez noter que le chemin d'accès doit exister. ![](https://community.intersystems.com/sites/default/files/post-associated-docs/java_gateway_log.png) N'oubliez pas que la connexion de la passerelle Java utilisée par la production Ensemble est distincte des connexions utilisées par les tableaux liés ou d'autres productions. Ainsi, si vous utilisez Ensemble, vous devez collecter le journal dans le service de passerelle Java. Le code qui démarre le service de passerelle Java utilise le paramètre "Log File" dans Ensemble, et n'utilise pas le paramètre dans la passerelle Caché SQL dans le SMP comme décrit [précédemment](#b-enabling-gateway-log). ## Récupération d'une trace `jstack` sur Red Hat La clé ici est de lancer le processus de la passerelle manuellement, et la commande pour lancer la passerelle peut être obtenue en exécutant `ps -ef | grep java`. Vous trouverez ci-dessous les étapes complètes à suivre pour collecter une trace `jstack` sur Red Hat lors de l'exécution de la passerelle JDBC ou du service métier de la passerelle Java. 1. Assurez-vous que le JDK est installé. 2. Dans un terminal, exécutez `ps -ef | grep java`. Obtenez les deux informations suivantes à partir du résultat : - a. Copiez la commande qui a lancé la passerelle. Cela devrait ressembler à quelque chose comme ça : `java -Xrs -classpath /Applications/Cache20151/lib/cachegateway.jar:/Applications/Cache20151/lib/cachejdbc.jar com.intersys.gateway.JavaGateway 62972 /Applications/Cache20151/mgr/JDBC2.log` - b. Obtenez l'ID du processus Java (pid), qui est le deuxième chiffre de la ligne qui contient la commande ci-dessus. 3. Arrêtez le processus avec `kill `. 4. Exécutez la commande que vous avez copiée à l'étape 2.a. pour lancer manuellement un processus de passerelle. 5. Jetez un coup d'oeil au journal de la passerelle (dans notre exemple, il est situé dans `/Applications/Cache20151/mgr/JDBC2.log`) et assurez-vous que vous voyez des entrées comme `>> LOAD_JAVA_CLASS: com.intersys.jdbc.CacheDriver`. Cette étape est juste pour vérifier qu'un appel à la passerelle est effectué avec succès. 6. Dans un nouveau terminal, exécutez `ps -ef | grep java` pour obtenir le pid du processus de la passerelle. 7. Rassemblez une trace jstack : `jstack -F > /tmp/jstack.txt`
Article
Sergei Sarkisian · Juil 3, 2022

Quoi de neuf dans Angular 14

Bonjour, je m'appelle Sergei Sarkisian et je crée des fronts Angular depuis plus de 7 ans en travaillant chez InterSystems. Comme Angular est un framework très populaire, nos développeurs, clients et partenaires le choisissent souvent comme partie de la pile pour leurs applications. J'aimerais commencer une série d'articles qui couvriront différents aspects d'Angular : concepts, comment faire, meilleures pratiques, sujets avancés et plus encore. Cette série s'adressera aux personnes qui connaissent déjà Angular et ne couvrira pas les concepts de base. Comme je suis en train d'établir la feuille de route des articles, je voudrais commencer par mettre en évidence certaines fonctionnalités importantes de la dernière version d'Angular. ## Formes strictement typées Il s'agit probablement de la fonctionnalité d'Angular la plus demandée ces dernières années. Avec Angular 14, les développeurs peuvent désormais utiliser toutes les fonctionnalités de vérification stricte des types de TypeScript avec les formulaires réactifs d'Angular. La classe FormControl est maintenant générique et prend le type de la valeur qu'elle contient. ``` /* Avant Angular 14 */ const untypedControl = new FormControl(true); untypedControl.setValue(100); // est définie, aucune erreur // Maintenant const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // vous recevrez le message d'erreur de vérification de type ici // Également dans Angular 14 const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // vous recevrez le message d'erreur de vérification de type ici ``` Comme vous le voyez, le premier et le dernier exemple sont presque identiques, mais les résultats sont différents. Cela se produit parce que dans Angular 14 la nouvelle classe FormControl infère les types à partir de la valeur initiale fournie par le développeur. Ainsi, si la valeur `true` a été fournie, Angular définit le type `boolean | null` pour ce FormControl. La valeur nullable est nécessaire pour la méthode `.reset()` qui annule les valeurs si aucune valeur n'est fournie. Une ancienne classe FormControl non typée a été convertie en `UntypedFormControl` (il en est de même pour `UntypedFormGroup`, `UntypedFormArray` et `UntypedFormBuilder`) qui est pratiquement un alias pour `FormControl`. Si vous effectuez une mise à jour depuis une version précédente d'Angular, toutes les mentions de votre classe `FormControl` seront remplacées par la classe `UntypedFormControl` par Angular CLI. Les classes Untyped* sont utilisées avec des objectifs spécifiques : 1. Faire en sorte que votre application fonctionne absolument comme elle l'était avant la transition de la version précédente (rappelez-vous que le nouveau FormControl déduira le type de la valeur initiale). 2. S'assurer que toutes les utilisations de `FormControl` sont prévues. Donc vous devrez changer tout UntypedFormControl en `FormControl` par vous-même. 3. Pour fournir aux développeurs plus de flexibilité (nous couvrirons ceci ci-dessous) Rappelez-vous que si votre valeur initiale est `null` alors vous devrez explicitement spécifier le type de FormControl. Aussi, il y a un bug dans TypeScript qui exige de faire la même chose si votre valeur initiale est `false`. Pour le groupe de formulaire, vous pouvez aussi définir l'interface et juste passer cette interface comme type pour le FormGroup. Dans ce cas, TypeScript déduira tous les types à l'intérieur du FormGroup. ``` interface LoginForm { email: FormControl; password?: FormControl; } const login = new FormGroup({ email: new FormControl('', {nonNullable: true}), password: new FormControl('', {nonNullable: true}), }); ``` La méthode de FormBuilder `.group()` a maintenant un attribut générique qui peut accepter votre interface prédéfinie comme dans l'exemple ci-dessus où nous avons créé manuellement FormGroup : ``` interface LoginForm { email: FormControl; password?: FormControl; } const fb = new FormBuilder(); const login = fb.group({ email: '', password: '', }); ``` Comme notre interface n'a que des types primitifs non nuls, elle peut être simplifiée avec la nouvelle propriété nonNullable FormBuilder (qui contient l'instance de la classe NonNullableFormBuilder qui peut aussi être créée directement) : ``` const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }) ``` ❗ Notez que si vous utilisez un FormBuilder non Nullable ou si vous définissez une option nonNullable dans le FormControl, alors lorsque vous appelez la méthode `.reset()`, elle utilisera la valeur initiale du FormControl comme valeur de réinitialisation. Aussi, il est très important de noter que toutes les propriétés dans `this.form.value` seront marquées comme optionnelles. Comme ceci : ``` const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); // login.value // { // email?: string; // password?: string; // } ``` Cela se produit parce que lorsque vous désactivez un FormControl à l'intérieur du FormGroup, la valeur de ce FormControl sera supprimée de `form.value`. ``` const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.value); // { // password: '' // } ``` Pour obtenir l'objet complet du formulaire, vous devez utiliser la méthode `.getRawValue()` : ``` const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.getRawValue()); // { // email: '', // password: '' // } ``` Avantages des formulaires strictement dactylographiés : 1. Toute propriété et méthode retournant les valeurs du FormControl / FormGroup est maintenant strictement typée. Par exemple, `value`, `getRawValue()`, `valueChanges`. 2. Toute méthode permettant de modifier la valeur d'un FormControl est maintenant sécurisée par le type : `setValue()`, `patchValue()`, `updateValue()`. 3. Les FormControls sont maintenant strictement typés. Cela s'applique également à la méthode `.get()` de FormGroup. Cela vous empêchera également d'accéder aux FormControls qui n'existent pas au moment de la compilation. ### Nouvelle classe FormRecord L'inconvénient de la nouvelle classe `FormGroup` est qu'elle a perdu sa nature dynamique. Une fois définie, vous ne serez pas en mesure d'ajouter ou de supprimer des FormControls à la volée. Pour résoudre ce problème, Angular présente une nouvelle classe - `FormRecord`. `FormRecord` est pratiquement le même que `FormGroup`, mais il est dynamique et tous ses FormControls doivent avoir le même type. ``` folders: new FormRecord({ home: new FormControl(true, { nonNullable: true }), music: new FormControl(false, { nonNullable: true }) }); // Ajouter un nouveau FormContol au groupe this.foldersForm.get('folders').addControl('videos', new FormControl(false, { nonNullable: true })); // Cela entraînera une erreur de compilation car le contrôle est de type différent. this.foldersForm.get('folders').addControl('books', new FormControl('Some string', { nonNullable: true })); ``` Et comme vous le voyez, ceci a une autre limitation - tous les FormControls doivent être du même type. Si vous avez vraiment besoin d'un FormGroup à la fois dynamique et hétérogène, vous devriez utiliser la classe `UntypedFormGroup` pour définir votre formulaire. ## Composants sans module (autonomes) Cette fonctionnalité est toujours considérée comme expérimentale, mais elle est intéressante. Elle vous permet de définir des composants, des directives et des tuyaux sans les inclure dans un module. Le concept n'est pas encore totalement au point, mais nous sommes déjà capables de construire une application sans ngModules. Pour définir un composant autonome, vous devez utiliser la nouvelle propriété `standalone` dans le décorateur Composant/Pipe/Directive : ``` @Component({ selector: 'app-table', standalone: true, templateUrl: './table.component.html' }) export class TableComponent { } ``` Dans ce cas, ce composant ne peut être déclaré dans aucun NgModule. Mais il peut être importé dans les NgModules et dans d'autres composants autonomes. Chaque autonome composant/pipe/directive a maintenant un mécanisme pour importer ses dépendances directement dans le décorateur : ``` @Component({ standalone: true, selector: 'photo-gallery', // un module existant est importé directement dans un composant autonome // CommonModule importé directement pour utiliser les directives Angular standard comme *ngIf // le composant autonome déclaré ci-dessus est également importé directement imports: [CommonModule, MatButtonModule, TableComponent], template: ` ... Next Page `, }) export class PhotoGalleryComponent { } ``` Comme je l'ai mentionné ci-dessus, vous pouvez importer des composants autonomes dans n'importe quel ngModule existant. Il n'est plus nécessaire d'importer l'intégralité d'un module partagé, nous ne pouvons importer que les éléments dont nous avons réellement besoin. C'est également une bonne stratégie pour commencer à utiliser de nouveaux composants autonomes: ``` @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule, TableComponent], // import our standalone TableComponent bootstrap: [AppComponent] }) export class AppModule {} ``` Vous pouvez créer un composant autonome avec Angular CLI en tapant: ``` ng g component --standalone user ``` ### Application Bootstrap sans module Si vous voulez vous débarrasser de tous les ngModules dans votre application, vous devrez amorcer votre application différemment. Angular a une nouvelle fonction pour cela que vous devez appeler dans le fichier main.ts: ``` bootstrapApplication(AppComponent); ``` Le second paramètre de cette fonction vous permettra de définir les providers dont vous avez besoin dans votre application. Comme la plupart des fournisseurs existent généralement dans les modules, Angular (pour l'instant) exige d'utiliser une nouvelle fonction d'extraction `importProvidersFrom` pour eux : ``` bootstrapApplication(AppComponent, { providers: [importProvidersFrom(HttpClientModule)] }); ``` ### Chargement paresseux de la route des composants autonomes: Angular dispose d'une nouvelle fonction de route de chargement paresseux, `loadComponent`, qui existe exactement pour charger des composants autonomes: ``` { path: 'home', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) } ``` `loadChildren` ne vous permet plus seulement de charger paresseusement un ngModule, mais aussi de charger les routes des enfants directement à partir du fichier des routes : ``` { path: 'home', loadChildren: () => import('./home/home.routes').then(c => c.HomeRoutes) } ``` ### Quelques notes au moment de la rédaction de l'article - La fonctionnalité des composants autonomes est encore au stade expérimental. Elle sera bien meilleure à l'avenir avec le passage à Vite builder au lieu de Webpack, un meilleur outillage, des temps de construction plus rapides, une architecture d'application plus robuste, des tests plus faciles et plus encore. Mais pour l'instant, beaucoup de ces éléments sont manquants, nous n'avons donc pas reçu le paquet complet, mais au moins nous pouvons commencer à développer nos applications avec le nouveau paradigme Angular en tête. - Les IDE et les outils Angular ne sont pas encore tout à fait prêts à analyser statiquement les nouvelles entités autonomes. Comme vous devez importer toutes les dépendances dans chaque entité autonome, si vous manquez quelque chose, le compilateur peut aussi le manquer et vous faire échouer au moment de l'exécution. Cela s'améliorera avec le temps, mais pour l'instant, les développeurs doivent accorder plus d'attention aux importations. - Il n'y a pas d'importations globales présentées dans Angular pour le moment (comme cela se fait dans Vue, par exemple), donc vous devez importer absolument chaque dépendance dans chaque entité autonome. J'espère que ce problème sera résolu dans une prochaine version, car l'objectif principal de cette fonctionnalité est de réduire le boilerplate et de rendre les choses plus faciles. # C'est tout pour aujourd'hui. A bientôt !
Article
Lorenzo Scalese · Juil 13, 2022

La WebGateway Apache avec Docker

Salut, communauté. Dans cet article, nous allons configurer de manière programmatique la WebGateway Apache avec Docker en incluant : * Protocole HTTPS. * TLS\SSL pour sécuriser la communication entre la WebGateway et l'instance IRIS. ![image](/sites/default/files/inline/images/net-schema-01.png) Nous utiliserons deux images : une pour la WebGateway et la seconde pour l'instance IRIS. Tous les fichiers nécessaires sont disponibles dans ce [repository GitHub](https://github.com/lscalese/docker-webgateway-sample). Commençons par un clone git : ```bash clone git https://github.com/lscalese/docker-webgateway-sample.git cd docker-webgateway-sample ``` ## Préparation de votre système Pour éviter les problèmes de permissions, votre système a besoin d'un utilisateur et d'un groupe : * www-data * irisowner Il est nécessaire de partager les fichiers de certificats avec les conteneurs. S'ils n'existent pas sur votre système, exécutez simplement : ```bash sudo useradd --uid 51773 --user-group irisowner sudo groupmod --gid 51773 irisowner sudo useradd –user-group www-data ``` ## Génération des certificats Dans cet exemple, nous allons utiliser trois certificats : 1. Utilisation du serveur web HTTPS. 2. Cryptage TLS\SSL sur le client de la WebGateway. 3. Cryptage TLS\SSL sur l'instance IRIS. Un script prêt à l'emploi est disponible pour les générer. Cependant, vous devez personnaliser l'objet du certificat ; il suffit pour cela de modifier le fichier [gen-certificates.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/gen-certificates.sh). Voici la structure de l'argument `subj` d'OpenSSL : 1. **C** : Code pays 2. **ST** : État 3. **L** : Localisation 4. **O** : Organisation 5. **OU** : Unité d'organisation 6. **CN** : Nom commun (essentiellement le nom de domaine ou le nom d'hôte) N'hésitez pas à modifier ces valeurs afin que ce soit cohérent avec votre localisation, organisation, etc... ```bash # sudo est nécessaire pour chown, chgrp, chmod ... sudo ./gen-certificates.sh ``` Si tout est ok, vous devriez voir deux nouveaux répertoires `./certificates/` et `~/webgateway-apache-certificates/` avec des certificats : | Fichier | Conteneur | Description | |--- |--- |--- | | ./certificates/CA_Server.cer | webgateway,iris | Certificat du serveur d'autorité| | ./certificates/iris_server.cer | iris | Certificat pour l'instance IRIS (utilisé pour le cryptage de la communication entre le miroir et la WebGateway) | | ./certificates/iris_server.key | iris | Clé privée associée | | ~/webgateway-apache-certificates/apache_webgateway.cer | webgateway | Certificat pour le serveur web apache | | ~/webgateway-apache-certificates/apache_webgateway.key | webgateway | Clé privée associée | | ./certificates/webgateway_client.cer | webgateway | Certificat pour crypter la communication entre webgateway et IRIS | | ./certificates/webgateway_client.key | webgateway | Clé privée associée | Gardez à l'esprit que ce sont des certificats auto-signés, les navigateurs web afficheront des alertes de sécurité. Évidemment, si vous avez un certificat délivré par une autorité certifiée, vous pouvez l'utiliser à la place d'un certificat auto-signé (en particulier pour le certificat du serveur Apache). ## Fichiers de configuration de la WebGateway Jetons un coup d'œil aux fichiers de configuration. ### CSP.INI Vous pouvez voir un fichier CSP.INI dans le répertoire `webgateway-config-files`. Il sera inséré dans l'image, mais son contenu peut être modifié lors de l'exécution. Considérez ce fichier comme un modèle. Dans cet exemple, les paramètres suivants seront remplacés au démarrage du conteneur : * Ip_Address * TCP_Port * System_Manager Référez-vous à [startUpScript.sh](https://github.com/lscalese/docker-webgateway-sample/blob/master/startUpScript.sh) pour plus de détails. De façon générale, le remplacement est effectué avec la ligne d'instruction `sed`. Ce fichier contient également la configuration SSL\TLS pour sécuriser la communication avec l'instance IRIS : ``` SSLCC_Certificate_File=/opt/webgateway/bin/webgateway_client.cer SSLCC_Certificate_Key_File=/opt/webgateway/bin/webgateway_client.key SSLCC_CA_Certificate_File=/opt/webgateway/bin/CA_Server.cer ``` Ces lignes sont importantes. Nous devons nous assurer que les fichiers de certificats seront disponibles pour le conteneur. Nous ferons cela plus tard dans le fichier `docker-compose` avec un volume. ### 000-default.conf Il s'agit d'un fichier de configuration d'Apache. Il permet l'utilisation du protocole HTTPS et redirige les appels HTTP vers HTTPS. Les fichiers de certificat et de clé privée sont configurés dans ce fichier : ``` SSLCertificateFile /etc/apache2/certificate/apache_webgateway.cer SSLCertificateKeyFile /etc/apache2/certificate/apache_webgateway.key ``` ## L'instance IRIS Pour notre instance IRIS, nous ne configurons que le minimum requis pour permettre la communication SSL\TLS avec la WebGateway ; cela implique les éléments suivants : 1. Configuration SSL de `%SuperServer`. 2. Activation du paramètre de sécurité SSLSuperServer. 3. Restriction de la liste des IPs qui peuvent utiliser le service Web Gateway. Pour faciliter la configuration, on utilise config-api avec un simple fichier de configuration JSON. ```json { "Security.SSLConfigs": { "%SuperServer": { "CAFile": "/usr/irissys/mgr/CA_Server.cer", "CertificateFile": "/usr/irissys/mgr/iris_server.cer", "Name": "%SuperServer", "PrivateKeyFile": "/usr/irissys/mgr/iris_server.key", "Type": "1", "VerifyPeer": 3 } }, "Security.System": { "SSLSuperServer":1 }, "Security.Services": { "%Service_WebGateway": { "ClientSystems": "172.16.238.50;127.0.0.1;172.16.238.20" } } } ``` Aucune action n'est nécessaire. La configuration sera automatiquement chargée au démarrage du conteneur. ## Image tls-ssl-webgateway ### dockerfile ``` ARG IMAGEWEBGTW=containers.intersystems.com/intersystems/webgateway:2021.1.0.215.0 FROM ${IMAGEWEBGTW} ADD webgateway-config-files /webgateway-config-files ADD buildWebGateway.sh / ADD startUpScript.sh / RUN chmod +x buildWebGateway.sh startUpScript.sh && /buildWebGateway.sh ENTRYPOINT ["/startUpScript.sh"] ``` Par défaut, le point d'entrée est `/startWebGateway`, mais il faut effectuer quelques opérations avant de démarrer le serveur web. Rappelez-vous que notre fichier CSP.ini est un `template`, et que nous devons changer certains paramètres (IP, port, gestionnaire de système) au démarrage. `startUpScript.sh` va effectuer ces changements et ensuite exécuter le script de point d'entrée initial `/startWebGateway`. ## Conteneurs de départ ### docker-compose file Avant de démarrer les conteneurs, le fichier `docker-compose.yml` doit être modifié :: * `**SYSTEM_MANAGER**` doit être défini avec l'IP autorisée à avoir un accès à **Web Gateway Management** https://localhost/csp/bin/Systems/Module.cxw. En gros, c'est votre adresse IP (Cela peut être une liste séparée par des virgules). * `**IRIS_WEBAPPS**` doit être défini avec la liste de vos applications CSP. La liste est séparée par des espaces, par exemple : `IRIS_WEBAPPS=/csp/sys /swagger-ui`. Par défaut, seul `/csp/sys` est exposé. * Les ports 80 et 443 sont mappés. Adaptez-les à d'autres ports s'ils sont déjà utilisés sur votre système. ``` version: '3.6' services: webgateway: image: tls-ssl-webgateway container_name: tls-ssl-webgateway networks: app_net: ipv4_address: 172.16.238.50 ports: # modification du port local déjà utilisé sur votre système. - "80:80" - "443:443" environnement: - IRIS_HOST=172.16.238.20 - IRIS_PORT=1972 # Remplacement par la liste des adresses IP autorisées à ouvrir le gestionnaire du système CSP # https://localhost/csp/bin/Systems/Module.cxw # consultez le fichier .env pour définir les variables d'environnement. - "SYSTEM_MANAGER=${LOCAL_IP}" # la liste des applications web # /csp permet à la webgateway de rediriger toutes les requêtes commençant par /csp vers l'instance iris # Vous pouvez spécifier une liste séparée par un espace : "IRIS_WEBAPPS=/csp /api /isc /swagger-ui" - "IRIS_WEBAPPS=/csp/sys" volumes: # Montage des fichiers de certificats. - ./volume-apache/webgateway_client.cer:/opt/webgateway/bin/webgateway_client.cer - ./volume-apache/webgateway_client.key:/opt/webgateway/bin/webgateway_client.key - ./volume-apache/CA_Server.cer:/opt/webgateway/bin/CA_Server.cer - ./volume-apache/apache_webgateway.cer:/etc/apache2/certificate/apache_webgateway.cer - ./volume-apache/apache_webgateway.key:/etc/apache2/certificate/apache_webgateway.key hostname: webgateway command: ["--ssl"] iris: image: intersystemsdc/iris-community:latest container_name: tls-ssl-iris networks: app_net: ipv4_address: 172.16.238.20 volumes: - ./iris-config-files:/opt/config-files # Mount certificates files. - ./volume-iris/CA_Server.cer:/usr/irissys/mgr/CA_Server.cer - ./volume-iris/iris_server.cer:/usr/irissys/mgr/iris_server.cer - ./volume-iris/iris_server.key:/usr/irissys/mgr/iris_server.key hostname: iris # Chargement du fichier de configuration IRIS ./iris-config-files/iris-config.json commande: ["-a","sh /opt/config-files/configureIris.sh"] networks: app_net: ipam: driver: default config: - subnet: "172.16.238.0/24" ``` Construction et démarrage : ```bash docker-compose up -d --build ``` Les conteneurs `tls-ssl-iris et tls-ssl-webgateway doivent être démarrés.` ## Test de l'accès au Web ### Page par défaut d'Apache Ouvrez la page [http://localhost](http://localhost). Vous serez automatiquement redirigé vers [https://localhost](https://localhost). Les navigateurs affichent des alertes de sécurité. C'est le comportement standard avec un certificat auto-signé, dans notre cas vous pouvez accepter le risque et continuer. ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-02.png) ### Page de gestion de la WebGateway Ouvrez [https://localhost/csp/bin/Systems/Module.cxw](https://localhost/csp/bin/Systems/Module.cxw) et testez la connexion du serveur. ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-03.png) ### Portail de gestion Ouvrez [https://localhost/csp/sys/utilhome.csp](https://localhost/csp/sys/utilhome.csp) ![image](/sites/default/files/inline/images/apache-web-gateway-with-docker-04.png) Parfait! Notre sample WebGateway fonctionne! ## Miroir IRIS avec la WebGateway Dans l'article précédent, nous avons construit un environnement miroir, mais la WebGateway était une pièce manquante. Maintenant, nous pouvons y remédier. Un nouveau dépôt [iris-miroring-with-webgateway](https://github.com/lscalese/iris-mirroring-with-webgateway) est disponible incluant la WebGateway et quelques autres améliorations : 1. Les certificats ne sont plus générés à la volée mais dans un processus séparé. 2. Les adresses IP sont remplacées par des variables d'environnement dans les fichiers de configuration docker-compose et JSON. Les variables sont définies dans le fichier '.env'. 3. Le repository peut être utilisé comme modèle. Consultez le fichier du dépôt [README.md](https://github.com/lscalese/iris-mirroring-with-webgateway) pour exécuter un environnement comme celui-ci : ![image](https://github.com/lscalese/iris-mirroring-with-webgateway/blob/master/img/network-schema-01.png?raw=true)
Article
Lucas Enard · Sept 30, 2022

Réalisation complète d'IRIS en Python : DataTransformation d'un CSV vers un serveur FHIR

[Dans ce GitHub](https://github.com/LucasEnard/fhir-orga-dt) nous recueillons des informations à partir d'un csv, nous utilisons une DataTransformation pour les transformer en un objet FHIR, puis nous sauvegardons ces informations sur un serveur FHIR, et tout cela en utilisant uniquement Python. The objective is to show how easy it is to manipulate data into the output we want, here a FHIR Bundle, in the IRIS full Python framework. # 1. Fhir-orga-dt - [1. Fhir-orga-dt](#1-fhir-orga-dt) - [2. Préalables](#2-prerequisites) - [3. Installation](#3-installation) - [3.1. Installation pour le dévelopment](#31-installation-for-development) - [3.2. Portail de gestion et VSCode](#32-management-portal-and-vscode) - [3.3. Avoir le dossier ouvert à l'intérieur du conteneur](#33-having-the-folder-open-inside-the-container) - [4. Serveur FHIR](#4-fhir-server) - [5. Présentation pas à pas](#5-walkthrough) - [5.1. Messages et objets](#51-messages-and-objects) - [5.2. Service aux entreprises](#52-business-service) - [5.3. Processus d'entreprise](#53-business-process) - [5.4. Opération d'un entreprise](#54-business-operation) - [5.5. Conclusion de la présentation](#55-conclusion-of-the-walkthrough) - [6. Création d'une nouvelle DataTransformation](#6-creation-of-a-new-datatransformation) - [7. Ce qu'il y a dans le référentiel](#7-whats-inside-the-repo) - [7.1. Dockerfile](#71-dockerfile) - [7.2. .vscode/settings.json](#72-vscodesettingsjson) - [7.3. .vscode/launch.json](#73-vscodelaunchjson) # 2. Préalables Assurez-vous que [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) et [Docker desktop](https://www.docker.com/products/docker-desktop) sont installé. Si vous travaillez à l'intérieur du conteneur, comme il est montré dans [3.3.](#33-having-the-folder-open-inside-the-container), vous n'avez pas besoin d'installer fhirpy et fhir.resources. Si vous n'êtes pas dans le conteneur, vous pouvez utiliser `pip` pour installer `fhirpy` et `fhir.resources`. Vérifiez [fhirpy](https://pypi.org/project/fhirpy/#resource-and-helper-methods) et [fhir.resources](https://pypi.org/project/fhir.resources/) pour plus d'information. # 3. Installation ## 3.1. Installation pour le développement Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous : ``` git clone https://github.com/LucasEnard/fhir-client-python.git ``` Ouvrez le terminal dans ce répertoire et lancez : ``` docker build . ``` ## 3.2. Portail de gestion et VSCode Ce référentiel est prêt pour [VS Code](https://code.visualstudio.com/). Ouvrez le dossier `fhir-client-python` cloné localement dans VS Code. Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées. ## 3.3. Avoir le dossier ouvert à l'intérieur du conteneur Vous pouvez être *à l'intérieur* du conteneur avant de coder si vous le souhaitez. Pour cela, il faut que docker soit activé avant d'ouvrir VSCode. Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient. La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé. Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur `Ouvrir à nouveau dans le conteneur` puis sélectionner `De Dockerfile` [Plus d'informations ici](https://code.visualstudio.com/docs/remote/containers) ![Architecture](https://code.visualstudio.com/assets/docs/remote/containers/architecture-containers.png) En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants python dans le conteneur. # 4. Serveur FHIR Pour compléter cette présentation, nous allons utiliser un serveur fhir. Ce serveur fhir était déjà intégré lorsque vous avez cloné et construit le conteneur. L'url est `localhost:52773/fhir/r4` # 5. Présentation pas à pas Présentation complète de la réalisation d'IRIS en Python. ## 5.1. Messages and objects Les objets et les messages contiennent les informations entre nos services, nos processus et nos opérations. Dans le fichier `obj.py` nous devons créer une classe de données qui correspond au csv, ceci sera utilisé pour garder l'information avant de faire la DataTransformation. Dans notre exemple, le csv organisation.csv ressemble à ceci, ``` active;name;city;country;system;value true;Name1;city1;country1;phone;050678504 false;Name2;city2;country2;phone;123456789 ``` Par conséquent, l'objet ressemblera à ceci, ```python @dataclass # > Cette classe représente une organisation simple class BaseOrganization: active:bool = None name:str = None city:str = None country:str = None system:str = None value:str = None ``` Dans le fichier `msg.py`, nous aurons deux types de requêtes, la première contenant les informations d'une organisation avant la DataTransformation et la seconde contenant les informations de l'organisation après la DataTransformation. ## 5.2. Service aux entreprises Dans le fichier `bs.py`, nous avons le code qui nous permet de lire le csv et pour chaque ligne du csv (donc pour chaque organisation), le mapper dans un objet que nous avons créé plus tôt. Ensuite, pour chacune de ces lignes (organisation), nous créons une requête et l'envoyons à notre processus pour effectuer la DataTransformation. ```python # Nous ouvrons le fichier with open(self.path + self.filename,encoding="utf-8") as csv: # Nous le lisons et le mappons en utilisant l'objet BaseOrganization précédemment utilisé reader = DataclassReader(csv, self.fhir_type ,delimiter=";") # Pour chacune de ces organisations, nous pouvons créer une requête et l'envoyer au processus for row in reader: msg = OrgaRequest() msg.organization = row self.send_request_sync('Python.ProcessCSV',msg) ``` ## 5.3. Service aux entreprises Dans le fichier `bp.py`, nous avons la DataTransformation, qui convertit un simple objet python contenant peu d'informations en un objet FHIR R4 Voici les étapes pour effectuer une DataTransformation en utilisant du python embarqué sur notre organisation simple, ```python # Création de l'objet Organisation organisation = Organisation() # Mappage des informations de la demande à l'objet Organisation organization.name = base_orga.name organization.active = base_orga.active ## Création de l'objet Adresse et mappage des informations ## de la demande à l'objet Addresse adresse = Addresse() adress.country = base_orga.country adress.city = base_orga.city ### Configuration de l'adresse de notre organisation à celle que nous avons créée organization.address = [adress] ## Création de l'objet ContactPoint et mise en correspondance ## des informations de la demande avec l'objet ContactPoint telecom = ContactPoint() telecom.value = base_orga.value telecom.system = base_orga.system ### Configuration de la télécommunication de notre organisation à celle que nous avons créée organization.telecom = [telecom] # Maintenant, notre DT est achevé, nous avons une organisation d'objets qui est # un objet FHIR R4 et qui contient toutes nos informations csv. ``` Après cela, notre mappage est terminé et notre DT fonctionne. Maintenant, nous pouvons envoyer cette ressource FHIR R4 nouvellement créée à notre FhirClient qui est notre opération. ## 5.4. Opération d'un entreprise Dans le fichier `bo.py` nous avons le FhirClient, ce client crée une connexion à un serveur fhir qui contiendra les informations recueillies par le csv. Dans cet exemple, nous utilisons un serveur fhir local qui n'a pas besoin d'une clé api pour se connecter. Pour s'y connecter, nous devons utiliser la fonction on_init, ```python if not hasattr(self,'url'): self.url = 'localhost:52773/fhir/r4' self.client = SyncFHIRClient(url=self.url) ``` Maintenant, lorsque nous recevons un message/demande, nous pouvons, en trouvant le type de ressource de la ressource que nous envoyons avec notre demande au client, créer un objet lisible par le client, puis le sauvegarder sur le serveur fhir. ```python # Obtenez le type de ressource de la demande ( ici "Organisation" ) resource_type = request.resource["resource_type"] # Créer une ressource de ce type en utilisant les données de la demande resource = construct_fhir_element(resource_type, request.resource) # Sauvegarder la ressource sur le serveur FHIR en utilisant le client self.client.resource(resource_type,**json.loads(resource.json())).save() ``` Il est à noter que le client fhir fonctionne avec n'importe quelle ressource de FHIR R4 et pour utiliser et modifier notre exemple, nous devons seulement changer la DataTransformation et l'objet qui contient les informations csv. ## 5.5. Conclusion de la présentation Si vous avez suivi ce parcours, vous savez maintenant exactement comment lire un csv d'une représentation d'une ressource FHIR R4, utiliser une DataTransformation pour le transformer en un véritable objet FHIR R4 et le sauvegarder sur un serveur. # 6. Création d'une nouvelle DataTransformation Ce référentiel est prêt à être codé en VSCode avec les plugins InterSystems. Ouvrez `/src/python` pour commencer à coder ou utiliser l'autocomplétion. **Étapes pour créer une nouvelle transformation** Pour ajouter une nouvelle transformation et l'utiliser, la seule chose que vous devez faire est d'ajouter votre csv nommé `Patient.csv` (par exemple) dans le dossier `src/python/csv`. Ensuite, créez un objet dans `src/python/obj.py` appelé `BasePatient` qui met en correspondance votre csv. Maintenant, créez une requête dans `src/python/msg.py` appelée `PatientRequest` qui a une variable `resource` de type BasePatient. L'étape finale est la DataTransformation, pour cela, allez dans `src/python/bp.py` et ajoutez votre DT. Ajoutez d'abord `if isinstance(request, PatientRequest):` puis mappez votre ressource de requête à une fhir.resource Patient. Maintenant si vous allez dans le portail de gestion et changez le paramètre du `ServiceCSV` pour ajouter `filename=Patient.csv` vous pouvez juste démarrer la réalisation et voir votre transformation se dérouler et votre client envoyer les informations au serveur. **Étapes détaillées pour créer une nouvelle transformation** Si vous n'êtes pas sûr de ce qu'il faut faire ou de la manière de le faire, voici une création étape par étape d'une nouvelle transformation : Créez le fichier `Patient.csv` and le dossier `src/python/csv` pour les remplir avec le suivant: ``` family;given;system;value FamilyName1;GivenName1;phone;555789675 FamilyName2;GivenName2;phone;023020202 ``` Notre CSV contient un nom de famille, un prénom et un numéro de téléphone pour deux patients. Dans `src/python/obj.py` écrivez : ```python @dataclass class BasePatient: family:str = None given:str = None system:str = None value:str = None ``` Dans `src/python/msg.py` écrivez : ```python from obj import BasePatient @dataclass class PatientRequest(Message): resource:BasePatient = None ``` Dans `src/python/bp.py` écrivez : ```python from msg import PatientRequest from fhir.resources.patient import Patient from fhir.resources.humanname import HumanName ``` Dans `src/python/bp.py` dans la fonction `on_request` écrivez : ```python if isinstance(request,PatientRequest): base_patient = request.resource patient = Patient() name = HumanName() name.family = base_patient.family name.given = [base_patient.given] patient.name = [name] telecom = ContactPoint() telecom.value = base_patient.value telecom.system = base_patient.system patient.telecom = [telecom] msg = FhirRequest() msg.resource = patient self.send_request_sync("Python.FhirClient", msg) ``` Maintenant si vous allez dans le portail de gestion et changez le paramètre du `ServiceCSV` pour ajouter `filename=Patient.csv` vous pouvez juste arrêter et redémarrer la production et voir votre transformation se dérouler et votre client envoyer les informations au serveur. ![Settings](https://user-images.githubusercontent.com/77791586/170278879-02eb4303-51af-45ba-93bf-393e9ff5ed94.png) # 7. Ce qu'il y a dans le référentiel ## 7.1. Dockerfile Le dockerfile le plus simple pour démarrer un conteneur Python. Utilisez `docker build .` pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci. ## 7.2. .vscode/settings.json Fichier de paramètres. ## 7.3. .vscode/launch.json Fichier de configuration si vous voulez déboguer.
Article
Lucas Enard · Avr 3, 2023

Apprentissage automatique dans IRIS en utilisant l'API HuggingFace et/ou des modèles ML en local ( en utilisant Python )

Sur [GitHub](https://github.com/LucasEnard/iris-local-ml), vous trouverez toutes les informations sur l'utilisation d'un modèle d'apprentissage automatique "HuggingFace" / modèle d'IA sur le cadre IRIS à l'aide de Python. # 1. iris-huggingface Utilisation de modèles d'apprentissage automatique dans IRIS à l'aide de Python ; pour les modèles texte-texte, texte-image ou image-image. Les modèles suivants servent d'exemple : - https://huggingface.co/gpt2 - https://huggingface.co/Jean-Baptiste/camembert-ner - https://huggingface.co/bert-base-uncased - https://huggingface.co/facebook/detr-resnet-50 - https://huggingface.co/facebook/detr-resnet-50-panoptic - [1. iris-huggingface](#1-iris-huggingface) - [2. Installation](#2-installation) - [2.1. Lancement de la production](#21-starting-the-production) - [2.2. Accès à la production](#22-access-the-production) - [2.3. Clôture de la production](#23-closing-the-production) - [Comment ça marche](#how-it-works) - [3. API HuggingFace](#3-huggingface-api) - [4. Utilisation de n'importe quel modèle sur le web](#4-use-any-model-from-the-web) - [4.1. PREMIER CAS : VOUS AVEZ VOTRE PROPRE MODÈLE](#41-first-case--you-have-your-own-model) - [4.2. DEUXIÈME CAS : VOUS VOULEZ TÉLÉCHARGER UN MODÈLE À PARTIR DE HUGGINGFACE](#42-second-case--you-want-to-download-a-model-from-huggingface) - [4.2.1. Paramètres](#421-settings) - [4.2.2. Tests](#422-testing) - [5. Dépannage](#5-troubleshooting) - [6. Conclusion](#6-conclusion) # 2. Installation ## 2.1. Lancement de la production Dans le dossier iris-local-ml, ouvrez un terminal et saisisse : ``` docker-compose up ``` La première fois, cela peut prendre quelques minutes pour construire l'image de manière correcte et installer tous les modules nécessaires à Python. ## 2.2. Accès à la production En suivant ce lien, vous accédez à la production : [Access the Production](http://localhost:52795/csp/irisapp/EnsPortal.ProductionConfig.zen?RODUCTION=INFORMATION.QuickFixProduction) ## 2.3. Clôture de la production ``` docker-compose down ``` # Comment ça marche Pour l'instant, certains modèles peuvent ne pas fonctionner avec cette implémentation car tout est fait automatiquement, ce qui veut dire que, quel que soit le modèle que vous saisissez, nous essaierons de le faire fonctionner grâce à la bibliothèque `transformers` `pipeline`. Pipeline est un outil puissant de l'équipe HuggingFace qui va scanner le dossier dans lequel nous avons téléchargé le modèle, puis il va déterminer quelle bibliothèque il doit utiliser entre PyTorch, Keras, Tensorflow ou JAX pour ensuite charger ce modèle en utilisant `AutoModel`. À partir de ce point, en saisissant la tâche, le pipeline sait ce qu'il doit faire avec le modèle, l'analyseur lexical ou même l'extracteur de caractéristiques dans ce dossier, et gère automatiquement votre entrée, l'analyse et la traite, la transmet au modèle, puis restitue la sortie sous une forme décodée directement utilisable par nous. # 3. API HuggingFace Vous devez d'abord démarrer la démo, en utilisant le bouton vert `Start` ou `Stop` et `Start` à nouveau pour appliquer vos changements de configuration. Ensuite, en cliquant sur l'opération `Python.HFOperation` de votre choix, et en sélectionnant dans l'onglet de droite `action`, vous pouvez `tester` la démo. Dans cette fenêtre `test`, sélectionnez : Type de demande : `Grongier.PEX.Message` Pour le `classname` vous devez saisir : ``` msg.HFRequest ``` Et pour le `json`, voici un exemple d'appel à GPT2 : ``` { "api_url":"https://api-inference.huggingface.co/models/gpt2", "payload":"Veuillez nous donner plus de détails sur votre ", "api_key":"----------------------" } ``` Vous pouvez maintenant cliquer sur le bouton `Visual Trace` (Trace visuelle) pour voir en détail ce qui s'est passé et consulter les journaux. **REMARQUE** : vous devez avoir une clé API de HuggingFace avant d'utiliser cette opération (les clés API sont gratuites, il suffit de s'enregistrer auprès de HF). **REMARQUE** : vous pouvez changer l'url pour essayer n'importe quel autre modèle de HuggingFace, mais vous devrez peut-être changer le payload. Par exemple : ![sending hf req](https://user-images.githubusercontent.com/77791586/182403526-0f6e97a0-2019-4d86-b1ae-38c56dfc8746.png) ![hf req](https://user-images.githubusercontent.com/77791586/182404662-b37b9489-c12c-47f8-98bd-18008c9a615e.jpg) ![hf resp](https://user-images.githubusercontent.com/77791586/182403515-7c6c2075-bdb6-46cd-9258-ac251844d591.png) # 4. Utilisation de n'importe quel modèle sur le web Dans cette section, nous vous apprendrons à utiliser presque tous les modèles disponibles sur l'internet, qu'il s'agisse de HuggingFace ou non. ## 4.1. PREMIER CAS : VOUS AVEZ VOTRE PROPRE MODÈLE Dans ce cas, vous devez copier-coller votre modèle, avec la configuration, l'outil tokenizer.json etc. dans un dossier à l'intérieur du dossier model. Chemin d'accès : `src/model/yourmodelname/` A partir de là, vous devez aller dans les paramètres de `Python.MLOperation`. Cliquez sur `Python.MLOperation` puis rendez-vous dans `settings` dans l'onglet de droite, puis dans la partie `Python`, puis dans la partie `%settings`. Ici, vous pouvez saisir ou modifier n'importe quel paramètre (n'oubliez pas d'appuyer sur `apply` une fois que vous avez terminé). Voici la configuration par défaut pour ce cas : %settings ``` name=yourmodelname task=text-generation ``` **REMARQUE** : tous les paramètres qui ne sont pas `name` ou `model_url` sont placés dans les paramètres de PIPELINE. NMaintenant vous pouvez double-cliquer sur l'opération `Python.MLOperation` et la `démarrer`. Vous devez voir dans la partie `Log` le démarrage de votre modèle. A partir de là, nous créons une `PIPELINE` en utilisant des transformateurs qui utilisent votre fichier de configuration situé dans le dossier comme nous l'avons vu précédemment. Pour appeler ce pipeline, cliquez sur l'opération `Python.MLOperation` , et sélectionnez dans l'onglet de droite `action`, vous pouvez `tester` la démo. Dans cette fenêtre `test`, sélectionnez : Type de demande : `Grongier.PEX.Message` Pour le `classname` (nom de classe), vous devez saisir : ``` msg.MLRequest ``` Et pour le `json`, vous devez saisissez tous les arguments nécessaires à votre modèle. Voici un exemple d'appel à GPT2 : ``` { "text_inputs":"Malheureusement, le résultat", "max_length":100, "num_return_sequences":3 } ``` Cliquez sur `Invoke Testing Service` (Invoquer le service de test) et attendez que le modèle fonctionne. Voir par exemple : ![sending ml req](https://user-images.githubusercontent.com/77791586/182402707-13ca90d0-ad5a-4934-8923-a58fe821e00e.png) Vous pouvez maintenant cliquer sur `Visual Trace` (Trace visuelle) pour voir en détail ce qui s'est passé et consulter les journaux. Voir par exemple : ![ml req](https://user-images.githubusercontent.com/77791586/182402878-e34b64de-351c-49c3-affe-023cd885e04b.png) ![ml resp](https://user-images.githubusercontent.com/77791586/182402932-4afd14fe-5f57-4b03-b0a6-1c6b74474015.png) ## 4.2. DEUXIÈME CAS : VOUS VOULEZ TÉLÉCHARGER UN MODÈLE À PARTIR DE HUGGINGFACE Dans ce cas, vous devez trouver l'URL du modèle sur HuggingFace ; ### 4.2.1. Paramètres À partir de là, vous devez accéder aux paramètres de la `Python.MLOperation`. Cliquez sur `Python.MLOperation` puis allez dans des paramètres `settings` dans l'onglet de droite, puis dans la partie `Python`, puis dans la partie `%settings`. Ici, vous pouvez saisir ou modifier n'importe quel paramètre (n'oubliez pas d'appuyer sur `apply` (appliquer) une fois que vous avez terminé). Voici quelques exemples de configuration pour certains modèles que nous avons trouvés sur HuggingFace : %settings pour gpt2 ``` model_url=https://huggingface.co/gpt2 name=gpt2 task=text-generation ``` %settings pour camembert-ner ``` name=camembert-ner model_url=https://huggingface.co/Jean-Baptiste/camembert-ner task=ner aggregation_strategy=simple ``` %settings pour bert-base-uncased ``` name=bert-base-uncased model_url=https://huggingface.co/bert-base-uncased task=fill-mask ``` %settings pour detr-resnet-50 ``` name=detr-resnet-50 model_url=https://huggingface.co/facebook/detr-resnet-50 task=object-detection ``` %settings pour detr-resnet-50-protnic ``` name=detr-resnet-50-panoptic model_url=https://huggingface.co/facebook/detr-resnet-50-panoptic task=image-segmentation ``` **REMARQUE** : tous les paramètres qui ne sont pas `name` ou `model_url` iront dans les paramètres du PIPELINE, donc dans notre second exemple, le pipeline camembert-ner requiert une stratégie `aggregation_strategy` et une tâche `task` qui sont spécifiées ici alors que le pipeline gpt2 ne requiert qu'une tâche `task`. Voir par exemple : ![settings ml ope2](https://user-images.githubusercontent.com/77791586/182403258-c24efb77-2696-4462-ae71-9184667ac9e4.png) Vous pouvez maintenant double-cliquer sur l'opération `Python.MLOperation` et la `démarrer`. **Vous devez voir dans la partie `Log` le démarrage de votre modèle et le téléchargement.** **REMARQUE** : Vous pouvez actualiser ces journaux toutes les x secondes pour voir l'évolution des téléchargements. ![dl en temps réel](https://user-images.githubusercontent.com/77791586/182403064-856724b5-876e-460e-a2b4-34eb63f44673.png) A partir de là, nous créons une `PIPELINE` en utilisant des transformateurs qui utilisent votre fichier de configuration qui se trouve dans le dossier comme nous l'avons vu précédemment. ### 4.2.2. Tests Pour appeler ce pipeline, cliquez sur l'opération `Python.MLOperation` , et sélectionnez dans l'onglet de droite `action`, vous pouvez `tester` la démo. Dans cette fenêtre `test`, sélectionnez : Type de demande : `Grongier.PEX.Message` Pour le `classname` (nom de classe), vous devez saisir : ``` msg.MLRequest ``` Et pour le `json`, vous devez saisir tous les arguments nécessaires à votre modèle. Voici un exemple d'appel à GPT2 ( `Python.MLOperation` ) : ``` { "text_inputs":"George Washington a vécu", "max_length":30, "num_return_sequences":3 } ``` Voici un exemple d'appel à Camembert-ner ( `Python.MLOperation2` ) : ``` { "inputs":"George Washington a vécu à Washington" } ``` Voici un exemple d'appel à bert-base-uncased ( `Python.MLOperation3` ) : ``` { "inputs":"George Washington a vécu à [MASQUE]." } ``` Voici un exemple d'appel à detr-resnet-50 à l'aide d'une url en ligne ( `Python.MLOperationDETRRESNET` ) : ``` { "url":"http://images.cocodataset.org/val2017/000000039769.jpg" } ``` Voici un exemple d'appel à detr-resnet-50-panoptic utilisant l'url comme chemin d'accès( `Python.MLOperationDetrPanoptic` ) : ``` { "url":"/irisdev/app/misc/000000039769.jpg" } ``` Cliquez sur `Invoke Testing Service` et attendez que le modèle fonctionne. Vous pouvez maintenant cliquer sur `Visual Trace` pour voir en détail ce qui s'est passé et consulter les journaux. **REMARQUE** : Lorsque le modèle a été téléchargé pour la première fois, la production ne le téléchargera pas à nouveau mais récupérera les fichiers mis en cache dans `src/model/TheModelName/`. Si certains fichiers sont manquants, la production les téléchargera à nouveau. Voir par exemple : ![sending ml req](https://user-images.githubusercontent.com/77791586/182402707-13ca90d0-ad5a-4934-8923-a58fe821e00e.png) ![ml req](https://user-images.githubusercontent.com/77791586/182402878-e34b64de-351c-49c3-affe-023cd885e04b.png) ![ml resp](https://user-images.githubusercontent.com/77791586/182402932-4afd14fe-5f57-4b03-b0a6-1c6b74474015.png) Voir par exemple : ![sending ml req](https://user-images.githubusercontent.com/77791586/183036076-f0cb9512-573b-4723-aa70-64f575c8f563.png) ![ml resp](https://user-images.githubusercontent.com/77791586/183036060-2a2328f7-535e-4046-9d2c-02d6fa666362.png) # 5. Dépannage Si vous avez des problèmes, la lecture est le premier conseil que nous pouvons vous donner, la plupart des erreurs sont facilement compréhensibles simplement en lisant les journaux car presque toutes les erreurs seront capturées par un try / catch et enregistrées. Si vous avez besoin d'installer un nouveau module, ou une dépendance Python, ouvrez un terminal à l'intérieur du conteneur et saisissez par exemple : "pip install new-module" Il y a plusieurs façons d'ouvrir un terminal, - Si vous utilisez les plugins InterSystems, vous pouvez cliquer sur la barre ci-dessous dans VSCode, celle qui ressemble à `docker:iris:52795[IRISAPP]` et sélectionner `Open Shell in Docker` (Ouvrir l'enveloppe dans Docker). - Dans n'importe quel terminal local, saisissez : `docker-compose exec -it iris bash` - Depuis Docker-Desktop, trouvez le conteneur IRIS et cliquez sur `Open in terminal` (Ouvrir dans le terminal). Certains modèles peuvent nécessiter des modifications au niveau de leur pipeline ou de leurs paramètres, par exemple, et c'est à vous qu'il incombe d'ajouter les informations adéquates dans les paramètres et dans la demande. # 6. Conclusion À partir de là, vous devriez pouvoir utiliser n'importe quel modèle dont vous avez besoin ou que vous possédez sur IRIS. **REMARQUE** : vous pouvez créer une `Python.MLOperation` pour chacun de vos modèles et les activer en même temps.
Article
Sylvain Guilbaud · Juin 21, 2023

Utilisation d'une bande LVM pour augmenter le nombre d'IOPS et le débit d'AWS EBS

# Aperçu général Des performances prévisibles en matière d'E/S de stockage avec une faible latence sont essentielles pour assurer l'évolutivité et la fiabilité de vos applications. Cette série de benchmarks a pour but d'informer les utilisateurs d'IRIS qui envisagent de déployer des applications dans AWS sur les performances des volumes EBS gp3. ## Résumé - Une bande LVM peut augmenter le nombre d'IOPS et le débit au-delà des limites de performance d'un volume EBS unique. - Une bande LVM réduit la latence de lecture. > **À lire en premier ** Une architecture de référence, comprenant une explication plus détaillée de la configuration de LVM dans AWS, est disponible ici : https://community.intersystems.com/post/intersystems-iris-example-reference-architectures-amazon-web-services-aws # Besoin de savoir ## Résumé des performances du volume EBS type gp3. Les fournisseurs de services en nuage imposent des limites de performance de stockage, par exemple, IOPS ou débit, généralement en augmentant la latence, ce qui aura un impact sur la performance de l'application et l'expérience de l'utilisateur final. Avec les volumes gp3, les limites de performance de stockage de base peuvent être augmentées en payant des prix plus élevés pour augmenter les limites prédéfinies. >Notez que vous devez augmenter le nombre d'IOPS __et__ le débit pour obtenir les performances maximales d'un volume EBS. La latence est appliquée lorsque l'une ou l'autre des limites est atteinte. **Performance IOPS** - Les volumes gp3 offrent une performance IOPS de base constante de 3 000 IOPS, qui est incluse dans le prix du stockage. - Vous pouvez provisionner des IOPS supplémentaires jusqu'à un maximum de 16 000 IOPS par volume. - Le maximum d'IOPS peut être provisionné pour les volumes de 32 GiB ou plus. - Les volumes gp3 n'utilisent pas les performances en rafale. **Performances de débit** - Les volumes gp3 offrent un débit de base constant de 125 Mio/s, qui est inclus dans le prix du stockage. - Vous pouvez provisionner un débit supplémentaire (jusqu'à un maximum de 1.000 Mio/s) pour un coût additionnel à un ratio de 0,25 Mio/s par IOPS provisionné. - Le débit maximum peut être provisionné à 4.000 IOPS ou plus et 8 GiB ou plus (4.000 IOPS × 0,25 Mio/s par IOPS = 1.000 Mio/s). Pour en savoir plus, consultez [Volumes SSD à usage général - Amazon Elastic Compute Cloud](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/general-purpose.html). **Limites d'instance** - Les types d'instances EC2 ont des limites maximales d'IOP et de débit. La latence est appliquée lorsque les limites d'instance ou d'EBS sont atteintes. Pour en savoir plus, consultez [Instances Amazon EBS optimisées - Amazon Elastic Compute Cloud](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html) ## Gestionnaire de volume logique (Linux) Si votre application nécessite plus de 16 000 IOPS à partir d'un seul système de fichiers, vous pouvez configurer plusieurs volumes dans une bande LVM (gestionnaire de volumes logiques). Par exemple : si vous avez besoin de 80 000 IOPS, vous pouvez provisionner un type d'instance EC2 capable de prendre en charge 80 000 IOPS à l'aide de cinq volumes gp3 en bandes LVM.  >LVM a été utilisé pour tous les tests suivants avec un ou plusieurs volumes EBS. ## Autres types de stockage par blocs Bien qu'il existe d'autres types de volumes, tels que io2 block express, avec des IOPS et des débits plus élevés pour un seul volume, il peut être beaucoup moins cher d'utiliser des volumes EBS type gp3 dans une bande LVM pour la même quantité de stockage et des IOPS "suffisamment élevés". io2 block express n'a de valeur que pour certains types d'instances. # Exécution des tests Deux tests sont exécutés pour générer des simulations d'E/S de stockage de l'application IRIS. Il s'agit des tests RANREAD et RANWRITE. Les détails de ces tests et la manière de les exécuter se trouvent ici : https://community.intersystems.com/post/perftools-io-test-suite ## RANREAD Ce test génère des lectures aléatoires d'une seule base de données d'IRIS. - Dans IRIS, les lectures sont continues par les processus utilisateurs ; un processus utilisateur initie une entrée-sortie de disque pour lire les données. Les démons qui servent les pages web, les requêtes SQL ou les processus utilisateurs directs effectuent des lectures. - Les processus RANREAD lisent des blocs de données aléatoires dans la base de données de test. - La base de données de test est dimensionnée de manière à dépasser le cache de lecture attendu des systèmes de stockage sur site. On ne sait pas si AWS utilise la mise en cache de lecture (par exemple, Azure le fait par défaut). ## RANWRITE Génère des E/S d'écriture à l'aide du cycle du démon d'écriture de la base de données IRIS. Les trois principales activités d'écriture d'IRIS sont les suivantes : - **Journal d'écriture d'images (WIJ).** Le WIJ écrit une rafale environ toutes les 80 secondes ou lorsqu'un pourcentage du cache de la base de données est en attente de mises à jour par un seul démon d'écriture du maître de la base de données. Le WIJ protège l'intégrité des fichiers de la base de données physique contre les défaillances du système pendant un cycle d'écriture de la base de données. Les écritures sont d'environ 256 Ko chacune juste avant que les fichiers de la base de données ne soient mis à jour par des écritures aléatoires de la base de données. - **Écritures aléatoires dans des bases de données.** Les écritures de la base de données par le démon d'écriture se font en rafale environ toutes les 80 secondes ou en fonction du pourcentage de mises à jour en attente dans le cache de la base de données. Un ensemble de processus du système de base de données, connus sous le nom de démons d'écriture, effectue les écritures. Les processus utilisateur mettent à jour le cache en mémoire de la base de données et un déclencheur (basé sur un seuil de temps ou d'activité) envoie les mises à jour sur le disque à l'aide des démons d'écriture. En règle générale, quelques Mo à plusieurs Go sont écrits au cours du cycle d'écriture, en fonction des taux de transaction. - **Écritures de journal** Les écritures dans le journal sont quasi-continues, de moins de deux secondes, et sont déclenchées lorsque les tampons du journal sont pleins ou lors d'une demande de synchronisation des données (par exemple, à partir d'une transaction). Les écritures de journal sont séquentielles et leur taille varie de 4 Ko à 4 Mo. Le nombre d'écritures par seconde peut varier de quelques dizaines à plusieurs milliers pour les grands déploiements utilisant des serveurs d'application distribués. >Note : des bases de données séparées sont utilisées pour RANREAD et RANWRITE dans les tests ; cependant, le même volume EBS et le même système de fichiers (/data) sont utilisés. >La taille de la base de données RANREAD est de 700 Go. >IRIS sur les systèmes Linux utilise l'IO directe pour les opérations de base de données. Le cache de page du système de fichiers Linux n'est pas utilisé. # Résultats de benchmarks ## A. Absence de bande LVM Le benchmark a été exécuté en utilisant l'instance EBS optimisée suivante : ![image](/sites/default/files/inline/images/2022-11-11_10-52-09.png) Le benchmark IRIS est exécuté avec **trois volumes EBS ( Données, WIJ, et Journaux).** Les IOPS pour le volume /data ont été provisionnés au maximum pour un seul volume. Le débit a été laissé à la valeur par défaut de 125 Mo/s. ![image](/sites/default/files/inline/images/2022-11-11_10-52-26.png) ### A.002 - RANREAD de 30 minutes seulement avec une augmentation progressive de l'IOPS. Cinq puis dix processus RANREAD ont été utilisés pour générer l'IO. Approche du débit maximum (16K IOPS et 125 Mo/s de débit). >Note : IRIS utilise un bloc de base de données de 8 Ko (1 500 * 8 Ko = environ 123 Mio/s). ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_read_size_0001_to_2359_-_aws_ebs_gp3_friday_16_sep_2022_1.png) Dans l'image ci-dessous : La latence de base (en vert) est cohérente entre les exécutions de RANREAD, avec une moyenne de 0,7 à 1,5 ms, bien qu'il y ait des pics à plus de 5 ms. Pour une raison inconnue, les performances se dégradent au milieu du test à 10 processus. ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_reads_and_db_read_latency_0001_to_2359_-_aws_ebs_gp3_friday_16_sep_2022_1.png) ### A.006 - RANREAD et RANWRITE de 20 minutes. L'image ci-dessous montre comment le test a été rythmé pour atteindre des pics autour de 8 000 IOPS en écriture et 12 000 IOPS en lecture. L'écart en lecture est intentionnel. ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_writes_and_db_reads_0001_to_2359_-_aws_ebs_gp3_friday_16_sep_2022.png) La latence de lecture dans l'image ci-dessous est similaire au test A.002. Les bases de données en lecture et en écriture sont sur le même volume EBS. Il y a un pic dans la latence de lecture (vert) pendant le cycle du démon d'écriture des écritures aléatoires de la base de données (bleu). Le pic de latence de lecture est transitoire et n'affectera probablement pas l'expérience d'un utilisateur interactif. >Le WIJ se trouve sur un volume EBS séparé afin d'isoler les écritures séquentielles du WIJ des IOPS de lecture. Bien que dans le nuage tout le stockage EBS soit un stockage réseau, il n'y a aucune garantie que les volumes EBS individuels soient dans un stockage SSD physique séparé ou que les différents volumes attachés à une instance soient dans le même boîtier de stockage. ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_writes_and_db_read_latency_0001_to_2359_-_aws_ebs_gp3_friday_16_sep_2022_0.png) # B. Augmentation des IOPS et du débit à l'aide d'une bande LVM. La suite de tests a été exécutée en utilisant une instance EBS optimisée avec un maximum de 80 000 IOPS : ![image](/sites/default/files/inline/images/2022-11-11_10-52-40.png) Le benchmark IRIS fonctionne avec cinq volumes EBS. Remarque : le fichier /data comprend cinq volumes EBS dans une bande LVM. ![image](/sites/default/files/inline/images/2022-11-11_10-52-51.png) ### B.080 - Le RANREAD de 30 minutes n'augmente que progressivement le nombre d'IOPS. Le test a été effectué avec 10 à 50 processus RANREAD, en augmentant par paliers de 10 processus. Le test s'approche du maximum d'IOPS (80K). ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_read_size_0000_to_2359_-_aws_ebs_gp3_-_4m_lv_pe_tuesday_08_nov_2022.png) La latence de lecture de la base de données est inférieure (0,7 en moyenne) à celle du test sans bande. ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_reads_and_db_read_latency_0000_to_2359_-_aws_ebs_gp3_-_4m_lv_pe_tuesday_08_nov_2022_0.png) ### B.083 - RANREAD et RANWRITE de 30 minutes. Ce test a été cadencé à des pics d'environ 40 000 IOPS en écriture et 60 000 IOPS en lecture. >Notez que les IOPS de lecture et d'écriture combinées sont supérieures aux IOPS maximales de l'instance EC2 (80 000 IOPS). ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_writes_and_db_reads_0000_to_2359_-_aws_ebs_gp3_-_4m_lv_pe_tuesday_08_nov_2022_0.png) La latence de lecture est similaire à celle du test B.080. Les bases de données de lecture et d'écriture sont sur le même volume EBS. Il y a un pic dans la latence de lecture (vert) pendant le cycle du démon d'écriture des écritures aléatoires de la base de données (bleu). Le pic de latence de lecture est transitoire et inférieur à 1,4 ms et n'affectera probablement pas l'expérience de l'utilisateur. ![image](/sites/default/files/inline/images/yaspe_systemperformance_db_writes_and_db_read_latency_0000_to_2359_-_aws_ebs_gp3_-_4m_lv_pe_tuesday_08_nov_2022_0.png)
Annonce
Guillaume Rongier · Mars 16, 2022

Démonstration complète d'IntegratedML et de Embedded Python

# 1. Démonstration d'IntegratedML Ce dépôt est une démonstration d'IntegratedML et d'Embedded Python. - [1. Démonstration d'IntegratedML](#1-integrated-ml-demonstration) - [2. Construction de la démo](#2-building-the-demo) - [2.1. Architecture](#21-architecture) - [2.2. Construction du conteneur nginx](#22-building-the-nginx-container) - [3. Exécution de la démo](#3-running-the-demo) - [4. Backend Python](#4-python-back-end) - [4.1. Python embarqué](#41-embedded-python) - [4.1.1. Configuration du conteneur](#411-setting-up-the-container) - [4.1.2. Utiliser Python embarqué](#412-using-embedded-python) - [4.1.3. Comparaison côte à côte](#413-side-by-side-comparaison) - [4.2. Démarrage du serveur](#42-launching-the-server) - [5. IntegratedML](#5-integratedml) - [5.1. Explorer les deux ensembles de données](#51-exploring-both-datasets) - [5.2. Gestion des modèles](#52-managing-models) - [5.2.1. Création d’un modèle](#521-creating-a-model) - [5.2.2. Entraînement d'u modèle](#522-training-a-model) - [5.2.3. Validation d'un modèle](#523-validating-a-model) - [5.2.4. Effectuer des prédictions](#524-making-predictions) - [6. Utilisation de COS](#6-using-cos) - [7. Plus d'explicabilité avec DataRobot](#7-more-explainability-with-datarobot) - [8. Conclusion](#8-conclusion) # 2. Construction de la démo Pour construire la démo, il suffit d'exécuter la commande : ```` docker compose up ```` ## 2.1. Architecture Deux conteneurs seront construits : un avec IRIS et un avec un serveur nginx. ![containers](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/containers.png) L'image IRIS utilisée contient Python embarqué. Après la construction, le conteneur exécutera un serveur wsgi avec l'API Flask. Nous utilisons le paquet communautaire csvgen pour importer le jeu de données titanic dans iris. Pour le jeu de données noshow, nous utilisons une autre méthode personnalisée (la classmethod `Load()` de la classe `Util.Loader`). Pour que le conteneur ait accès aux fichiers csv, nous lions le répertoire local `iris/` au répertoire `/opt/irisapp/` dans le conteneur. ## 2.2. Construction du conteneur nginx Afin de construire notre conteneur nginx, docker utilise une construction en plusieurs étapes. Tout d'abord, il crée un conteneur avec node. Il installe ensuite npm et copie tous nos fichiers dans ce conteneur. Il construit le projet avec la commande `ng build`, et le fichier de sortie est copié dans un nouveau conteneur qui ne contient que nginx. Grâce à cette manœuvre, nous obtenons un conteneur très léger qui ne contient pas toutes les bibliothèques et outils nécessaires à la construction de la page Web. Vous pouvez vérifier les détails de ce multi-build dans le fichier `angular/Dockerfile`. Nous avons également configuré les paramètres de notre serveur nginx grâce au fichier `angular/nginx.default.conf`. # 3. Exécution de la démo Il suffit d'aller à l'adresse : http://localhost:8080/ et c'est tout ! Profitez-en ! # 4. Backend Python Le back-end est réalisé avec Python Flask. Nous utilisons Python embarqué afin d'appeler les classes iris et d'exécuter des requêtes depuis Python. ## 4.1. Python embarqué ### 4.1.1. Configuration du conteneur Dans le dockerfile, nous devons d'abord expliciter deux variables d'environnement que Python embarqué utilisera : ````dockerfile ENV IRISUSERNAME "SuperUser" ENV IRISPASSWORD $IRIS_PASSWORD ```` Avec $IRIS_PASSWORD configuré comme ceci dans le fichier docker-compose : ````yaml iris: build: args: - IRIS_PASSWORD=${IRIS_PASSWORD:-SYS} ```` (Le mot de passe transféré est celui configuré sur votre machine locale ou -s'il n'est pas configuré- sera par défaut « SYS ») ### 4.1.2. Utiliser Python embarqué Afin d'utiliser Python embarqué, nous utilisons `irispython` comme intercepteur de python, et faisons : ```python import iris ``` Tout au début du fichier. Nous serons alors en mesure d'exécuter des méthodes telles que : ![flaskExample](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/flaskExample.png) Comme vous pouvez le voir, pour OBTENIR un passager avec un ID, nous exécutons simplement une requête et utilisons son jeu de résultats. Nous pouvons également utiliser directement les objets IRIS : ![flaskObjectExample](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/flaskObjectExample.png) Ici, nous utilisons une requête SQL pour obtenir tous les ID du tableau, puis nous récupérons chaque passager du tableau avec la méthode `%OpenId()` de la classe `Titanic.Table.Passenger` (notez que `%` étant un caractère illégal en Python, nous utilisons `_` à la place). Grâce à Flask, nous implémentons toutes nos routes et méthodes de cette façon. ### 4.1.3. Comparaison côte à côte Sur cette capture d'écran, vous avez une comparaison côte à côte entre une implémentation **Flask** et une implémentation **ObjectScript**. Comme vous pouvez le voir, il y a beaucoup de similitudes. ## 4.2. Démarrage du serveur Pour lancer le serveur, nous utilisons `gunicorn` avec `irispython`. Dans le fichier docker-compose, nous ajoutons la ligne suivante : ````yaml iris: command: -a "sh /opt/irisapp/flask_server_start.sh" ```` Cela lancera, après le démarrage du conteneur (grâce à l'indicateur `-a`), le script suivant : ````bash #!/bin/bash cd ${FLASK_PATH} ${PYTHON_PATH} /usr/irissys/bin/gunicorn --bind "0.0.0.0:8080" wsgi:app -w 4 2>&1 exit 1 ```` Avec les variables d'environnement définies dans le dockerfile comme suit : ````dockerfile ENV PYTHON_PATH=/usr/irissys/bin/irispython ENV FLASK_PATH=/opt/irisapp/python/flask ```` Nous aurons alors accès au back-end Flask via le port local `4040`, puisque nous y avons lié le port 8080 du conteneur. # 5. IntegratedML ## 5.1. Explorer les deux ensembles de données Pour les deux ensembles de données, vous aurez accès à un CRUD complet, vous permettant de modifier à volonté les tableaux enregistrés. Afin de passer d'un ensemble de données à l'autre, vous pouvez appuyer sur le bouton en haut à droite. ## 5.2. Managing models ### 5.2.1. Création d’un modèle Une fois que vous avez découvert les données, vous pouvez créer un modèle prédisant la valeur que vous souhaitez. En cliquant dans le menu de navigation latéral `Gestionnaire de modèles`, dans la `liste des modèles`, vous aurez accès à la page suivante (ici dans le cas de l'ensemble de données NoShow) : ![modelList](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/modelList.png) Vous pouvez choisir la valeur que vous voulez prédire, le nom de votre modèle, et avec quelles variables vous voulez prédire. Dans le menu latéral, vous pouvez activer `Voir les requêtes SQL ?` pour voir comment les modèles sont gérés dans IRIS. Après avoir créé un modèle, vous devriez voir ceci : ![modelCreated](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/modelCreated.png) Comme vous pouvez le voir, la création d'un modèle ne prend qu'une seule requête SQL. Les informations que vous avez sont toutes les informations que vous pouvez récupérer d'IRIS. Dans la colonne `actions`, vous pouvez supprimer un modèle ou le purger. La purge d'un modèle supprimera tous ses cycles de formation (et leurs cycles de validation), à l'exception du dernier. ### 5.2.2. Entraînement d'u modèle Dans l'onglet suivant, vous pourrez entraîner vos modèles. Vous avez le choix entre 3 fournisseurs. `AutoML` d'InterSystems, `H2O`, une solution open-source, et `DataRobot`, dont vous pouvez avoir un essai gratuit de 14 jours si vous vous enregistrez sur leur site. Vous pouvez sélectionner le pourcentage de l'ensemble de données que vous souhaitez utiliser pour entraîner votre modèle. Étant donné que l'entraînement de grands ensembles de données peut prendre beaucoup de temps, il est possible, pour les besoins des démonstrations, de prendre un ensemble de données plus petit. Ici, nous avons entraîné un modèle en utilisant l'ensemble des données Titanic : ![modelTrained](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/modelTrained.png) Le bouton dans la colonne `actions` vous permettra de voir le log. Pour l'AutoML, vous verrez ce que l'algorithme a réellement fait : comment il a préparé les données et comment il a choisi le modèle à utiliser. L'entraînement d'un modèle ne nécessite qu'une seule requête SQL, comme vous pouvez le voir dans la section des messages du menu de navigation latéral. Gardez à l'esprit que dans ces deux onglets, vous ne verrez que les modèles qui concernent l'ensemble de données que vous utilisez réellement. ### 5.2.3. Validation d'un modèle Enfin, vous pouvez valider un modèle dans le dernier onglet. En cliquant sur une exécution de validation, une boîte de dialogue s'ouvre avec les métriques associées à la validation. Là encore, vous pouvez choisir un pourcentage de l'ensemble de données à utiliser pour la validation. ![modelValidated](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/modelValidated.png) Une fois de plus, il suffit d'une seule requête SQL. ### 5.2.4. Effectuer des prédictions Dans le menu `Faire des prédictions`, dernier onglet, vous pouvez faire des prédictions en utilisant vos modèles nouvellement formés. Il vous suffit de rechercher un passager / patient et de le sélectionner, de choisir l'un des modèles entraînés et d'appuyer sur prédire. Dans le cas d'un modèle de classification (comme dans cet exemple, pour prédire la survie), la prédiction sera associée à la probabilité d'être dans la classe prédite. Dans le cas de Mme Fatima Masselmani, le modèle a correctement prédit qu'elle a survécu, avec une probabilité de 73 %. Juste en dessous de cette prédiction, vous pouvez voir les données utilisées par le modèle : ![prédiction](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/prediction.png) Une fois encore, il faut une requête pour obtenir la prédiction et une pour la probabilité. # 6. Utilisation de COS La démonstration fournit en fait deux API. Nous utilisons l'API Flask avec Python embarqué, mais un service REST dans COS a également été configuré lors de la construction du conteneur. En appuyant sur le bouton en haut à droite **« Passer à l'API COS »**, vous pourrez utiliser ce service. Remarquez que rien ne change. Les deux API sont équivalentes et fonctionnent de la même manière. # 7. Plus d'explicabilité avec DataRobot Si vous voulez plus d'explicabilité (plus que ce que le journal peut vous offrir), nous vous suggérons d'utiliser le fournisseur DataRobot. Pour cela, vous devez vous rendre à l'adresse de votre instance DataRobot, et chercher les `outils de développement` pour obtenir votre jeton. Lors de l'entraînement de votre modèle, la page Web vous demandera votre jeton. Une fois l'entraînement commencé, vous pouvez accéder à votre instance DataRobot pour en savoir beaucoup plus sur votre ensemble de données et vos modèles : ![DRdata](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/DRdata.png) Ici, nous pouvons voir que les champs `sexe` et `nom` de chaque passager sont les valeurs les plus importantes pour prédire la survie. Nous pouvons également voir que le champ `tarif` contient des valeurs aberrantes. Une fois les modèles entraînés, vous pouvez avoir accès à de **nombreux** détails, en voici un aperçu : ![DRmodelDetails](https://raw.githubusercontent.com/thewophile-beep/integrated-ml-demo/main/misc/img/DRmodelDetails.png) # 8. Conclusion Grâce à cette démonstration, nous avons pu voir à quel point il était facile de créer, d'entraîner et de valider un modèle ainsi que de prédire des valeurs à l'aide de très peu de requêtes SQL. Nous avons fait cela en utilisant une API RESTful avec Python Flask, en utilisant Python embarqué, et nous avons fait une comparaison avec une API COS. Le front-end a été réalisé avec Angular. # 9. Remerciements À Théophile, le stagiaire qui a construit cette belle démo pendant l'été 2021.