La prima cosa da capire sulla popolazione di manguste è che non è una magia, ma solo un metodo conveniente che ti consente di recuperare le informazioni correlate senza fare tutto da solo.
Il concetto è essenzialmente per l'uso in cui si decide che sarà necessario inserire i dati in una raccolta separata anziché incorporarli e le considerazioni principali dovrebbero essere in genere sulla dimensione del documento o laddove tali informazioni correlate siano soggette a frequenti aggiornamenti che renderebbero mantenere i dati incorporati ingombranti.
La parte "non magica" è che essenzialmente ciò che accade sotto le coperte è che quando "fai riferimento" a un'altra fonte, la funzione popola effettua una query/interrogazione aggiuntiva a quella raccolta "correlata" per "unire" quei risultati del genitore oggetto che hai recuperato. Potresti farlo tu stesso, ma il metodo è lì per comodità per semplificare il compito. L'ovvia considerazione sulle "prestazioni" è che non esiste un solo round trip al database (istanza MongoDB) per recuperare tutte le informazioni. Ce n'è sempre più di uno.
Come esempio, prendi due raccolte:
{
"_id": ObjectId("5392fea00ff066b7d533a765"),
"customerName": "Bill",
"items": [
ObjectId("5392fee10ff066b7d533a766"),
ObjectId("5392fefe0ff066b7d533a767")
]
}
E gli articoli:
{ "_id": ObjectId("5392fee10ff066b7d533a766"), "prod": "ABC", "qty": 1 }
{ "_id": ObjectId("5392fefe0ff066b7d533a767"), "prod": "XYZ", "qty": 2 }
Il "meglio" che può essere fatto da un modello "referenziato" o dall'uso di popolare (sotto il cofano) è questo:
var order = db.orders.findOne({ "_id": ObjectId("5392fea00ff066b7d533a765") });
order.items = db.items.find({ "_id": { "$in": order.items } ).toArray();
Quindi ci sono chiaramente "almeno" due query e operazioni per "unire" quei dati.
Il concetto di incorporamento è essenzialmente la risposta di MongoDB a come gestire i "join" non supportati. In modo che, piuttosto che dividere i dati in raccolte normalizzate, provi a incorporare i dati "correlati" direttamente all'interno del documento che li utilizza. I vantaggi qui sono che esiste un'unica operazione di "lettura" per recuperare le informazioni "correlate" e anche un unico punto di operazioni di "scrittura" per aggiornare le voci "genitore" e "figlio", sebbene spesso non sia possibile scrivere su "molti" figli contemporaneamente senza elaborare "liste" sul client o altrimenti accettare operazioni di scrittura "multiple", e preferibilmente in elaborazione "batch".
I dati quindi assomigliano piuttosto a questo (rispetto all'esempio sopra):
{
"_id": ObjectId("5392fea00ff066b7d533a765"),
"customerName": "Bill",
"items": [
{ "_id": ObjectId("5392fee10ff066b7d533a766"), "prod": "ABC", "qty": 1 },
{ "_id": ObjectId("5392fefe0ff066b7d533a767"), "prod": "XYZ", "qty": 2 }
]
}
Pertanto il recupero effettivo dei dati è solo una questione di:
db.orders.findOne({ "_id": ObjectId("5392fea00ff066b7d533a765") });
I pro ei contro di entrambi dipenderanno sempre in gran parte dal modello di utilizzo dell'applicazione. Ma a colpo d'occhio:
Incorporamento
-
La dimensione totale del documento con dati incorporati in genere non supererà i 16 MB di spazio di archiviazione (il limite BSON) o altrimenti (come linea guida) avranno array che contengono 500 o più voci.
-
I dati incorporati generalmente non richiedono modifiche frequenti. Quindi potresti convivere con la "duplicazione" che deriva dalla denormalizzazione che non comporta la necessità di aggiornare quei "duplicati" con le stesse informazioni su molti documenti principali solo per invocare una modifica.
-
I dati correlati vengono spesso utilizzati in associazione con il genitore. Il che significa che se i tuoi casi di "lettura/scrittura" hanno praticamente sempre bisogno di "leggere/scrivere" sia per il genitore che per il figlio, allora ha senso incorporare i dati per le operazioni atomiche.
Riferimento
-
I dati correlati supereranno sempre il limite BSON di 16 MB. Puoi sempre considerare un approccio ibrido di "bucketing", ma il limite rigido generale del documento principale non può essere violato. I casi comuni sono "post" e "commenti" in cui si prevede che l'attività di "commento" sarà molto ampia.
-
I dati correlati devono essere aggiornati regolarmente. O essenzialmente il caso in cui "normalizzi" perché quei dati sono "condivisi" tra molti genitori e i dati "correlati" vengono modificati abbastanza frequentemente da non essere pratico aggiornare gli elementi incorporati in ogni "genitore" in cui si trova quell'elemento "figlio" . Il caso più semplice è semplicemente fare riferimento al "figlio" e apportare la modifica una volta.
-
C'è una netta separazione tra letture e scritture. Nel caso in cui forse non avrai sempre bisogno di quelle informazioni "correlate" quando leggi il "genitore" o comunque non hai bisogno di modificare sempre il "genitore" quando scrivi al bambino, potrebbe esserci una buona ragione per separare il modello come referenziato. Inoltre, se c'è un desiderio generale di aggiornare contemporaneamente molti "documenti secondari" in cui quei "documenti secondari" sono effettivamente riferimenti a un'altra raccolta, molto spesso l'implementazione è più efficiente da fare quando i dati si trovano in una raccolta separata raccolta.
Quindi in realtà c'è una discussione molto più ampia sui "pro/contro" per entrambe le posizioni nella documentazione di MongoDB sulla modellazione dei dati, che copre vari casi d'uso e modi per affrontare l'utilizzo dell'incorporamento o del modello di riferimento come supportato dal metodo popolate.
Si spera che i "puntini" siano utili, ma la raccomandazione in generale è di considerare i modelli di utilizzo dei dati dell'applicazione e scegliere ciò che è meglio. Avere la "opzione" per incorporare "dovrebbe" essere il motivo per cui hai scelto MongoDB, ma in realtà sarà il modo in cui la tua applicazione "usa i dati" che prenderà la decisione su quale metodo si adatta a quale parte della tua modellazione dei dati (poiché non lo è "tutto o niente") il migliore.
- Nota che poiché questo è stato originariamente scritto, MongoDB ha introdotto il
$lookup
operatore che esegue effettivamente i "join" tra le raccolte sul server. Ai fini della discussione generale qui, whist "meglio" nella maggior parte dei casi che l'overhead di "query multiple" sostenuto dapopulate()
e "query multiple" in generale, c'è ancora un "overhead significativo" sostenute con qualsiasi$lookup
operazione.
Il principio di base del design è "incorporato" significa "già lì" invece di "prendere da qualche altra parte". Sostanzialmente la differenza tra "in tasca" e "sullo scaffale", e in termini di I/O di solito è più simile a "sullo scaffale in biblioteca in centro" , e in particolare più lontano per le richieste basate sulla rete.