0 Abonnés · 9 Publications

Node.js est un environnement d'exécution JavaScript open source et multiplateforme permettant d'exécuter du code JavaScript côté serveur..

Article Iryna Mykhailova · Oct 9, 2025 6m read

Aperçu

Le projet typeorm-iris fournit une prise en charge expérimentale pour l'intégration de TypeORM avec InterSystems IRIS, permettant aux développeurs d'interagir avec IRIS à l'aide des décorateurs et des abstractions de référentiel bien connus de TypeORM. Cela offre une expérience de développement plus familière aux développeurs JavaScript et TypeScript qui créent des applications Node.js avec IRIS comme base de données backend.

Aperçu de TypeORM MongoDB. J'ai récemment commencé à utiliser TypeORM dans… | Eliezer  Steinbock | Medium

Bien que le projet mette en œuvre des éléments clés d'intégration avec TypeORM et prenne en charge les opérations de base sur les entités, il n'a pas encore été testé en conditions réelles et n'est pas adapté aux environnements de production.

Pourquoi typeorm-iris?

Le pilote officiel InterSystems IRIS Node.js ne permet pas d'exécuter des requêtes SQL natives , contrairement à d'autres pilotes de base de données (par exemple, PostgreSQL ou MySQL). Au lieu de cela, vous devez utiliser une API basée sur ObjectScript (par exemple, %SQL.Statement) pour préparer et exécuter les commandes SQL.

Cela pose problème lors du développement d'applications modernes qui s'appuient sur des outils de mappage objet-relationnel (ORM) tels que TypeORM. TypeORM nécessite un pilote de niveau inférieur capable de préparer et d'exécuter du code SQL brut dans une seule session de connexion, ce qui n'est actuellement pas possible avec les outils JavaScript d'IRIS.

Pour surmonter ces limitations, typeorm-iris implémente les éléments nécessaires pour faire le pont entre IRIS et TypeORM, en utilisant les interfaces d'exécution SQL ObjectScript disponibles en arrière-plan.

Phase initiale & problèmes connus

Ce projet en est à la phase initiale et n'a été testé que sur quelques cas. Il faut s'attendre à une certaine instabilité, à des fonctionnalités manquantes et à des changements importants dans les prochaines versions.

Les limitations importantes observées au cours du développement sont les suivantes:

1. Allers-retours excessifs en réseau

L'exécution de SQL via la classe %SQL.Statement à partir de JavaScript implique plusieurs messages réseau entre le processus Node.js et le serveur IRIS. Par exemple, une seule opération SQL logique peut nécessiter plusieurs étapes, telles que:

  • Preparation de l'instruction
  • Exécution de la requête
  • Récupération des métadonnées
  • Récupération de lignes de manière individuelle

Chacun d'entre eux peut générer des messages distincts sur le réseau, ce qui entraîne une surcharge considérable par rapport à l'utilisation d'un pilote SQL natif.

2. Aucune prise en charge de l'asynchronisme/du parallélisme véritable

Le pilote IRIS Node.js officiel ne prend pas en charge l'utilisation asynchrone dans un contexte multithread ou basé sur le travail:

  • La reconnexion dans le même processus échoue souvent ou provoque un comportement imprévisible.
  • La création de threads de travail et l'utilisation du pilote à l'intérieur de ceux-ci entraînent des problèmes.
  • Que une seul connexion par processus fonctionne de manière fiable.

Ces contraintes rendent ce système inadapté aux applications Node.js modernes qui traitent des tâches simultanées. Cette situation limite considérablement la capacité du pilote à s'adapter à des charges de travail simultanées et restreint considérablement les choix architecturaux.

Guide d'utilization

En raison de l'utilisation des dernières fonctionnalités IRIS SQL, il est nécessaire de disposer d'IRIS 2025.1+ pour fonctionner.

Vous pouvez installer typeorm-iris avec npm:

npm install typeorm-iris

Comme ce pilote n'est pas officiellement pris en charge par TypeORM, son utilisation nécessite une solution de remplacement pour configurer le DataSource. Vous ne pouvez pas utiliser directement new DataSource() ou createConnection() comme vous le feriez avec les pilotes officiels.

Configuration personnalisée de la source de données

import { IRISDataSource, IRISConnectionOptions } from "typeorm-iris"

const dataSourceOptions: IRISConnectionOptions = { name: "iris", type: "iris", host: "localhost", port: 1972, username: "_SYSTEM", password: "SYS", namespace: "USER", logging: true, dropSchema: true, }

