web-dev-qa-db-fra.com

Comment gérer Vue 2 utilisation de la mémoire pour les données volumineuses (~ 50 000 objets)

J'essaie d'implémenter une vue de table pour de grandes collections d'objets semi-complexes sur Vue 2. L'idée est essentiellement de collecter entre 50 000 et 100 000 lignes de DB dans le cache JS , qui est ensuite analysée dynamiquement pour créer une vue de tableau avec des filtres en temps réel (recherche de texte). Chaque ligne du tableau est commutable, ce qui signifie que cliquer sur la ligne fait passer la ligne en mode édition, ce qui permet une édition de type Excel pour ce champ/cellule spécifique.

Chaque objet a environ ~ 100-150 champs/propriétés, mais seule une certaine quantité d'entre eux est affichée à un moment donné dans le tableau (les colonnes du tableau peuvent être basculées en temps réel). Pour les grands ensembles de données, il semble que la base de données pousse environ 10 à 100 Mo de données JSON, ce qui est acceptable dans ce cas d'utilisation. En termes de rendu, les performances ne sont pas un problème - les filtres fonctionnent assez rapidement et seule une quantité limitée de résultats est rendue dans DOM.

Tout fonctionne déjà, les filtres, répertoriant ~ 100 lignes par rapport aux filtres (+ "afficher 100 autres" -mécanisme, etc.), mais j'ai atteint la limite de mémoire lorsque j'ai chargé environ 8000 objets dans le tableau. Cela semble réserver 2 gigaoctets de RAM, qui après Chrome arrête d'exécuter le code JS tous ensemble (même si étrangement je ne reçois aucun avertissement/erreur).

J'ai comparé l'utilisation de la mémoire pour les lignes et il semble que ~ 1000 lignes réservent environ 300 Mo de mémoire. Ceci est très probablement réservé par Vue observateurs de réactivité.

Trois questions:

  1. Existe-t-il un moyen de basculer la réactivité pour des objets de liste de tableaux spécifiques (par index ou autre), de sorte que les objets dans le tableau lui-même ne soient pas observés/non modifiables sauf s'ils sont spécifiquement appelés à devenir mutables (par exemple, lorsque l'utilisateur clique sur la ligne, ce qui permet mode)?
  2. Comment mettriez-vous en œuvre la gestion de grands ensembles de données pour Vue, car la réactivité semble goulot d'étrangler l'utilisation de la mémoire? Veuillez ne pas suggérer de "limiter les résultats dans le backend", car ce n'est pas la solution que je recherche ici (même si je dois créer un filtrage en deux parties, une pour récupérer un ensemble de données initial plus petit et un pour le filtrage en temps réel). Fondamentalement, j'essaie de repousser les limites de la "fin de mémoire" de 8 000 à> 80 000 en repensant l'architecture des données avec Vue. Le seul problème est-il que le jeu de données soit stocké dans les variables de données de Vue comme réactif?
  3. Une idée que j'ai est de transformer cet ensemble de données "items" en non observable/non réactif avec Object.freeze ou une approche similaire et d'avoir un tableau pour rendre deux jeux de données: un pour non réactif et un pour ceux qui sont actuellement dans mode édition (qui serait poussé vers le jeu de données "editableItems" lorsque la ligne est cliquée) ... toutes suggestions ici (quelque chose de plus simple, pour que je puisse tout gérer dans un tableau?)

J'ai fait une application similaire sur Angular 1, et il a très bien géré 50 000 lignes, donc je suis sûr que cela devrait être faisable dans Vue 2 également ... devrait être juste une question de trouver un moyen de gérer la réactivité.

12
Janne

Modifier le 12.03.2019 - conseils supplémentaires à la fin de cette réponse

Cela fait un moment que je n'ai pas posé cette question et j'ai finalement pu optimiser cette partie de mon projet. Je voudrais donner quelques conseils à toute personne ayant ces problèmes de performances et/ou de mémoire.

La documentation de Vue ne l'a jamais vraiment expliqué, mais comme l'a souligné Andrey, vous POUVEZ utiliser l'objet composant comme stockage de données pour vos listes d'objets d'objets personnalisés. Après tout, c'est juste un objet javascript normal.

Après l'optimisation, la configuration de mes composants de liste ressemble un peu à ceci:

