Dans quelle mesure Meteor peut-il être efficace tout en partageant une énorme collection parmi de nombreux clients?

Imaginez le cas suivant:

Qu’est-ce qui va arriver:

  • Toutes les collections Meteor.Collection sur les clients seront mises à jour, c’est-à-dire l’insertion transmise à tous (un message d’insertion envoyé à 1000 clients)

Quel est le coût en terme de CPU du serveur pour déterminer quel client doit être mis à jour?

Est-il exact que seule la valeur insérée sera transmise aux clients, et non à la liste complète?

Comment ça marche dans la vraie vie? Existe-t-il des repères ou des expériences de cette ampleur?

La réponse courte est que seules les nouvelles données sont envoyées vers le bas. Voici comment cela fonctionne.

Il existe trois parties importantes du serveur Meteor qui gèrent les abonnements: la fonction de publication , qui définit la logique des données fournies par l’abonnement; le pilote Mongo , qui surveille les modifications de la firebase database; et la boîte de fusion , qui combine tous les abonnements actifs d’un client et les envoie au client via le réseau.

Publier des fonctions

Chaque fois qu’un client Meteor s’abonne à une collection, le serveur exécute une fonction de publication . Le travail de la fonction de publication consiste à déterminer l’ensemble des documents que son client doit avoir et à envoyer chaque propriété du document dans la zone de fusion. Il s’exécute une fois pour chaque nouveau client abonné. Vous pouvez placer n’importe quel this.userId JavaScript de votre choix dans la fonction de publication, comme un contrôle d’access arbitrairement complexe utilisant this.userId . La fonction de publication envoie des données dans la zone de fusion en appelant this.added , this.changed et this.removed . Consultez la documentation de publication complète pour plus de détails.

Cependant, la plupart des fonctions de publication ne sont pas obligées d’utiliser l’API de bas niveau added , changed et removed . Si une fonction de publication renvoie un curseur Mongo, le serveur Meteor connecte automatiquement la sortie du pilote Mongo ( insert , update et rappels removed ) à l’entrée de la boîte de fusion ( this.added , this.changed et this.removed ). Il est très intéressant de pouvoir effectuer toutes les vérifications d’autorisation dans une fonction de publication, puis de connecter directement le pilote de firebase database à la boîte de fusion sans aucun code utilisateur. Et lorsque la publication automatique est activée, même ce petit bit est masqué: le serveur configure automatiquement une requête pour tous les documents de chaque collection et les envoie dans la boîte de fusion.

D’autre part, vous n’êtes pas limité à la publication de requêtes de firebase database. Par exemple, vous pouvez écrire une fonction de publication qui lit une position GPS à partir d’un périphérique dans Meteor.setInterval ou interroge une API REST héritée à partir d’un autre service Web. Dans ces cas, vous émettez des modifications dans la boîte de fusion en appelant l’API DDP added , changed et removed bas niveau.

Le pilote Mongo

Le travail du pilote Mongo consiste à surveiller la firebase database Mongo pour voir les modifications apscopes aux requêtes en direct. Ces requêtes s’exécutent en continu et renvoient les mises à jour à mesure que les résultats changent en appelant, en removed et en modifiant les rappels.

Mongo n’est pas une firebase database en temps réel. Donc, le conducteur interroge. Il conserve une copie en mémoire du dernier résultat de la requête pour chaque requête en direct active. À chaque cycle d’interrogation, il compare le nouveau résultat au résultat enregistré précédent, en calculant l’ensemble minimal d’événements added , removed et changed qui décrivent la différence. Si plusieurs appelants enregistrent des rappels pour la même requête en direct, le pilote surveille uniquement une copie de la requête, appelant chaque rappel enregistré avec le même résultat.

Chaque fois que le serveur met à jour une collection, le pilote recalcule chaque requête en direct sur cette collection (les futures versions de Meteor exposeront une API de mise à l’échelle pour limiter les requêtes en temps réel lors de la mise à jour). intercepter les mises à jour de la firebase database hors bande qui ont contourné le serveur Meteor.

La boîte de fusion

