Je suis d'accord avec la notion générale d'autres réponses selon lesquelles il s'agit d'un borderline problème relationnel.
La clé des modèles de données MongoDB est la lourdeur en écriture, mais cela peut être délicat pour ce cas d'utilisation, principalement en raison de la comptabilité qui serait nécessaire si vous vouliez lier directement les utilisateurs aux éléments (un changement dans un groupe suivi de lots des utilisateurs subiraient un grand nombre d'écritures, et vous avez besoin d'un certain travailleur pour le faire).
Examinons si le modèle de lecture intensive est inapplicable ici ou si nous procédons à une optimisation prématurée.
L'approche intensive en lecture
Votre principale préoccupation est le cas d'utilisation suivant :
un vrai problème de performances pourrait être lorsque je veux obtenir tous les groupes qu'un utilisateur suit pour un élément spécifique [...] parce que je dois alors trouver tous les groupes que l'utilisateur suit, et à partir de là, trouver tous les item_groups avec le group_id
$in
et l'identifiant de l'article.
Disséquons ceci :
-
Obtenir tous les groupes suivis par l'utilisateur
C'est une requête simple :
db.followers.find({userId : userId})
. Nous allons avoir besoin d'un index suruserId
ce qui rendra le temps d'exécution de cette opération O (log n), ou extrêmement rapide même pour un grand n. -
à partir de là, trouvez tous les items_groups avec le group_id
$in
et l'identifiant de l'articleMaintenant, c'est la partie la plus délicate. Supposons un instant qu'il est peu probable que des éléments fassent partie d'un grand nombre de groupes. Puis un index composé
{ itemId, groupId }
fonctionnerait mieux, car nous pouvons réduire considérablement l'ensemble de candidats via le premier critère - si un élément est partagé dans seulement 800 groupes et que l'utilisateur suit 220 groupes, mongodb n'a besoin que de trouver l'intersection de ceux-ci, ce qui est relativement facile car les deux les ensembles sont petits.
Nous devrons cependant aller plus loin :
La structure de vos données est probablement celle d'un réseau complexe . Les réseaux complexes se présentent sous plusieurs formes, mais il est logique de supposer que votre graphique suiveur est presque sans échelle, ce qui est également à peu près le pire des cas. Dans un réseau sans échelle, un très petit nombre de nœuds (célébrités, super bowl, Wikipédia) attirent beaucoup "d'attention" (c'est-à-dire ont de nombreuses connexions), tandis qu'un nombre beaucoup plus grand de nœuds ont du mal à obtenir la même quantité d'attention combiné .
Les petits nœuds ne sont pas une raison de s'inquiéter , les requêtes ci-dessus, y compris les allers-retours vers la base de données, sont de l'ordre de 2 ms sur ma machine de développement sur un ensemble de données avec des dizaines de millions de connexions et> 5 Go de données. Maintenant, cet ensemble de données n'est pas énorme, mais quelle que soit la technologie que vous choisissez, il sera lié à la RAM car les index doivent être dans la RAM dans tous les cas (la localité et la séparabilité des données dans les réseaux sont généralement médiocres), et la taille d'intersection définie est petit par définition. En d'autres termes :ce régime est dominé par des goulots d'étranglement matériels.
Qu'en est-il des supernœuds ? cependant ?
Comme ce serait une conjecture et que je m'intéresse beaucoup aux modèles de réseau, j'ai pris la liberté de mettre en œuvre un outil de réseau considérablement simplifié basé sur votre modèle de données pour effectuer des mesures. (Désolé, c'est en C#, mais générer des réseaux bien structurés est déjà assez difficile dans le langage que je maîtrise le mieux...).
Lors de l'interrogation des super-nœuds, j'obtiens des résultats dans la plage des sommets de 7 ms (c'est-à-dire sur 12 millions d'entrées dans une base de données de 1,3 Go, le plus grand groupe contenant 133 000 éléments et un utilisateur qui suit 143 groupes.)
L'hypothèse dans ce code est que le nombre de groupes suivis par un utilisateur n'est pas énorme, mais cela semble raisonnable ici. Si ce n'est pas le cas, j'opterais pour l'approche intensive en écriture.
N'hésitez pas à jouer avec le code. Malheureusement, il faudra un peu d'optimisation si vous voulez essayer cela avec plus de quelques Go de données, car ce n'est tout simplement pas optimisé et fait des calculs très inefficaces ici et là (en particulier le mélange aléatoire pondéré en bêta pourrait être amélioré ).
En d'autres termes :je ne m'inquiéterais pas encore des performances de l'approche à lecture intensive. . Le problème n'est souvent pas tant que le nombre d'utilisateurs augmente, mais plutôt que les utilisateurs utilisent le système de manière inattendue.
L'approche d'écriture intensive
L'approche alternative consiste probablement à inverser l'ordre des liens :
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Il s'agit probablement du modèle de données le plus évolutif, mais je ne le ferais pas à moins que nous ne parlions d'ÉNORMES quantités de données où le partage est une exigence clé. La principale différence ici est que nous pouvons désormais compartimenter efficacement les données en utilisant l'ID utilisateur dans le cadre de la clé de partition. Cela permet de paralléliser les requêtes, de partitionner efficacement et d'améliorer la localité des données dans les scénarios multi-centres de données.
Cela pourrait être testé avec une version plus élaborée du banc d'essai, mais je n'ai pas encore trouvé le temps, et franchement, je pense que c'est exagéré pour la plupart des applications.