Il problema/i
Come scritto prima , ci sono diversi problemi durante l'incorporamento eccessivo:
Problema 1:limite di dimensioni BSON
Al momento della stesura di questo documento, I documenti BSON sono limitati a 16 MB . Se viene raggiunto tale limite, MongoDB genererebbe un'eccezione e semplicemente non potresti aggiungere più commenti e, nel peggiore dei casi, nemmeno cambiare il nome (utente) o l'immagine se la modifica aumenterebbe le dimensioni del documento.
Problema 2:limitazioni e prestazioni delle query
Non è facile interrogare o ordinare la matrice dei commenti in determinate condizioni. Alcune cose richiederebbero un'aggregazione piuttosto costosa, altre dichiarazioni piuttosto complicate.
Mentre si potrebbe obiettare che una volta che le query sono a posto, questo non è un grosso problema, mi permetto di dissentire. Innanzitutto, più una query è complicata, più difficile sarà l'ottimizzazione, sia per lo sviluppatore che per l'ottimizzatore di query di MongoDB. Ho ottenuto i migliori risultati semplicemente semplificando i modelli di dati e le query, accelerando le risposte di un fattore 100 in un'istanza.
Durante il ridimensionamento, le risorse necessarie per query complicate e/o costose potrebbero anche essere sommate a intere macchine rispetto a un modello di dati più semplice e alle query corrispondenti.
Problema 3:manutenibilità
Ultimo ma non meno importante, potresti riscontrare problemi durante la manutenzione del tuo codice. Come semplice regola pratica
In questo contesto, "costoso" si riferisce sia al denaro (per progetti professionali) che al tempo (per progetti hobby).
(Mia!) Soluzione
È abbastanza semplice:semplifica il tuo modello di dati. Di conseguenza, le tue domande diventeranno meno complicate e (si spera) più veloci.
Fase 1:identifica i tuoi casi d'uso
Sarà un'ipotesi folle per me, ma la cosa importante qui è mostrarti il metodo generale. Definirei i tuoi casi d'uso come segue:
- Per un determinato post, gli utenti dovrebbero poter commentare
- Per un determinato post, mostra l'autore e i commenti, insieme al nome utente dei commentatori e degli autori e alla loro immagine
- Per un determinato utente, dovrebbe essere facilmente possibile modificare il nome, il nome utente e l'immagine
Fase 2:modella i tuoi dati di conseguenza
Utenti
Prima di tutto, abbiamo un modello utente semplice
{
_id: new ObjectId(),
name: "Joe Average",
username: "HotGrrrl96",
picture: "some_link"
}
Niente di nuovo qui, aggiunto solo per completezza.
Post
{
_id: new ObjectId()
title: "A post",
content: " Interesting stuff",
picture: "some_link",
created: new ISODate(),
author: {
username: "HotGrrrl96",
picture: "some_link"
}
}
E questo è tutto per un post. Ci sono due cose da notare qui:in primo luogo, memorizziamo i dati dell'autore di cui abbiamo immediatamente bisogno quando visualizziamo un post, poiché questo ci risparmia una query per un caso d'uso molto comune, se non onnipresente. Perché non salviamo i commenti e i dati dei commentatori di conseguenza? A causa del limite di dimensione di 16 MB , stiamo cercando di impedire la memorizzazione dei riferimenti in un unico documento. Piuttosto, memorizziamo i riferimenti nei documenti di commento:
Commenti
{
_id: new ObjectId(),
post: someObjectId,
created: new ISODate(),
commenter: {
username: "FooBar",
picture: "some_link"
},
comment: "Awesome!"
}
Come per i post, abbiamo tutti i dati necessari per visualizzare un post.
Le domande
Quello che abbiamo ottenuto ora è che abbiamo aggirato il limite di dimensione BSON e non abbiamo bisogno di fare riferimento ai dati dell'utente per poter visualizzare post e commenti, il che dovrebbe farci risparmiare molte domande. Ma torniamo ai casi d'uso e ad altre query
Aggiunta di un commento
Adesso è tutto molto semplice.
Ricevere tutti o alcuni commenti per un determinato post
Per tutti i commenti
db.comments.find({post:objectIdOfPost})
Per gli ultimi 3 commenti
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
Quindi, per visualizzare un post e tutti (o alcuni) dei suoi commenti, inclusi i nomi utente e le immagini, siamo a due domande. Più del necessario prima, ma abbiamo aggirato il limite delle dimensioni e in pratica puoi avere un numero indefinito di commenti per ogni post. Ma veniamo a qualcosa di reale
Ricevere gli ultimi 5 post e i loro ultimi 3 commenti
Questo è un processo in due fasi. Tuttavia, con un'indicizzazione adeguata (ci tornerò più avanti) questo dovrebbe comunque essere veloce (e quindi risparmiare risorse):
var posts = db.posts.find().sort({created:-1}).limit(5)
posts.forEach(
function(post) {
doSomethingWith(post);
var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
doSomethingElseWith(comments);
}
)
Ottieni tutti i post di un determinato utente ordinati dal più recente al meno recente e i relativi commenti
var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
var postIds = [];
posts.forEach(
function(post){
postIds.push(post._id);
}
)
var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});
Nota che abbiamo solo due domande qui. Anche se devi "manualmente" stabilire la connessione tra i post e i rispettivi commenti, dovrebbe essere abbastanza semplice.
Cambia un nome utente
Questo presumibilmente è un caso d'uso raro eseguito. Tuttavia, non è molto complicato con detto modello di dati
Innanzitutto, cambiamo il documento utente
db.users.update(
{ username: "HotGrrrl96"},
{
$set: { username: "Joe Cool"},
$push: {oldUsernames: "HotGrrrl96" }
},
{
writeConcern: {w: "majority"}
}
);
Inseriamo il vecchio nome utente in un array corrispondente. Questa è una misura di sicurezza nel caso qualcosa vada storto con le seguenti operazioni. Inoltre, impostiamo il problema di scrittura su un livello piuttosto alto per assicurarci che i dati siano durevoli.
db.posts.update(
{ "author.username": "HotGrrrl96"},
{ $set:{ "author.username": "Joe Cool"} },
{
multi:true,
writeConcern: {w:"majority"}
}
)
Niente di speciale qui. La dichiarazione di aggiornamento per i commenti sembra praticamente la stessa. Sebbene queste query richiedano del tempo, vengono eseguite raramente.
Gli indici
Come regola generale, si può dire che MongoDB può utilizzare solo un indice per query. Anche se questo non è del tutto vero poiché ci sono intersezioni di indici, è facile da gestire. Un'altra cosa è che i singoli campi in un indice composto possono essere usati indipendentemente. Quindi un approccio semplice all'ottimizzazione dell'indice consiste nel trovare la query con il maggior numero di campi utilizzati nelle operazioni che utilizzano gli indici e creare un indice composto di essi. Si noti che l'ordine di occorrenza nella query è importante. Allora, andiamo avanti.
Post
db.posts.createIndex({"author.username":1,"created":-1})
Commenti
db.comments.createIndex({"post":1, "created":-1})
Conclusione
È vero che un documento completamente incorporato per post è il modo più veloce per caricarlo e i suoi commenti. Tuttavia, non si adatta bene e, a causa della natura delle query eventualmente complesse necessarie per gestirlo, questo vantaggio in termini di prestazioni può essere sfruttato o addirittura eliminato.
Con la soluzione di cui sopra, scambi una certa velocità (se!) Con una scalabilità praticamente illimitata e un modo molto più semplice di gestire i dati.
Hth.