module.exports = {
    items: [],
    mixins: [sharedUtils],
    data: function() {
        return {
            columns: {
                all: []
    etc... Lot's of data & methods

Le tableau d'éléments est rempli de milliers d'objets complexes (environ 80 Mo de données, 6 Mo compressés) que je traite comme non réactif. Cela s'est avéré moins problématique que je ne l'aurais pensé - Au lieu d'utiliser v-for directement contre des éléments, j'utilisais déjà une structure dans laquelle je déclenchais le filtrage de ce tableau chaque fois que l'utilisateur cliquait sur un bouton de filtre et/ou une chaîne entrée - filtrage (tel que le nom). Fondamentalement, cette méthode "processFilters" passe par un tableau d'éléments non réactifs et renvoie des éléments filtrés, qui sont stockés dans un contexte de données. Ainsi, il devient automatiquement réactif au fur et à mesure de sa mutation.

<tr v-for="item in filteredItems" 

De cette façon, tous les éléments contenus dans les éléments filtrés restent réactifs, mais perdent également de la réactivité lorsqu'ils sont filtrés, ce qui permet d'économiser beaucoup de mémoire. Un énorme 1200 Mo est devenu 400 Mo, ce qui était exactement ce que je cherchais. Intelligent!

Il y a peu de problèmes à résoudre. Étant donné que les éléments n'existent pas dans le contexte de données, vous ne pouvez pas les utiliser directement dans le modèle. Cela signifie qu'au lieu d'écrire ...

<div v-if="items.length > 0 && everythingElseIsReady">

... J'ai dû stocker la longueur du tableau des éléments pour séparer les accessoires de données. Cela aurait également pu être corrigé avec la valeur calculée, mais j'aime garder ces propriétés existantes.

Abandonner la réactivité de votre tableau de données principal n'est pas une si mauvaise chose après tout - La partie la plus importante est de comprendre que les modifications apportées directement aux éléments de ce tableau de base ne déclenchent jamais de modifications de l'interface utilisateur et/ou sous-composants (douh). Cela ne devrait pas être un problème tant que vous séparez votre code de manière à avoir un "conteneur de données caché" qui contient tous les résultats du backend et que vous ayez un tableau de présentation plus petit (filtré) de ce grand conteneur. En utilisant une bonne architecture REST, vous devriez déjà être prêt à utiliser un stockage de données non réactif, tant que vous vous souvenez de vérifier qu'après l'enregistrement de l'élément dans le stockage de données non réactif, la dernière révision a également été mise à jour.

De plus, j'ai été déconcerté par le peu d'importance en termes de performances du nombre de micro-composants contre des centaines de rangées. Le rendu prend évidemment un coup, mais même si je devais passer des gros accessoires des milliers de fois (comme j'ai des milliers d'instances de cellules d'entrée), il ne semblait pas toucher la mémoire. Un de ce type d'objets est mon objet global de traduction-clé/paire de valeurs, ayant plus de 20 000 lignes de chaînes traduites ... mais cela n'avait toujours pas d'importance. Cela a du sens, car Javascript utilise des références d'objet et Vue Core semble être correctement codé, donc tant que vous utilisez de tels objets de configuration comme accessoires, vous vous référez simplement à des milliers d'objets vers le même base de données.

Enfin, je dirais commencer à devenir fou avec des objets CRUD complexes sans craindre d'atteindre la limite de mémoire!

Un grand merci à Andrey Popov pour avoir donné à Nudge la bonne direction!

Conseils (12.03.2019)

Depuis un certain temps et comme j'ai continué à créer des interfaces utilisateur avec de grands ensembles de données complexes, j'ai décidé de laisser quelques conseils d'idées courts.

  1. Réfléchissez à la façon dont vous gérez vos enregistrements principaux (c.-à-d. Personnes ou produits) par rapport aux enregistrements associés (sous-objets/objets relationnels). Essayez de limiter la quantité de données injectées pour les sous-composants, car vous pourriez représenter plusieurs fois le même sous-objet pour différents enregistrements principaux. Le problème est qu'il est possible que ces objets ne soient pas réellement des objets de référence!

Considérez la situation où vous avez un objet-personne, qui contient un objet-ville. Plusieurs personnes vivent dans la même ville, mais lorsque vous récupérez les données JSON du backend, vous êtes sûr que ces objets de ville dupliqués sont en fait une seule et même ville (objet de ville partagé/référencé entre les personnes), ou plusieurs représentations d'un objet similaire (avec les données étant exactement les mêmes, mais sous le capot, chacune étant une instance individuelle/un objet unique). Disons que vous avez 50 000 personnes, chacune contenant le même sous-objet/propriété "ville": {id: 4, nom: "Megatown"}, venez-vous de récupérer 50 000 instances de ville individuelles au lieu d'une seule? Est-ce que person1.city === person2.city, ou ont-ils simplement la même apparence et sont-ils toujours deux objets différents?

Si vous ne savez pas si vous vous référez à un objet ville partagé ou si vous utilisez des dizaines d'instances de sous-objets similaires, vous pouvez simplement y faire référence dans votre composant liste de personnes. Votre personne contient l'identifiant de la ville, alors récupérez la liste des villes avec la méthode REST distincte (getCities) et effectuez le couplage au niveau de l'interface utilisateur. De cette façon, vous n'avez qu'une seule liste de villes, et vous pouvez résoudre la ville à partir de cette liste et l'injecter à la personne, faisant ainsi référence à une seule ville. Vous pouvez également résoudre la ville à partir de la liste et la transmettre en tant que propriété à votre composant personnel.

Assurez-vous également de considérer quel est le but du sous-objet. En avez-vous besoin pour être réactif, ou est-ce statique? Afin d'économiser beaucoup de mémoire, vous pouvez simplement dire "person.city = city", qui sera injecté pour chaque composant personne, mais s'il doit être réactif, vous devez utiliser Vue.set -method .. et rappelez-vous que si chaque ville doit être sa propre instance (afin que chaque personne ait un objet-ville similaire, mais que les propriétés doivent être modifiables par personne), vous devez vous assurer que vous n'utilisez pas d'objet référencé! Ainsi, vous devrez probablement cloner l'objet ville, ce qui consommera de la mémoire du navigateur.

  1. Votre micro-composant peut contenir des états d'affichage distincts pour l'état en lecture seule et l'état de l'éditeur. C'est assez courant. Pourtant, vous créez en fait une instance de ce micro-composant à chaque fois, initialisant ainsi ce composant des milliers de fois.

Pensez à une situation où vous avez une feuille de calcul de type Excel avec un tableau et des lignes de tableau. Chaque cellule contient votre composant "my-input" personnalisé, qui prend la propriété "en lecture seule" de votre mise en page. Si l'interface utilisateur est en lecture seule, vous affichez uniquement la partie d'étiquette à l'intérieur de ce composant my-input, mais sinon, vous affichez la balise d'entrée avec certaines conditions spéciales (telles que des entrées différentes pour l'heure, le nombre, le texte, zone de texte, balise de sélection, etc.). Supposons maintenant que vous avez 100 lignes avec 20 colonnes, donc vous êtes en train d'initialiser 2000 my-input-components. Maintenant, la question est - ce qui pourrait être amélioré (en termes de performances)?

Eh bien, vous pouvez séparer readonly-label de my-input-component à votre liste, afin d'afficher soit readonly-version (label) OR you you display the editable my-input- De cette façon, vous avez la condition v-if, qui garantit que ces 2000 micro-composants ne seront pas initialisés à moins que vous n'ayez spécifiquement demandé de les initialiser (en raison de la ligne ou de la disposition entière passant de readonly -> editable -state) ... Vous devinez probablement l'ampleur de l'impact en termes de mémoire pour le navigateur, lorsque Vue n'a pas besoin de créer 2000 composants.

Si vous faites face à un chargement très lent de votre page, il se peut que ce ne soit pas du tout VUE. Vérifiez la quantité de balises HTML rendues à votre HTML. HTML fonctionne plutôt mal lorsque vous avez de grandes quantités de balises. L'une des façons les plus simples de le démontrer est de répéter 100 fois la balise de sélection avec 2000 options, ou d'avoir une seule balise de sélection d'option 20000. De la même manière, vous pourriez déborder le nombre de balises html en ayant beaucoup de micro-composants avec divs d'emballage inutiles, etc. ... Moins vous avez de profondeur et moins de balises, moins le navigateur et le CPU nécessitent de performances de rendu.

Essayez d'apprendre une bonne architecture de balises HTML via des exemples. Par exemple, vous pouvez étudier la façon dont la vue du tableau de bord des services Trello a été programmée. C'est une représentation assez simple et belle d'un service plutôt semi-complexe, avec un nombre minimal de sous-divisions.


Il existe de nombreuses façons d'améliorer la gestion de la mémoire, mais je dirais que les plus importantes concernent la séparation des objets "cachés" des objets visibles, comme décrit dans ma réponse d'origine. La deuxième partie consiste à comprendre la différence ou les objets instanciés par rapport aux objets référencés. La troisième consiste à limiter la quantité de données inutiles passant entre les objets.

Personnellement, je n'ai pas essayé cela, mais il existe un composant Vue-virtual-scroller qui gère n'importe quelle quantité de données en étant simplement un wrapper pour des quantités apparemment infinies de données. Découvrez le concept @ https://github.com/Akryum/vue-virtual-scroller , et faites-moi savoir si cela a résolu le problème pour vous.

J'espère que ces directives donnent quelques idées pour optimiser vos composants. Ne perdez jamais espoir, il y a toujours place à l'amélioration!

15
Janne

D'après tout ce que j'ai lu, je constate que vous n'avez tout simplement pas besoin de réactivité pour ces données, car:

Chaque ligne dans le tableau est commutable, ce qui signifie que cliquer sur la ligne change la ligne en mode édition, ce qui permet une édition de type Excel pour ce champ/cellule spécifique

Cela signifie que les lignes ne sont pas modifiables et que les données ne peuvent pas être mutées sans interaction de l'utilisateur.

Chaque objet a environ ~ 100-150 champs/propriétés, mais seule une certaine quantité d'entre eux est affichée à un moment donné dans le tableau (les colonnes du tableau peuvent être basculées en temps réel).

Vous gardez les champs réactifs mais ne les affichez pas.


Et maintenant vos questions

Existe-t-il un moyen de basculer la réactivité pour des objets de liste de tableaux spécifiques (par index ou autre), de sorte que les objets dans le tableau lui-même ne soient pas observés/non modifiables sauf s'ils sont spécifiquement appelés à devenir mutables (par exemple, lorsque l'utilisateur clique sur la ligne, ce qui permet mode)?

S'il y a un seul élément qui peut être modifié à la fois, alors pourquoi tout garder réactif? Vous pouvez facilement utiliser une seule variable pour écouter ces changements.

Comment mettriez-vous en œuvre la gestion de grands ensembles de données pour Vue, car la réactivité semble goulot d'étrangler l'utilisation de la mémoire?

Tout est question de mise en œuvre - vous vous retrouvez rarement dans une situation où vous avez besoin d'une énorme liste d'éléments pour être réactif. Plus vous avez d'éléments, plus d'événements doivent se produire pour utiliser la réactivité. Si vous avez 50 000 éléments et qu'il n'y a que quelques événements à muter (comme la modification manuelle des données par l'utilisateur), vous pouvez facilement écouter ces événements et créer la réactivité manuellement plutôt que de laisser Vue gérer toutes les données. Vous pouvez vérifier Vuex qui peut vous faciliter la vie :)

Une idée que j'ai est de transformer cet ensemble de données "items" en non observable/non réactif avec Object.freeze ou une approche similaire et d'avoir un tableau pour rendre deux jeux de données: un pour non réactif et un pour ceux qui sont actuellement dans mode édition (qui serait poussé vers le jeu de données "editableItems" lorsque la ligne est cliquée)

C'est en quelque sorte aller dans la bonne direction, mais il n'est pas nécessaire de prendre en charge deux tableaux. Imaginez utiliser quelque chose comme ça:

data: function() {
    return {
        editingItem: {}
        // when editing is enabled bind the input fields to this item
    }
},
created: function() {
    this.items = [] // your items, can be used in markdown in the loop, but won't be reactive!
},
watch: {
    editingItem: function(data) {
        // this method will be called whenever user edits the input fields
        // here you can do whatever you want
        // like get item's id, find it in the array and update it's properties
        // something like manual reactivity ;)
    }
}
4
Andrey Popov