2010-11-15 17 views
11

Supposons que vous ayez un grand nombre d'utilisateurs (M) et un grand nombre de documents (N) et que vous souhaitiez que chaque utilisateur puisse marquer chaque document comme lu ou non lu (comme n'importe quel système de messagerie). Quelle est la meilleure façon de représenter cela dans MongoDB? Ou toute autre base de données de documents?MongoDB/NOSQL: meilleure approche pour gérer l'état de lecture/non-lu sur les messages

Il y a plusieurs questions sur StackOverflow poser cette question pour les bases de données relationnelles, mais je ne l'ai pas avec des recommandations pour les bases de données de documents:

What's the most efficient way to remember read/unread status across multiple items?

Implementing an efficient system of "unread comments" counters

En général, les réponses comportent une liste de table tout ce qu'un utilisateur a lu: (c'est-à-dire les tuples de l'ID utilisateur, ID du document) avec quelques optimisations possibles pour une date de coupure permettant à toute la lecture d'effacer la base de données et de recommencer '. Donc, experts MongoDB/NOSQL, quelles approches avez-vous vu dans la pratique à ce problème et comment ont-ils performé?

Répondre

4
{ 
_id: messagePrefs_uniqueId, 
type: 'prefs', 
timestamp: unix_timestamp 
ownerId: receipientId, 
messageId: messageId, 
read: true/false, 
} 

{ 
_id: message_uniqueId, 
timestamp: unix_timestamp 
type: 'message', 
contents: 'this is the message', 
senderId: senderId, 
recipients: [receipientId1,receipientId2] 
} 

Supposons que vous avez 3 messages que vous souhaitez récupérer les préférences pour, vous pouvez les obtenir par quelque chose comme:

db.messages.find({ 
messageId : { $in : [messageId1,messageId2,messageId3]}, 
ownerId: receipientId, 
type:'prefs' 
}) 

Si tout ce que vous avez besoin est lu/non lu, vous pouvez l'utiliser avec les capacités de upsert de MongoDB , donc vous ne créez pas de préférences pour chaque message à moins que l'utilisateur ne le lise réellement, alors fondamentalement vous créez l'objet prefs avec votre propre identifiant unique et l'augmentez dans MongoDB. Si vous voulez plus de flexibilité (comme les tags ou les dossiers), vous voudrez probablement faire le pref pour chaque destinataire du message. Par exemple, vous pouvez ajouter:

tags: ['inbox','tech stuff'] 

aux prefs objet, puis d'obtenir toutes les prefs de tous les messages marqués avec « stuff tech » vous iriez quelque chose comme:

db.messages.find({type: 'prefs', ownerId: recipientId, tags: 'tech stuff'}) 

Vous pourriez puis utilisez les messageIds que vous trouvez dans les prefs pour interroger et trouver tous les messages qui correspondent:

db.messages.find((type:'message', _id: { $in : [array of messageIds from prefs]}}) 

Il est peut-être un peu délicat si vous voulez faire quelque chose comme comptant le nombre de messages chaque 'tag' contient efficacement. Si ce n'est qu'une poignée de balises, vous pouvez simplement ajouter .count() à la fin de votre requête pour chaque requête. Si c'est des centaines ou des milliers, alors vous pourriez faire mieux avec un script côté carte/réduire ou peut-être un objet qui garde la trace du nombre de messages par tag par utilisateur.

+1

Merci, donc votre recommandation est essentiellement le même type de table 'tuple/join' que le cas relationnel, non? Une raison particulière pour laquelle vous stockez les messages et les préférences dans la même collection? –

+0

La chose avec MongoDB est que généralement le plus plat vous permet de rendre votre objet meilleur. Bien qu'il puisse stocker des structures imbriquées, il n'est pas le meilleur pour interroger ou entrer dans ces structures plus tard pour les modifier. Donc, beaucoup de choses peuvent finir par ressembler à un relationnel, mais avec moins d'abstraction en raison de ne pas utiliser les tables. En outre, il n'y a vraiment aucune raison pour laquelle je les stocke dans la même collection autre que ne pas aimer avoir une collection bazillion. Si vous prévoyez d'avoir des millions de messages, il peut être judicieux d'utiliser des collections différentes afin de pouvoir configurer les index pour mieux s'adapter à chaque objet. – Klinky

3

Si vous ne stockez qu'une valeur booléenne simple, comme read/non read, une autre méthode consiste à incorporer un tableau dans chaque document contenant une liste des utilisateurs qui l'ont lu.

{ 
    _id: 'document#42', 
    ... 
    read_by: ['user#83', 'user#2702'] 
} 

Vous devriez alors être en mesure d'indexer ce domaine, ce qui pour les requêtes rapides pour les documents lus par l'utilisateur et les utilisateurs-qui-en lecture de documents.

db.documents.find({read_by: 'user#83'}) 

db.documents.find({_id: 'document#42}, {read_by: 1}) 

Cependant, je trouve que je suis généralement pour tous interroger les documents qui ont pas été lu par un utilisateur particulier, et je ne peux pas penser à une solution qui peut utiliser l'indice dans ce Cas.Je suppose que ce n'est pas possible de faire cela rapidement sans avoir à la fois read_by et unread_by tableaux, de sorte que chaque utilisateur est inclus dans chaque document (ou table de jointure), mais cela aurait un grand coût de stockage.

+0

Concernant ce dernier point sur l'interrogation des messages * non lus * mais en utilisant un champ * read_by *, corrigez-moi si je me trompe mais qu'une clause ** $ not ** ne pourrait pas l'obtenir, comme dans $ $ not: {$ dans: [{id: 'user # 83'}]} ''? – bigp