export function createDataSource(options: any): IRISDataSource { // @ts-ignore const dataSource = new IRISDataSource({ ...dataSourceOptions, ...options }) return dataSource }

Lorsque l'initialisation est terminée, vous pouvez utiliser les décorateurs TypeORM comme d'habitude:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity() export class User { @PrimaryGeneratedColumn() id: number

@Column()
name: string

@Column()
email: string

}

Il en va de même pour les référentiels:

const userRepository = dataSource.getRepository(User)
const newUser = userRepository.create({ name: "Alice", email: "alice@example.com" })
await userRepository.save(newUser)

Exemples de projets

Le référentiel GitHub comprend un dossier sample/ qui contient plusieurs exemples fonctionnels:

  • sample1-simple-entity
  • sample2-one-to-one
  • sample3-many-to-one
  • sample4-many-to-many
  • sample16-indexes

Celles-ci traitent des fonctionnalités de base en matière de persistance, de relations et de schémas, et présentent des exemples d'utilisation pratique.

Tests unitaires

Les tests initiaux comprennent les cas d'utilisation suivants :

Modèle d'entité

  • doit enregistrer et utiliser de manière appropriée les méthodes statiques
  • doit recharger correctement l'entité donnée
  • doit recharger exactement la même entité
  • doit effectuer correctement un upsert

Schéma d'entité > Index

  • basique

Persistance

  • fonctionnalité basique
  • mise à jour d'entité
  • insertion > update-relation-columns-after-insertion (mise à jour des colonnes de relation après insertion)
  • plusieurs-à-plusieurs
  • en individuel

Ces tests ont une portée limitée et leur couverture sera élargie au fur et à mesure de l'avancement du projet.

Fonctionnalités prises en charge

  • Décorateurs d'entités: @Entity(), @Column(), @PrimaryGeneratedColumn()
  • Référentiels: create, save, find, delete, etc.
  • Suppression et synchronisation du schéma (expérimental)
  • Prise en charge partielle des relations et des requêtes personnalisées

Attention, ces fonctionnalités sont encore en phase initiale et peuvent ne pas couvrir l'ensemble des capacités de TypeORM.

Limitations réelles

Limitations liées au pilote Node.js d'InterSystems IRIS

  • Une seule connexion utilisable par processus
  • Pas de prise en charge adéquate des opérations parallèles ou du threading
  • Absence de prise en charge native de l'API SQL (via le protocole SQL)
  • Dépendance importante à la communication par messages utilisant un protocole propriétaire

Tant qu'InterSystems n'aura pas mis à jour le pilote officiel afin d'assurer la prise en charge de l'exécution SQL et des opérations simultanées, ce projet restera très limité en termes de performances et d'évolutivité.

Commentaires & Contributions

Comme il s'agit d'un pilote expérimental, vos commentaires sont essentiels. Que vous l'essayiez pour un petit projet parallèle ou que vous l'évaluiez en vue d'une utilisation plus large, n'hésitez pas à partager vos problèmes et suggestions sur GitHub:

➡️ github.com/caretdev/typeorm-iris/issues

Les pull requests, les cas de test et les améliorations de la documentation sont appréciés.

À venir

Les améliorations prévues incluent:

  • Élargissement de la couverture des tests pour les requêtes réelles et les conceptions de schémas
  • Prise en charge d'un plus grand nombre de fonctionnalités du générateur de requêtes TypeORM
  • Analyse des optimisations par lots
  • Amélioration de l'introspection des schémas pour les migrations

Conclusion

typeorm-iris apporte un soutien indispensable de TypeORM à InterSystems IRIS pour les développeurs Node.js. Bien qu'il ne soit pas encore prêt pour la production et qu'il hérite des limitations importantes de l'infrastructure actuelle du pilote, il constitue une base pour poursuivre l'expérimentation et potentiellement élargir son adoption au sein de la communauté des développeurs IRIS.

Si vous êtes un développeur IRIS et cherchez à intégrer un backend Node.js moderne à l'aide de TypeORM, c'est le point de départ idéal.

Et si vous avez trouvé cela utile, n'hésitez pas à voter à pour cette solution dans le concours InterSystems Developer Tools Contest!

0
0 11
Article Sylvain Guilbaud · Oct 7, 2025 5m read

Aperçu Je suis ravi d'annoncer la sortie de testcontainers-iris-node, une bibliothèque Node.js qui facilite le lancement de conteneurs InterSystems IRIS temporaires pour l'intégration et les tests E2E. Ce projet vient naturellement compléter la gamme existante d'adaptateurs Testcontainers pour IRIS, notamment testcontainers-iris-python et testcontainers-iris-java.

Pourquoi testcontainers-iris-node? En tant que développeur Node.js travaillant avec InterSystems IRIS, j'ai souvent été confronté à des difficultés lors de la configuration d'environnements de test imitant la production. testcontainers-iris-node résout ce problème en exploitant le framework testcontainers-node pour créer des environnements IRIS isolés à la demande.

Ceci est particulièrement important pour:

  • Les tests d'intégration avec les bases de données IRIS
  • Les tests de pipelines de données ou de microservices
  • L'automatisation des environnements de test dans les pipelines CI

Fonctionnalités

  • Lancement d'IRIS dans des conteneurs Docker à l'aide de Testcontainers
  • Prise en charge des images et de la configuration Docker personnalisées
  • Stratégies d'attente pour s'assurer qu'IRIS est prêt avant le début des tests
  • Désinstallation de nettoyage entre les exécutions de tests

Pour commencer

npm install testcontainers-iris --save-dev

Exemple d'utilisation

import { IRISContainer } from"testcontainers-iris";
import { createConnection } from"@intersystems/intersystems-iris-native";

const IMAGE = "containers.intersystems.com/intersystems/iris-community:latest-preview"; const container = awaitnew IRISContainer(IMAGE).start(); const connection = createConnection(container.getConnectionOptions()); const iris = connection.createIris(); const version = iris.classMethodString("%SYSTEM.Version", "GetNumber");

Fonctionnement En interne, la bibliothèque étend GenericContainer à partir de testcontainers, ajoute des stratégies d'attente spécifiques à IRIS et fournit des méthodes auxiliaires pour la génération de chaînes de connexion et le remplacement des configurations.

Scénarios pris en charge

  • Suites de tests basées sur Jest ou Mocha
  • Environments CI (GitHub Actions, GitLab CI, Jenkins, etc.)
  • Développement et débogage locaux

Exemple de tests basées sur Mocha Vous pouvez également utiliser cette bibliothèque pour des tests d'intégration robustes avec Mocha. Voici un exemple de configuration:

test-setup.ts

import"source-map-support/register"import"reflect-metadata"import { IRISContainer, StartedIRISContainer } from"testcontainers-iris"import { IRISNative } from"../../src"import chai from"chai"import sinonChai from"sinon-chai"import chaiAsPromised from"chai-as-promised"
declare global {
    var container: StartedIRISContainer | undefinedvar connectionOptions: {
        host: string
        port: number
        user: string
        pwd: string
        ns: string
    }
}

process.env.TZ = "UTC" chai.should() chai.use(sinonChai) chai.use(chaiAsPromised)

before(async () => { console.log("Setting up test environment...") const image = process.env["IRIS_IMAGE"] let connectionOptions = { host: "localhost", port: 1972, user: "_SYSTEM", pwd: "SYS", ns: "USER", } if (image) { const container: StartedIRISContainer = awaitnew IRISContainer(image) .withNamespace("TEST") .start() console.log(IRIS container started at <span class="hljs-subst">${container.getConnectionUri()}</span>) global.container = container connectionOptions = { host: container.getHost(), port: container.getMappedPort(1972), user: container.getUsername(), pwd: container.getPassword(), ns: container.getNamespace(), } } global.connectionOptions = connectionOptions IRISNative.createConnection({ ...connectionOptions, sharedmemory: false }) })

after(async () => { console.log("Cleaning up test environment...") if (global.container) { await global.container.stop() } delete global.container })

Cas de test:

import { IRISNative, IRISConnection } from"../src/IRISNative"
describe("IRISNative test", () => {
    let connection: IRISConnection
    before(() => {
        const connectionOptions = global.connectionOptions
        connection = IRISNative.createConnection({ ...connectionOptions })
    })
    after(() => {
        if (connection) {
            connection.close()
        }
    })
    it("should work", async () => {
        const res = await connection.query(
            "SELECT 1 AS test1, '2' AS test2",
            [],
        )
        res.rows.should.be.an("array")
        res.rows.should.have.lengthOf(1)
        res.rows[0].should.be.an("object")
        res.rows[0].should.have.property("test1")
        res.rows[0].should.have.property("test2")
        res.rows[0].should.have.property("test1", 1)
        res.rows[0].should.have.property("test2", "2")
    })
})

Utilisation dans typeorm-iris Cette bibliothèque est également utilisée dans mon projet typeorm-iris qui fournit une prise en charge expérimentale de TypeORM pour InterSystems IRIS. testcontainers-iris-node alimente la configuration des tests d'intégration pour ce projet, aidant à valider la fonctionnalité ORM par rapport à des instances IRIS réelles.

Problèmes liés à l'adoption de la bibliothèque En tant que développeur chargé de l'adoption de la bibliothèque, l'un de mes plus grands défis consiste à tester plusieurs versions d'InterSystems IRIS. Cet outil simplifie considérablement ce processus en permettant de basculer facilement et d'automatiser des environnements conteneurisés avec des versions IRIS différentes.

Comparaison avec d'autres liaisons linguistiques Alors que testcontainers-iris-python et testcontainers-iris-java sont matures et prennent en charge des fonctionnalités avancées telles que les montages en bind et les scripts de démarrage personnalisés, la variante Node.js est rationalisée pour les environnements JavaScript/TypeScript et vise la simplicité et l'ergonomie pour les développeurs.

Contributions et commentaires Je suis ouvert à tous commentaires, problèmes et demandes de modification via GitHub: testcontainers-iris-node.

Pour concluretestcontainers-iris-node facilite la réalisation de tests automatisés et robustes des applications basées sur IRIS dans l'écosystème Node.js. Que vous développiez des API, des tâches ETL ou des services d'intégration, cet outil vous aide à garantir la fiabilité de vos flux de travail IRIS.

0
1 17
Article Sylvain Guilbaud · Mars 15, 2024 2m read

InterSystems rubrique FAQ 

Vous pouvez utiliser la classe %IndexBuilder pour effectuer la reconstruction d'index dans plusieurs processus parallèles.

Voici un exemple dans le but de définir l'index standard HomeStateIdx pour la colonne Home_State (informations de l'État de l'adresse du domicile) de Sample.Person.

Les étapes sont les suivantes:

1. Masquez le nom de l'index à ajouter/reconstruire à partir de l'optimiseur de requêtes.

>write$system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",0)
1
0
0 65
Article Pierre LaFay · Mars 3, 2024 9m read

Intro

La plupart des communications serveur-client sur le web sont basées sur une structure de demande et de réponse. Le client envoie une demande au serveur et le serveur répond à cette demande. Le protocole WebSocket fournit un canal de communication bidirectionnel entre un serveur et un client, permettant aux serveurs d'envoyer des messages aux clients sans recevoir de demande au préalable. Pour plus d'informations sur le protocole WebSocket et son implémentation dans InterSystems IRIS, voir les liens ci-dessous.

Ce tutoriel est une mise à jour de ["Asynchronous Websockets -- a quick tutorial"] (https://community.intersystems.com/post/asynchronous-websockets-quick-tutorial) pour Caché 2016.2+ et InterSystems IRIS 2018.1+.

Fonctionnement asynchrone ou synchrone

Dans InterSystems IRIS, une connexion WebSocket peut être mise en œuvre de manière synchrone ou asynchrone. Le mode de fonctionnement de la connexion WebSocket entre le client et le serveur est déterminé par la propriété "SharedConnection" de la classe %CSP.WebSocket.

  • SharedConnection=1 : Fonctionnement asynchrone

  • SharedConnection=0: Fonctionnement synchrone

Une connexion WebSocket entre un client et un serveur hébergé sur une instance InterSystems IRIS comprend une connexion entre l'instance IRIS et la passerelle Web. En mode synchrone, la connexion utilise un canal privé. Dans le cas d'une opération WebSocket asynchrone, un groupe de clients WebSocket partage un pool de connexions entre l'instance IRIS et la passerelle Web. L'avantage d'une implémentation asynchrone des WebSockets est évident lorsque de nombreux clients se connectent au même serveur, car cette implémentation n'exige pas que chaque client soit traité par une connexion exclusive entre la passerelle Web et l'instance IRIS.

Dans ce tutoriel, nous mettrons en œuvre les WebSockets de manière asynchrone. Ainsi, toutes les fenêtres de chat ouvertes partagent un pool de connexions entre la passerelle Web et l'instance IRIS qui héberge la classe de serveur WebSocket.

Présentation de l'application Chat

Le "hello world" des WebSockets est une application de chat dans laquelle un utilisateur peut envoyer des messages qui sont diffusés à tous les utilisateurs connectés à l'application. Dans ce tutoriel, les composants de l'application de chat sont les suivants :

  • Serveur : implémenté dans une classe qui étend %CSP.WebSocket

  • Client: mis en œuvre par une page CSP

La mise en œuvre de cette application de chat permettra d'atteindre les objectifs suivants :

  • Les utilisateurs peuvent diffuser des messages à toutes les fenêtres de chat ouvertes

  • Les utilisateurs en ligne apparaissent dans la liste "Utilisateurs en ligne" de toutes les fenêtres de chat ouvertes.

  • Les utilisateurs peuvent changer leur nom d'utilisateur en composant un message commençant par le mot-clé "alias". Ce message ne sera pas diffusé mais mettra à jour la liste des "Utilisateurs en ligne".

  • Lorsque les utilisateurs ferment leur fenêtre de chat, ils sont retirés de la liste des "Utilisateurs en ligne".

Pour obtenir le code source de l'application de chat, veuillez consulter ce [dépôt GitHub] (https://github.com/intersystems/InterSystems-WebSockets).

Le Client

Le côté client de notre application de chat est mis en œuvre par une page CSP contenant le style de la fenêtre de chat, la déclaration de la connexion WebSocket, les événements et méthodes WebSocket qui gèrent la communication vers et depuis le serveur, et les fonctions d'aide qui regroupent les messages envoyés au serveur et traitent les messages entrants.

Tout d'abord, nous verrons comment l'application initie la connexion WebSocket à l'aide d'une bibliothèque Javascript WebSocket.

    ws = new WebSocket(((window.location.protocol === "https:")? "wss:":"ws:")
                    + "//"+ window.location.host + "/csp/user/Chat.Server.cls");

new crée une nouvelle instance de la classe WebSocket. Celle-ci ouvre une connexion WebSocket au serveur en utilisant le protocole "wss" (indique l'utilisation de TLS pour le canal de communication WebSocket) ou "ws". Le serveur est spécifié par le numéro de port du serveur web et le nom de l'hôte de l'instance qui définit la classe Chat.Server (cette information est contenue dans la variable window.location.host). Le nom de notre classe de serveur (Chat.Server.cls) est inclus dans l'URI d'ouverture de la WebSocket en tant que requête GET pour la ressource sur le serveur.

L'événement ws.onopen se déclenche lorsque la connexion WebSocket est établie avec succès, passant d'un état connecting à un état open.

    ws.onopen = function(event){
        document.getElementById("headline").innerHTML = "CHAT - CONNECTED";
    };

Cet événement met à jour l'en-tête de la fenêtre de chat pour indiquer que le client et le serveur sont connectés.

Envoi de messages

L'action d'un utilisateur qui envoie un message déclenche la fonction send. Cette fonction sert d'enveloppe à la méthode ws.send, qui contient les mécanismes d'envoi du message du client au serveur via la connexion WebSocket.

function send() {
	var line=$("#inputline").val();
	if (line.substr(0,5)=="alias"){
	    alias=line.split(" ")[1];
		if (alias==""){
		    alias="default";
		}
		var data = {}
		data.User = alias
		ws.send(JSON.stringify(data));
        } else {
	    var msg=btoa(line);
	    var data={};
	    data.Message=msg;
	    data.Author=alias;
	    if (ws && msg!="") {
		    ws.send(JSON.stringify(data));
	    }
	}
	$("#inputline").val("");
}

send compile les informations à envoyer au serveur dans un objet JSON, en définissant des paires clé/valeur en fonction du type d'information envoyée (mise à jour d'alias ou message général). btoa traduit le contenu d'un message général en une chaîne ASCII encodée en base 64.

Réception des messages

Lorsque le client reçoit un message du serveur, l'événement ws.onmessage est déclenché.

ws.onmessage = function(event) {
	var d=JSON.parse(event.data);
	if (d.Type=="Chat") {
	    $("#chat").append(wrapmessage(d));
            $("#chatdiv").animate({ scrollTop: $('#chatdiv').prop("scrollHeight")}, 1000);
	} else if(d.Type=="userlist") {
		var ul = document.getElementById("userlist");
		while(ul.firstChild){ul.removeChild(ul.firstChild)};
		$("#userlist").append(wrapuser(d.Users));
	} else if(d.Type=="Status"){
		document.getElementById("headline").innerHTML = "CHAT - connected - "+d.WSID;
	}
};

Selon le type de message reçu par le client ("Chat", "userlist" ou "status"), l'événement onmessage appelle wrapmessage ou wrapuser pour remplir les sections appropriées de la fenêtre de chat avec les données entrantes. Si le message entrant est une mise à jour d'état, l'en-tête d'état de la fenêtre de discussion est mis à jour avec l'ID WebSocket, qui identifie la connexion WebSocket bidirectionnelle associée à la fenêtre de discussion.

Composants supplémentaires du client

Une erreur dans la communication entre le client et le serveur déclenche la méthode WebSocket onerror, qui émet une alerte nous informant de l'erreur et mettant à jour l'en-tête d'état de la page.

ws.onerror = function(event) {
	document.GetElementById("headline").innerHTML = "CHAT - error";
	alert("Received error"); 
};

The onclose method is triggered when the WebSocket connection between the client and server is closed and updates the status header.

ws.onclose = function(event) {
	ws = null;
	document.getElementById("headline").innerHTML = "CHAT - disconnected";
}

Le Serveur

Le côté serveur de l'application de chat est implémenté par la classe Chat.Server, qui étend %CSP.WebSocket. Notre classe serveur hérite de diverses propriétés et méthodes de %CSP.WebSocket, dont certaines seront discutées ci-dessous. Chat.Server implémente également des méthodes personnalisées pour traiter les messages provenant du ou des client(s) et pour les diffuser.

Avant de démarrer le serveur

OnPreServer() est exécutée avant la création du serveur WebSocket et est héritée de la classe %CSP.WebSocket.

Method OnPreServer() As %Status
{
    set ..SharedConnection=1
    if (..WebSocketID '= ""){ 
        set ^Chat.WebSocketConnections(..WebSocketID)=""
    } else {
        set ^Chat.Errors($INCREMENT(^Chat.Errors),"no websocketid defined")=$HOROLOG 
    }
    Quit $$$OK
}

Cette méthode définit le paramètre de classe SharedConnection à 1, indiquant que notre connexion WebSocket sera asynchrone et supportée par plusieurs processus qui définissent les connexions entre l'instance InterSystems IRIS et la Web Gateway. Le paramètre SharedConnection ne peut être modifié que dans OnPreServer(). OnPreServer() stocke également l'identifiant WebSocket associé au client dans le global ^Chat.WebSocketConnections.

La méthode Server

L'essentiel de la logique exécutée par le serveur est contenu dans la méthode Server().``` Method Server() As %Status { do ..StatusUpdate(..WebSocketID) for { set data=..Read(.size,.sc,1) if ($$$ISERR(sc)){ if ($$$GETERRORCODE(sc)=$$$CSPWebSocketTimeout) { //$$$DEBUG("no data") } if ($$$GETERRORCODE(sc)=$$$CSPWebSocketClosed){ kill ^Chat.WebSocketConnections(..WebSocketID) do ..RemoveUser($g(^Chat.Users(..WebSocketID))) kill ^Chat.Users(..WebSocketID) quit // Client closed WebSocket } } else{ if data["User"{ do ..AddUser(data,..WebSocketID) } else { set mid=$INCREMENT(^Chat.Messages) set ^Chat.Messages(mid)=data do ..ProcessMessage(mid) } } } Quit $$$OK }

Cette méthode lit les messages entrants du client (en utilisant la méthode `Read` de la classe `%CSP.WebSockets`), ajoute les objets JSON reçus au global `^Chat.Messages`, et appelle `ProcessMessage()` pour transmettre le message à tous les autres clients de chat connectés. Lorsqu'un utilisateur ferme sa fenêtre de chat (mettant ainsi fin à la connexion WebSocket avec le serveur), l'appel à `Read` de la méthode `Server()` renvoie un code d'erreur qui évalue la macro `$$CSPWebSocketClosed` et la méthode procède à la gestion de la fermeture en conséquence.

### *Traitement et distribution des messages*

`ProcessMessage()` ajoute des métadonnées au message de chat entrant et appelle `SendData()`, en passant le message en paramètre.  

ClassMethod ProcessMessage(mid As %String) { set msg = ##class(%DynamicObject).%FromJSON($GET(^Chat.Messages(mid))) set msg.Type="Chat" set msg.Sent=$ZDATETIME($HOROLOG,3) do ..SendData(msg) }


`ProcessMessage()` récupère le message au format JSON dans le global `^Chat.Messages` et le convertit en un objet IRIS d'InterSystems en utilisant la méthode `%FromJSON` de la classe `%DynamicObject`. Cela nous permet d'éditer facilement les données avant de transmettre le message à tous les clients de chat connectés. Nous ajoutons un attribut `Type` avec la valeur "Chat", que le client utilise pour déterminer comment traiter le message entrant. `SendData()` envoie le message à tous les autres clients de chat connectés.```
ClassMethod SendData(data As %DynamicObject)
{
    set c = ""
    for {
        set c = $order(^Chat.WebSocketConnections(c))
        if c="" Quit
        set ws = ..%New()
        set sc = ws.OpenServer(c)
        if $$$ISERR(sc) { do ..HandleError(c,"open") } 
        set sc = ws.Write(data.%ToJSON())
        if $$$ISERR(sc) { do ..HandleError(c,"write") }
    }
}

SendData() convertit l'objet InterSystems IRIS en une chaîne JSON (data.%ToJSON()) et envoie le message à tous les clients du chat. SendData() récupère l'ID WebSocket associé à chaque connexion client-serveur dans le global ^Chat.WebSocketConnections et utilise l'ID pour ouvrir une connexion WebSocket via la méthode OpenServer de la classe %CSP.WebSocket. Nous pouvons utiliser la méthode OpenServer car nos connexions WebSocket sont asynchrones - nous puisons dans le pool existant de processus IRIS-Web Gateway et attribuons à l'un d'entre eux l'ID WebSocket qui identifie la connexion du serveur à un client de chat spécifique. Enfin, la méthode Write()%CSP.WebSocket` envoie la représentation JSON du message au client.

Conclusion

Cette application de chat montre comment établir des connexions WebSocket entre un client et un serveur hébergés par InterSystems IRIS. Pour en savoir plus sur le protocole et son implémentation dans InterSystems IRIS, consultez les liens dans l'introduction.

0
0 199
Question Cyril Grosjean · Nov 15, 2023

Bonjour,

Dans le besoin de notre service infrastructure de notre entreprise, j'ai créé une petite API qui fait des requêtes SNMP sur InterSystems afin de visualiser les données intéressantes à récupérer lorsque l'infra mettra en place le monitoring.

Cependant, j'ai un timeout lorsque j'essaye de collecter des informations via un walk. Voici le code du service SNMP de mon API:

7
0 111
Article Guillaume Rongier · Avr 4, 2022 13m read

3. Variantes des structures lors de l'utilisation de globales

Une structure, telle qu'un arbre ordonné, présente plusieurs cas particuliers. Examinons ceux qui ont une valeur pratique pour le travail avec les globales.

3.1 Cas particulier 1. Un nœud sans branches

Les globales peuvent être utilisées non seulement comme une liste de données, mais aussi comme des variables ordinaires. Par exemple, pour créer un compteur :  

Set ^counter = 0  ; setting counter
Set id=$Increment(^counter) ;  atomic incrementation

En même temps, une globale peut avoir des branches outre sa valeur. L'un n'exclut pas l'autre.

3.2 Cas particulier 2. Un nœud et plusieurs branches

En fait, il s'agit d'une base classique clé-valeur. Et si nous enregistrons des tuples de valeurs au lieu de valeurs, nous obtiendrons une table ordinaire avec une clé primaire.

Afin d'implémenter une table basé sur des globales, nous devrons former des chaînes de caractères à partir des valeurs des colonnes, puis les enregistrer dans une globale par la clé primaire. Afin de pouvoir diviser la chaîne en colonnes lors de la lecture, nous pouvons utiliser ce qui suit :

  1. Caractère de délimitation.
Set ^t(id1) = "col11/col21/col31"
Set ^t(id2) = "col12/col22/col32"
  1. Un schéma fixe, dans lequel chaque champ occupe un nombre particulier d'octets. C'est ainsi qu'on procède généralement dans les bases de données relationnelles.

  2. Une fonction spéciale $LB (introduite dans Caché) qui compose une chaîne de caractères à partir de valeurs.

Set ^t(id1) = $LB("col11", "col21", "col31")
Set ^t(id2) = $LB("col12", "col22", "col32")

Ce qui est intéressant, c'est qu'il n'est pas difficile de faire quelque chose de similaire aux clés étrangères dans les bases de données relationnelles en utilisant des globales. Appelons ces structures des index globaux. Un index global est un arbre supplémentaire permettant d'effectuer des recherches rapides sur des champs qui ne font pas partie intégrante de la clé primaire de la globale principale. Vous devez écrire un code supplémentaire pour le remplir et l'utiliser.

Nous créons un index global basé sur la première colonne.

Set ^i("col11", id1) = 1
Set ^i("col12", id2) = 1

Pour effectuer une recherche rapide par la première colonne, vous devrez regarder dans la ^i globale et trouver les clés primaires (id) correspondant à la valeur nécessaire dans la première colonne.

Lors de l'insertion d'une valeur, nous pouvons créer à la fois des valeurs et des index globaux pour les champs nécessaires. Pour plus de fiabilité, nous allons l'intégrer dans une transaction.

TSTART
Set ^t(id1) = $LB("col11", "col21", "col31")
Set ^i("col11", id1) = 1
TCOMMIT

Plus d'informations sont disponibles ici making tables in M using globals and emulation of secondary keys.

Ces tables fonctionneront aussi rapidement que dans les bases de données traditionnelles (ou même plus rapidement) si les fonctions d'insertion/mise à jour/suppression sont écrites en COS/M et compilées.

J'ai vérifié cette affirmation en appliquant un grand nombre d'opérations INSERT et SELECT à une seule table à deux colonnes, en utilisant également les commandes TSTART et TCOMMIT (transactions).

Je n'ai pas testé de scénarios plus complexes avec des accès concurrents et des transactions parallèles.

Sans utiliser de transactions, la vitesse d'insertion pour un million de valeurs était de 778 361 insertions/seconde.

Pour 300 millions de valeurs, la vitesse était de 422 141 insertions/seconde.

Lorsque des transactions ont été utilisées, la vitesse a atteint 572 082 insertions/seconde pour 50 millions de valeurs. Toutes les opérations ont été exécutées à partir du code M compilé. J'ai utilisé des disques durs ordinaires, pas des SSD. RAID5 avec Write-back. Le tout fonctionnant sur un processeur Phenom II 1100T.

Pour effectuer le même test pour une base de données SQL, il faudrait écrire une procédure stockée qui effectuerait les insertions en boucle. En testant MySQL 5.5 (stockage InnoDB) avec la même méthode, je n'ai jamais obtenu plus de 11K insertions par seconde.

En effet, l'implémentation de tables avec des globales est plus complexe que de faire la même chose dans des bases de données relationnelles. C'est pourquoi les bases de données industrielles basées sur les globales ont un accès SQL pour simplifier le travail avec les données tabulaires.

En général, si le schéma de données ne change pas souvent, que la vitesse d'insertion n'est pas critique et que l'ensemble de la base de données peut être facilement représenté par des tables normalisées, il est plus facile de travailler avec SQL, car il offre un niveau d'abstraction plus élevé.

Dans ce cas, je voulais montrer que les globales peuvent être utilisées comme un constructeur pour créer d'autres bases de données. Comme le langage assembleur qui peut être utilisé pour créer d'autres langages. Et voici quelques exemples d'utilisation des globales pour créer des contreparties de key-values, lists, sets, tabular, document-oriented DB's.

Si vous devez créer une base de données non standard avec un minimum d'efforts, vous devriez envisager d'utiliser les globales.

3.3 Cas particulier 3. Un arbre à deux niveaux dont chaque nœud de deuxième niveau a un nombre fixe de branches

Vous l'avez probablement deviné : il s'agit d'une implémentation alternative des tables utilisant des globales. Comparons-la avec la précédente.

<th>
  Pros
</th>
<td>
  Un accès plus rapide aux valeurs de certaines colonnes, puisque vous n'avez pas besoin d'analyser la chaîne de caractères. D'après mes tests, c'est 11,5 % plus rapide pour 2 colonnes et encore plus rapide pour plus de colonnes. Il est plus facile de modifier le schéma de données et de lire le code.
</td>
Tables dans un arborescence deux niveaux vs. Tables dans un arborescence mono niveau.
Cons
Insertions plus lentes, car le nombre de nœuds doit être égal au nombre de colonnes. Une plus grande consommation d'espace sur le disque dur, car les index globaux (comme les index de table) avec les noms de colonne occupent de l'espace sur le disque dur et sont dupliqués pour chaque ligne.  

Conclusion: Rien d'extraordinaire. Les performances étant l'un des principaux avantages des globales, il n'y a pratiquement aucun intérêt à utiliser cette approche, car il est peu probable qu'elle soit plus rapide que les tables ordinaires des bases de données relationnelles.

3.4 Cas général. Arbres et clés ordonnées

Toute structure de données qui peut être représentée comme un arbre s'adapte parfaitement aux globales.

3.4.1 Objets avec des sous-objets

C'est dans ce domaine que les globales sont traditionnellement utilisées. Il existe de nombreuses maladies, médicaments, symptômes et méthodes de traitement dans le domaine médical. Il est irrationnel de créer une table avec un million de champs pour chaque patient, d'autant plus que 99% d'entre eux seront vides.

Imaginez une base de données SQL composée des tables suivants : " Patient " ~ 100 000 champs, " Médicament " 100 000 champs, " Thérapie " 100 000 champs, " Complications " 100 000 champs et ainsi de suite. Comme alternative, vous pouvez créer une BD avec des milliers de tableaux, chacun pour un type de patient particulier (et ils peuvent aussi se superposer !), un traitement, un médicament, ainsi que des milliers de tables pour les relations entre ces tables.

Les globales s'adaptent parfaitement aux soins de santé, puisqu'elles permettent à chaque patient de disposer d'un dossier complet, de la liste des thérapies, des médicaments administrés et de leurs effets, le tout sous la forme d'un arbre, sans gaspiller trop d'espace disque en colonnes vides, comme ce serait le cas avec les bases de données relationnelles.

Les globales fonctionnent bien pour les bases de données contenant des données personnelles, lorsque la tâche consiste à accumuler et à systématiser le maximum de données personnelles diverses sur un client. C'est particulièrement important dans les domaines de la santé, de la banque, du marketing, de l'archivage et autres.

Il est évident que SQL permet également d'émuler un arbre en utilisant seulement quelques tables (EAV, 1,2,3,4,5,6, 7,8), mais c'est beaucoup plus complexe et plus lent. En fait, nous devrions écrire une globale basé sur des tables et cacher toutes les routines liées aux tables sous une couche d'abstraction. Il n'est pas correct d'émuler une technologie de niveau inférieur (les globales) à l'aide d'une technologie de niveau supérieur (SQL). C'est tout simplement injustifié.

Ce n'est pas un secret que la modification d'un schéma de données dans des tableaux gigantesques (ALTER TABLE) peut prendre un temps considérable. MySQL, par exemple, effectue l'opération ALTER TABLE ADD|DROP COLUMN en copiant toutes les données de l'ancienne tableau vers la nouvelle (je l'ai testé sur MyISAM et InnoDB). Cela peut bloquer une base de données de production contenant des milliards d'enregistrements pendant des jours, voire des semaines.

Si nous utilisons des globales, la modification de la structure des données ne nous coûte rien Nous pouvons ajouter de nouvelles propriétés à n'importe quel objet, à n'importe quel niveau de la hiérarchie et à n'importe quel moment. Les changements qui nécessitent de renommer les branches peuvent être appliqués en arrière-plan avec la base de données en fonctionnement.


Par conséquent, lorsqu'il s'agit de stocker des objets comportant un grand nombre de propriétés facultatives, les globales fonctionnent parfaitement.

Je vous rappelle que l'accès à l'une des propriétés est instantané, puisque dans une globale, tous les chemins sont un B-Arbre.

Dans le cas général, les bases de données basées sur des globales sont un type de bases de données orientées documents qui supportent le stockage d'informations hiérarchiques. Par conséquent, les bases de données orientées documents peuvent concurrencer efficacement les globales dans le domaine du stockage des cartes médicales.

Mais ce n'est pas encore le cas.

Prenons MongoDB, par exemple. Dans ce champ, il perd face aux globales pour les raisons suivantes :
  1. Taille du document. L'unité de stockage est un texte au format JSON (BSON, pour être exact) dont la taille maximale est d'environ 16 Mo. Cette limitation a été introduite dans le but de s'assurer que la base de données JSON ne devienne pas trop lente lors de l'analyse syntaxique, lorsqu'un énorme document JSON y est enregistré et que des valeurs de champ particulières sont traitées. Ce document est censé contenir des informations complètes sur un patient. Nous savons tous à quel point les cartes de patient peuvent être épaisses. Si la taille maximale de la carte est plafonnée à 16 Mo, cela permet de filtrer immédiatement les patients dont les cartes contiennent des IRM, des radiographies et d'autres documents. Une seule branche d'une entreprise mondiale peut contenir des gigaoctets, des pétaoctets ou des téraoctets de données. Tout est dit, mais laissez-moi vous en dire plus.
  2. Le temps nécessaire à la création/modification/suppression de nouvelles propriétés de la carte du patient. Une telle base de données devrait copier la carte entière dans la mémoire (beaucoup de données !), analyser les données BSON, ajouter/modifier/supprimer le nouveau nœud, mettre à jour les index, remballer le tout en BSON et sauvegarder sur le disque. Une globale n'aurait besoin que d'adresser la propriété nécessaire et d'effectuer l'opération nécessaire.
  3. La vitesse d'accès à des propriétés particulières. Si le document possède de nombreuses propriétés et une structure à plusieurs niveaux, l'accès à des propriétés particulières sera plus rapide car chaque chemin dans la globale est le B-Arbre. En BSON, vous devrez analyser linéairement le document pour trouver la propriété nécessaire.

3.3.2 Tables associatives

Les tables associatives (même avec les tables imbriquées) fonctionnent parfaitement avec les globales. Par exemple, cette table PHP ressemblera à la première illustration en 3.3.1.

$a = array(
  "name" => "Vince Medvedev",
  "city" => "Moscow",
  "threatments" => array(
    "surgeries" => array("apedicectomy", "biopsy"),
    "radiation" => array("gamma", "x-rays"),
    "physiotherapy" => array("knee", "shoulder")
  )
);

3.3.3 Documents hiérarchiques : XML, JSON

Ils peuvent également être facilement stockés dans des globales et décomposés de manières différentes.

XML

La méthode la plus simple pour décomposer le XML en globales consiste à stocker les attributs des balises dans les nœuds. Et si vous avez besoin d'un accès rapide aux attributs des attributs, nous pouvons les placer dans des branches séparées.

<note id=5>
<to>Alex</to>
<from>Sveta</from>
<heading>Reminder</heading>
<body>Call me tomorrow!</body>
</note>

Dans COS, le code ressemblera à ceci :

Set ^xml("note")="id=5"
Set ^xml("note","to")="Alex"
Set ^xml("note","from")="Sveta"
Set ^xml("note","heading")="Reminder"
Set ^xml("note","body")="Call me tomorrow!"

Note: Pour XML, JSON et les tables associatives, vous pouvez imaginer un certain nombre de méthodes pour les afficher dans les globales. Dans ce cas particulier, nous n'avons pas reflété l'ordre des balises imbriquées dans la balise "note". Dans la globale ^xml, les balises imbriquées seront affichés dans l'ordre alphabétique. Pour un affichage précis de l'ordre, vous pouvez utiliser le modèle suivant, par exemple :

JSON.

Le contenu de ce document JSON est présenté dans la première illustration de la section 3.3.1 :

var document = {
  "name": "Vince Medvedev",
  "city": "Moscow",
  "threatments": {
    "surgeries": ["apedicectomy", "biopsy"],
    "radiation": ["gamma", "x-rays"],
    "physiotherapy": ["knee", "shoulder"]
  },
};

3.3.4 Structures identiques liées par des relations hiérarchiques

Exemples : structure des bureaux de vente, positions des personnes dans une structure MLM, base des débuts aux échecs.

Base de données des débuts. Vous pouvez utiliser une évaluation de la force du mouvement comme valeur de l'indice de nœud d'une globale. Dans ce cas, vous devrez sélectionner une branche ayant le poids le plus élevé pour déterminer le meilleur déplacement. Dans la globale, toutes les branches de chaque niveau seront triées en fonction de la force du mouvement.

La structure des bureaux de vente, des personnes dans une société MLM. Les noeuds peuvent stocker certaines valeurs de cache reflétant les caractéristiques de la sous-arborescence entière. Par exemple, les ventes de cette sous-arborescence particulière. Nous pouvons obtenir des informations exactes sur les réalisations de n'importe quelle branche à tout moment.

4. Situations où l'utilisation des globales est avantageuse

La première colonne contient une liste de cas où l'utilisation des globales vous donnera un avantage considérable en termes de performance, et la seconde - une liste de situations où elles simplifieront le développement ou le modèle de données.

<th>
  Commodité du traitement/de la présentation des données
</th>
<td>
  1.  Objets/instances avec un grand nombre de propriétés/instances non requises [et/ou imbriquées] 
    
  2.   Données sans schéma - de nouvelles propriétés peuvent souvent être ajoutées et d'anciennes supprimées
    
  3.   Vous devez créer une BD non standard.Bases de données de chemins et arbres de solutions 
    
  4.   Lorsque les chemins peuvent être représentés de manière pratique sous forme d'arbre
    
  5.  On doit supprimer les structures hiérarchiques sans utiliser la récursion  
    
Vitesse
1. Insertion [avec tri automatique à chaque niveau], [indexation par la clé primaire] 2. Suppression de sous-arbres 3. Objets comportant de nombreuses propriétés imbriquées auxquelles vous devez accéder individuellement 4. Structure hiérarchique avec possibilité de parcourir les branches enfant à partir de n'importe quelle branche, même inexistante 5. Parcours en profondeur de l'arbre
Clause de non-responsabilité: cet article et les commentaires le concernant reflètent uniquement mon opinion et n'ont rien à voir avec la position officielle de la société InterSystems.
0
0 107
Article Guillaume Rongier · Mars 29, 2022 9m read

Les globales, ces épées magiques destinées à stocker des données, existent depuis un certain temps, mais peu de gens savent les utiliser efficacement ou connaissent cette super-arme.

Si vous utilisez les globales pour des tâches où ils sont vraiment utiles, les résultats peuvent être étonnants, que ce soit en termes d'amélioration des performances ou de simplification spectaculaire de la solution globale (1, 2).

Les globales offrent une façon particulière de stocker et de traiter les données, qui est complètement différente des tableaux SQL. Ils ont été introduits en 1966 dans le langage de programmation M(UMPS), qui était initialement utilisé dans les bases de données médicales. Il est toujours utilisé de la même manière, mais a également été adopté par d'autres secteurs où la fiabilité et les hautes performances sont des priorités absolues : finance, commerce, etc.

Plus tard, M(UMPS) a évolué vers Caché ObjectScript (COS). COS a été développé par InterSystems comme un superset de M. Le langage original est toujours accepté par la communauté des développeurs et existe dans quelques implémentations. Il y a plusieurs signes d'activité sur le web : MUMPS Google group, Mumps User's group), effective ISO Standard, etc.

Les DBMS (systèmes de base de données) globales modernes prennent en charge les transactions, la journalisation, la réplication et le partitionnement. Cela signifie qu'ils peuvent être utilisés pour construire des systèmes distribués modernes, fiables et rapides.

Les globales ne vous limitent pas aux frontières du modèle relationnel. Ils vous donnent la liberté de créer des structures de données optimisées pour des tâches particulières. Pour de nombreuses applications, l'utilisation raisonnable des globales peut être une véritable solution miracle offrant des vitesses dont les développeurs d'applications relationnelles classiques ne peuvent que rêver.

Les globales, en tant que méthode de stockage des données, peuvent être utilisés dans de nombreux langages de programmation modernes, tant de haut niveau que de bas niveau. Par conséquent, cet article se concentrera spécifiquement sur les globales et non sur le langage dont ils sont issus.

2. Comment fonctionnent les globales

Commençons par comprendre comment fonctionnent les globales et quels sont leurs avantages. Les globales peuvent être vus sous différents angles. Dans cette partie de l'article, nous les verrons comme des arbres ou des stockages de données hiérarchiques.

En termes simples, une globale est une liste de données persistantes. Une liste qui est automatiquement sauvegardé sur le disque.

Il est difficile d'imaginer quelque chose de plus simple pour stocker des données. Dans le code du programme (écrit en langage COS/M), la seule différence avec un tableau associatif ordinaire est le symbole ^ qui précède leur nom.

Il n'est pas nécessaire de connaître SQL pour enregistrer des données dans une globale, car toutes les commandes nécessaires sont très simples et peuvent être apprises en une heure.

Commençons par l'exemple le plus simple, un arborescence mono niveau avec deux branches. Les exemples sont écrits en COS.

Set ^a("+7926X") = "John Sidorov"
Set ^a("+7916Y") = "Sergey Smith"

  Lorsque des données sont insérées dans une globale (la commande Set), 3 choses se produisent automatiquement:

  1. Sauvegarde des données sur le disque.
  2. Indexation. Ce qui est entre les parenthèses est un indice, ce qui est à droite du signe égal est la valeur du nœud.
  3. Tri. Les données sont triées par une clé. La prochaine traversée mettra "Sergey Smith" en première position, suivi de "John Sidorov". Lorsque l'on obtient une liste d'utilisateurs à partir d'une globale, la base de données ne passe pas de temps à trier. Vous pouvez en fait demander une liste triée à partir de n'importe quelle clé, même une clé inexistante (la sortie commencera à partir de la première clé réelle suivant celle-ci).

Toutes ces opérations sont effectuées à une vitesse incroyable. Sur mon système personnel (i5-3340, 16GB, HDD WD 1TB Blue), j'ai réussi à atteindre 1 050 000 insertions/sec en un seul processus. Sur les systèmes multi-cœurs, les vitesses peuvent atteindre des dizaines de millions of insertions/sec.

Bien sûr, la vitesse de saisie des données en elle-même ne dit pas grand-chose. Nous pouvons, par exemple, saisir des données dans des fichiers texte - c'est ainsi que le traitement fonctionne chez Visa, selon les rumeurs. Cependant, avec les globales, nous obtenons un stockage structuré et indexé avec lequel vous pouvez travailler en profitant de sa grande vitesse et de sa facilité d'utilisation.

  • La plus grande force des globales est la vitesse d'insertion de nouveaux nœuds dans ceux-ci.
  • Les données sont toujours indexées dans une globale. Les traversées en profondeur et les traversées arborescentes mono-niveau sont toujours très rapides.

Ajoutons quelques branches de deuxième et troisième niveau de la globale.

Set ^a("+7926X", "city") = "Moscow"
Set ^a("+7926X", "city", "street") = "Req Square"
Set ^a("+7926X", "age") = 25
Set ^a("+7916Y", "city") = "London"
Set ^a("+7916Y", "city", "street") = "Baker Street"
Set ^a("+7916Y", "age") = 36

Apparemment, vous pouvez construire des arbres à plusieurs niveaux en utilisant des globales. L'accès à n'importe quel nœud est presque instantané grâce à l'indexation automatique après chaque insertion. Les branches de l'arbre à n'importe quel niveau sont triées par une clé.

Comme vous pouvez le constater, les données peuvent être stockées à la fois sous forme de clés et de valeurs. La longueur combinée d'une clé (la somme des longueurs de tous les index) peut atteindre 511 octets et les valeurs peuvent atteindre une taille de 3,6 MB dans Caché. Le nombre de niveaux dans un arbre (nombre de dimensions) est plafonné à 31.

Une autre chose intéressante : vous pouvez construire un arbre sans définir les valeurs des nœuds de niveau supérieur.

Set ^b("a", "b", "c", "d") = 1
Set ^b("a", "b", "c", "e") = 2
Set ^b("a", "b", "f", "g") = 3

Les cercles vides sont des nœuds sans valeur.

Pour mieux comprendre les globales, comparons-les à d'autres arbres : les arbres de jardin et les arbres de noms de systèmes de fichiers.

Comparons les globales aux structures hiérarchiques les plus familières : les arbres réguliers qui poussent dans les jardins et les champs, ainsi que les systèmes de fichiers.

Comme nous pouvons le constater, les feuilles et les fruits ne poussent qu'à l'extrémité des branches des arbres ordinaires.
Systèmes de fichiers - les informations sont également stockées à l'extrémité des branches, également connues comme des noms de fichiers complets.

Et voici la structure de données d'une globale.

Differences:

  1. Nœuds internes: les informations d'une globale peuvent être stockées dans tous les nœuds et pas seulement aux extrémités des branches.
  2. Nœuds externes: les globales doivent avoir des extrémités de branche définies (extrémités avec des valeurs), ce qui n'est pas obligatoire pour les systèmes de fichiers et les arbres de jardin.

En ce qui concerne les noeuds internes, nous pouvons traiter la structure de globale comme un sur-ensemble des arbres de noms des systèmes de fichiers et des arbres de jardins. La structure de globale est donc plus flexible.

En général, une globale est un arbre structuré qui prend en charge la sauvegarde des données dans chaque nœud.

Afin de mieux comprendre le fonctionnement des globales, imaginons ce qui se passerait si les créateurs d'un système de fichiers utilisaient une approche identique à celle des globales pour stocker les informations ?

  1. Si le dernier fichier d'un dossier était supprimé, le dossier lui-même serait également supprimé, ainsi que tous les dossiers de niveau supérieur qui ne contenaient que ce dossier supprimé.
  2. Il n'y aurait pas besoin de dossiers du tout. Il y aurait des fichiers avec des sous-fichiers et des fichiers sans sous-fichiers. Si vous le comparez à un arbre normal, chaque branche deviendrait un fruit.


  3. Des choses comme README.txt ne seraient probablement plus nécessaires. Tout ce que vous avez besoin de dire sur le contenu d'un dossier pourrait être écrit dans le fichier du dossier lui-même. En général, les noms de fichiers ne peuvent pas être distingués des noms de dossiers (par exemple, /etc/readme peut être soit un dossier, soit un fichier), ce qui signifie que nous pourrions nous contenter d'exploiter des fichiers. 4. Les dossiers comportant des sous-dossiers et des fichiers pourraient être supprimés beaucoup plus rapidement. Il existe des articles sur le net qui racontent à quel point il est long et difficile de supprimer des millions de petits fichiers (1, 2, 3). En revanche, si vous créez un pseudo-système de fichiers basé sur une globale, cela prendra quelques secondes, voire des fractions de seconde. Lorsque j'ai testé la suppression de sous-arbres sur mon ordinateur personnel, j'ai réussi à supprimer 96 à 341 millions de nœuds d'un arbre à deux niveaux sur un disque dur (pas un disque dur externe). Et il convient de mentionner que nous parlons de la suppression d'une partie d'un arbre global, et non de la suppression d'un fichier entier contenant une globale.

La suppression des sous-arbres est encore un autre avantage des globaux : vous n'avez pas besoin de récursion pour cela. C'est incroyablement rapide. Dans notre arbre, cela pourrait être fait avec une commande Kill.

Kill ^a("+7926X")

Vous trouverez ci-dessous un petit tableau qui vous permettra de mieux comprendre les actions que vous pouvez effectuer sur une globale.

Commandes et fonctions clés liées aux globales dans COS
Set Paramétrage (initialisation) des branches jusqu'à un noeud (si non défini) et valeur du noeud
Merge Copie d'un sous-arbre
Kill Suppression d'un sous-arbre
ZKill Suppression de la valeur d'un nœud particulier. Le sous-arbre issu de ce noeud n'est pas affecté
$Query Traversée complète et approfondie de l'arbre
$Order Renvoie l'indice suivant au même niveau
$Data Vérifier si un nœud est défini
$Increment Incrémentation atomique de la valeur d'un nœud afin d'éviter la lecture et l'écriture pour ACID. La dernière recommandation est d'utiliser $Sequence à la place.

Merci de votre attention, je serai heureux de répondre à vos questions.

Démenti: Cet article reflète l'opinion privée de l'auteur et n'a aucun rapport avec la position officielle d'InterSystems.

0
0 133