La tâche de la boîte de fusion consiste à combiner les résultats (appels added , changed et removed ) de toutes les fonctions de publication actives d’un client dans un seul stream de données. Il existe une boîte de fusion pour chaque client connecté. Il contient une copie complète du cache minimongo du client.

Dans votre exemple avec un seul abonnement, la boîte de fusion est essentiellement une passerelle. Mais une application plus complexe peut avoir plusieurs abonnements qui peuvent se chevaucher. Si deux abonnements définissent tous deux le même atsortingbut sur le même document, la boîte de fusion décide quelle valeur est prioritaire et envoie uniquement le client. Nous n’avons pas encore exposé l’API pour définir la priorité d’abonnement. Pour l’instant, la priorité est déterminée par l’ordre dans lequel le client s’abonne aux ensembles de données. Le premier abonnement d’un client a la priorité la plus élevée, le deuxième abonnement est le plus élevé, etc.

Comme la boîte de fusion contient l’état du client, elle peut envoyer le minimum de données pour garder chaque client à jour, quelle que soit la fonction de publication.

Que se passe-t-il sur une mise à jour

Alors maintenant, nous avons préparé le terrain pour votre scénario.

Nous avons 1 000 clients connectés. Chacun est abonné à la même requête Mongo en direct ( Somestuff.find({}) ). Comme la requête est la même pour chaque client, le pilote n’exécute qu’une seule requête en direct. Il existe 1 000 boîtes de fusion actives. Et la fonction de publication de chaque client a enregistré une requête added , changed et removed sur cette requête en direct qui alimente l’une des zones de fusion. Rien d’autre n’est connecté aux boîtes de fusion.

D’abord le pilote Mongo. Lorsqu’un des clients insère un nouveau document dans Somestuff , cela déclenche un recalcul. Le pilote Mongo réexécute la requête pour tous les documents dans Somestuff , compare le résultat au résultat précédent dans la mémoire, constate qu’il existe un nouveau document et appelle chacun des 1 000 rappels d’ insert enregistrés.

Ensuite, les fonctions de publication. Il se passe très peu de choses ici: chacun des 1 000 rappels d’ insert des données dans la boîte de fusion en appelant added .

Enfin, chaque case de fusion vérifie ces nouveaux atsortingbuts sur sa copie en mémoire cache du cache de son client. Dans chaque cas, il trouve que les valeurs ne sont pas encore sur le client et ne suivent pas une valeur existante. Ainsi, la boîte de fusion émet un message DDP DATA sur la connexion SockJS à son client et met à jour sa copie en mémoire côté serveur.

Le coût total de l’UC est le coût de la différenciation d’une requête Mongo, plus le coût de 1 000 zones de fusion vérifiant l’état de leurs clients et la construction d’une nouvelle charge de message DDP. La seule donnée qui circule sur le fil est un object JSON unique envoyé à chacun des 1 000 clients, correspondant au nouveau document de la firebase database, plus un message RPC au serveur provenant du client qui a effectué l’insertion d’origine.

Optimisations

Voici ce que nous avons certainement prévu.

  • Pilote Mongo plus efficace. Nous avons optimisé le pilote dans 0.5.1 pour ne lancer qu’un seul observateur par requête distincte.

  • Tous les changements de firebase database ne devraient pas déclencher un recalcul d’une requête. Nous pouvons apporter des améliorations automatisées, mais la meilleure approche est une API qui permet au développeur de spécifier quelles requêtes doivent être réexécutées. Par exemple, il est évident pour un développeur que l’insertion d’un message dans un salon de discussion ne doit pas invalider une requête en direct pour les messages dans une deuxième salle.

  • Le pilote Mongo, la fonction de publication et la boîte de fusion ne doivent pas nécessairement être exécutés dans le même processus, ni même sur le même ordinateur. Certaines applications exécutent des requêtes en direct complexes et ont besoin de davantage de CPU pour regarder la firebase database. D’autres n’ont que peu de requêtes distinctes (imaginez un moteur de blog), mais peut-être de nombreux clients connectés – ceux-ci nécessitent davantage de CPU pour les boîtes de fusion. La séparation de ces composants nous permettra de mettre à l’échelle chaque pièce indépendamment.

  • De nombreuses bases de données prennent en charge les déclencheurs qui se déclenchent lorsqu’une ligne est mise à jour et fournissent les anciennes et les nouvelles lignes. Avec cette fonctionnalité, un pilote de firebase database peut enregistrer un déclencheur au lieu d’interroger les modifications.

D’après mon expérience, l’utilisation de nombreux clients avec le partage d’une énorme collection dans Meteor est essentiellement irréalisable, à compter de la version 0.7.0.1. Je vais essayer d’expliquer pourquoi.

Comme décrit dans le message ci-dessus et également dans https://github.com/meteor/meteor/issues/1821 , le serveur meteor doit conserver une copie des données publiées pour chaque client dans la boîte de fusion . C’est ce qui permet à la magie Meteor de se produire, mais cela permet également de conserver de manière répétée les grandes bases de données partagées dans le processus de noeud. Même en utilisant une optimisation possible pour des collections statiques telles que ( Y a-t-il un moyen de dire meteor qu’une collection est statique (ne changera jamais)? ), Nous avons rencontré un énorme problème avec l’utilisation du CPU et de la mémoire du processus.

Dans notre cas, nous publiions une collection de 15 000 documents complètement statiques pour chaque client. Le problème est que la copie de ces documents dans la boîte de fusion d’un client (en mémoire) lors de la connexion a fondamentalement amené le processus Node à 100% du processeur pendant presque une seconde, ce qui a entraîné une utilisation supplémentaire importante de la mémoire. Cela est insortingnsèquement incontrôlable, car tout client connecté mettra le serveur à genoux (et des connexions simultanées se bloqueront mutuellement) et l’utilisation de la mémoire augmentera linéairement dans le nombre de clients. Dans notre cas, chaque client entraînait une consommation supplémentaire d’ environ 60 Mo de mémoire, même si les données brutes transférées n’étaient que d’environ 5 Mo.

Dans notre cas, comme la collection était statique, nous avons résolu ce problème en envoyant tous les documents sous la forme d’un fichier .json , qui était compressé par nginx, et chargé dans une collection anonyme, ce qui entraînait un transfert de données d’environ CPU supplémentaire ou mémoire dans le processus de noeud et un temps de chargement beaucoup plus rapide. Toutes les opérations sur cette collection ont été effectuées en utilisant _id s de publications beaucoup plus petites sur le serveur, permettant de conserver la plupart des avantages de Meteor. Cela a permis à l’application de s’adapter à beaucoup plus de clients. De plus, notre application étant principalement en lecture seule, nous avons encore amélioré l’évolutivité en exécutant plusieurs instances de Meteor derrière nginx avec un équilibrage de charge (bien qu’avec un seul Mongo), chaque instance de nœud étant à thread unique.

Cependant, le problème du partage de grandes collections inscriptibles entre plusieurs clients est un problème technique qui doit être résolu par Meteor. Il y a probablement un meilleur moyen que de garder une copie de tout pour chaque client, mais cela nécessite une reflection sérieuse en tant que problème de systèmes dissortingbués. Les problèmes actuels liés à l’utilisation massive du processeur et de la mémoire ne seront tout simplement pas adaptés.

L’expérience que vous pouvez utiliser pour répondre à cette question:

  1. Installez un meteor de test: meteor create --example todos
  2. Exécutez-le sous l’inspecteur Webkit (WKI).
  3. Examiner le contenu des messages XHR se déplaçant sur le fil.
  4. Observez que la collection entière n’est pas déplacée sur le câble.

Pour obtenir des conseils sur l’utilisation de WKI, consultez cet article . C’est un peu dépassé, mais surtout valable, surtout pour cette question.

Cela fait encore un an maintenant et donc je pense avant la connaissance “Meteor 1.0”, donc les choses ont peut-être changé à nouveau? Je suis toujours en train de regarder ça. http://meteorhacks.com/does-meteor-scale.html conduit à un “Comment faire évoluer Meteor?” article http://meteorhacks.com/how-to-scale-meteor.html