Relazioni MongoDB: incorporare o fare riferimento?


524

Sono nuovo di MongoDB, proveniente da un background di database relazionale. Voglio progettare una struttura di domande con alcuni commenti, ma non so quale relazione usare per i commenti: embedo reference?

Una domanda con alcuni commenti, come StackOverflow , avrebbe una struttura come questa:

Question
    title = 'aaa'
    content = bbb'
    comments = ???

All'inizio, voglio usare i commenti incorporati (penso che embedsia consigliato in MongoDB), in questo modo:

Question
    title = 'aaa'
    content = 'bbb'
    comments = [ { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'}, 
                 { content = 'xxx', createdAt = 'yyy'} ]

È chiaro, ma sono preoccupato per questo caso: se voglio modificare un commento specificato, come ottengo il suo contenuto e la sua domanda? Non c'è modo _iddi farmi trovare uno, né question_refdi farmi trovare la sua domanda. (Sono così alle prime armi, che non so se c'è modo di farlo senza _ide question_ref.)

Devo usare ref non embed? Quindi devo creare una nuova raccolta per i commenti?


Tutti gli oggetti Mongo vengono creati con un _ID, sia che si crei il campo o meno. Quindi tecnicamente ogni commento avrà ancora un ID.
Robbie Guilfoyle il

25
@RobbieGuilfoyle non true-- vedere stackoverflow.com/a/11263912/347455
pennstatephil

13
Sono corretto, grazie @pennstatephil :)
Robbie Guilfoyle,

4
Ciò che forse intende dire è che tutti gli oggetti mangusta sono creati con un _id per coloro che usano questo framework - vedi subdocs mangusta
Luca Steeb

1
Un ottimo libro per l'apprendimento delle relazioni mongo db è "MongoDB Applied Design Patterns - O'Reilly". Capitolo primo, parla di questa decisione, di incorporare o fare riferimento?
Felipe Toledo,

Risposte:


769

Questa è più un'arte che una scienza. La documentazione Mongo sugli schemi è un buon riferimento, ma qui ci sono alcune cose da considerare:

  • Metti il ​​più possibile

    La gioia di un database di documenti è che elimina molti join. Il tuo primo istinto dovrebbe essere quello di inserire il più possibile in un unico documento. Poiché i documenti MongoDB hanno una struttura e poiché è possibile eseguire query in modo efficiente all'interno di tale struttura (ciò significa che è possibile prendere la parte del documento di cui hai bisogno, quindi le dimensioni del documento non dovrebbero preoccuparti molto) non è necessario immediatamente normalizzare dati come lo faresti in SQL. In particolare, tutti i dati non utili a parte il documento principale dovrebbero far parte dello stesso documento.

  • Separare i dati a cui è possibile fare riferimento da più posizioni nella propria raccolta.

    Questo non è tanto un problema di "spazio di archiviazione" quanto un problema di "coerenza dei dati". Se molti record faranno riferimento agli stessi dati, è più efficiente e meno soggetto a errori aggiornare un singolo record e conservarne i riferimenti in altri luoghi.

  • Considerazioni sulla dimensione del documento

    MongoDB impone un limite di dimensione di 4 MB (16 MB con 1,8) su un singolo documento. In un mondo di GB di dati questo sembra piccolo, ma sono anche 30 mila tweet o 250 risposte tipiche di Stack Overflow o 20 foto sfarfallio. D'altra parte, si tratta di molte più informazioni di quelle che si potrebbero desiderare di presentare contemporaneamente in una tipica pagina Web. Innanzitutto considera cosa renderà le tue domande più facili. In molti casi la preoccupazione per le dimensioni del documento sarà l'ottimizzazione prematura.

  • Strutture dati complesse:

    MongoDB può archiviare strutture di dati annidate in profondità arbitrarie, ma non può cercarle in modo efficiente. Se i tuoi dati formano un albero, una foresta o un grafico, devi effettivamente memorizzare ciascun nodo e i suoi bordi in un documento separato. (Si noti che ci sono archivi di dati appositamente progettati per questo tipo di dati che si dovrebbero considerare anche)

    È stato anche sottolineato che è impossibile restituire un sottoinsieme di elementi in un documento. Se devi selezionare alcuni bit di ciascun documento, sarà più facile separarli.

  • Coerenza dei dati

    MongoDB fa un compromesso tra efficienza e coerenza. La regola è che le modifiche a un singolo documento sono sempre atomiche, mentre gli aggiornamenti a più documenti non dovrebbero mai essere considerati atomici. Non è inoltre possibile "bloccare" un record sul server (è possibile inserirlo nella logica del client utilizzando ad esempio un campo "blocco"). Quando progetti lo schema, considera come manterrai i tuoi dati coerenti. Generalmente, più ne conservi in ​​un documento, meglio è.

Per quello che stai descrivendo, incorporerei i commenti e darei ad ogni commento un campo ID con un ObjectID. L'ObjectID ha un timestamp incorporato in esso in modo da poterlo utilizzare invece di crearlo se lo desideri.


1
Vorrei aggiungere alla domanda OP: il mio modello di commenti contiene il nome utente e il link al suo avatar. Quale sarebbe l'approccio migliore, considerando che un utente può modificare il suo nome / avatar?
user1102018,

5
Per quanto riguarda le "Strutture di dati complesse", sembra che sia possibile restituire un sottoinsieme di elementi in un documento utilizzando il framework di aggregazione (provare $ unfind).
Eyal Roth,

4
Errr, questa tecnica non era possibile o non era ampiamente conosciuta in MongoDB all'inizio del 2012. Data la popolarità di questa domanda, ti incoraggio a scrivere la tua risposta aggiornata. Temo di essermi allontanato dallo sviluppo attivo su MongoDB e non sono in una buona posizione per rispondere al tuo commento all'interno del mio post originale.
John F. Miller,

54
16 MB = 30 milioni di tweet? questo è circa 0,5 byte per tweet ?!
Paolo,

8
Sì, sembra che fossi spento di un fattore 1000 e alcune persone lo trovano importante. Modificherò il post. WRT 560bytes per tweet, quando ho scritto questo nel 2011 twitter era ancora legato ai messaggi di testo e alle stringhe di Ruby 1.4; in altre parole ancora solo caratteri ASCII.
John F. Miller,


29

Se voglio modificare un commento specificato, come ottenerne il contenuto e la domanda?

È possibile eseguire una query per documento secondario: db.question.find({'comments.content' : 'xxx'}) .

Ciò restituirà l'intero documento della domanda. Per modificare il commento specificato, è quindi necessario trovare il commento sul client, apportare la modifica e salvarlo nuovamente nel DB.

In generale, se il tuo documento contiene una matrice di oggetti, scoprirai che questi oggetti secondari dovranno essere modificati sul lato client.


4
questo non funzionerà se due commenti hanno contenuti identici. si potrebbe sostenere che potremmo anche aggiungere l'autore alla query di ricerca, che comunque non funzionerebbe se l'autore avesse fatto due commenti identici con lo stesso contenuto
Steel Brain

@SteelBrain: se avesse mantenuto l'indice dei commenti, la notazione con i punti potrebbe aiutare. vedi stackoverflow.com/a/33284416/1587329
serv-inc

13
Non capisco come questa risposta abbia 34 voti, la seconda persona multipla commenta la stessa cosa che l'intero sistema si romperà. Questo è un design assolutamente terribile e non dovrebbe mai essere usato. Il modo in cui @user lo fa è la strada da percorrere
user2073973

21

Bene, sono un po 'in ritardo ma vorrei comunque condividere il mio modo di creare uno schema.

Ho schemi per tutto ciò che può essere descritto da una parola, come faresti con il classico OOP.

PER ESEMPIO

  • Commento
  • account
  • Utente
  • Post sul blog
  • ...

Ogni schema può essere salvato come documento o documento secondario, quindi lo dichiaro per ogni schema.

Documento:

  • Può essere usato come riferimento. (Ad esempio, l'utente ha fatto un commento -> commento ha un riferimento "creato da" all'utente)
  • È un "Root" nella tua applicazione. (Ad esempio il blogpost -> c'è una pagina sul blogpost)

Documento secondario:

  • Può essere usato solo una volta / non è mai un riferimento. (Ad esempio, il commento viene salvato nel post del blog)
  • Non è mai un "Root" nella tua applicazione. (Il commento appare solo nella pagina del post del blog ma la pagina riguarda ancora il post del blog)

20

Mi sono imbattuto in questa piccola presentazione mentre cercavo questa domanda da solo. Sono rimasto sorpreso dal modo in cui è stato preparato, sia le informazioni che la presentazione.

http://openmymind.net/Multiple-Collections-Versus-Embedded-Documents

Ha riassunto:

Come regola generale, se hai molti [documenti secondari] o se sono grandi, una raccolta separata potrebbe essere la migliore.

Documenti più piccoli e / o meno tendono ad essere una scelta naturale per l'incorporamento.


11
Quanto costa a lot? 3? 10? 100? Cosa large? 1kb? 1MB? 3 campi? 20 campi? Che cos'è smaller/ fewer?
Traxo,

1
Questa è una buona domanda e per la quale non ho una risposta specifica. La stessa presentazione includeva una diapositiva che diceva "Un documento, compresi tutti i suoi documenti e array incorporati, non può superare i 16 MB", quindi potrebbe essere il tuo limite o semplicemente andare con ciò che sembra ragionevole / comodo per la tua situazione specifica. Nel mio progetto attuale, la maggior parte dei documenti incorporati riguarda relazioni 1: 1 o 1: molti in cui i documenti incorporati sono davvero semplici.
Chris Bloom,

Vedi anche l'attuale commento principale di @ john-f-miller, che pur non fornendo numeri specifici per una soglia contiene alcuni puntatori aggiuntivi che dovrebbero guidare la tua decisione.
Chris Bloom,

16

So che questo è piuttosto vecchio ma se stai cercando la risposta alla domanda del PO su come restituire solo il commento specificato, puoi usare l' operatore $ (query) in questo modo:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})

4
questo non funzionerà se due commenti hanno contenuti identici. si potrebbe sostenere che potremmo anche aggiungere l'autore alla query di ricerca, che comunque non funzionerebbe se l'autore avesse fatto due commenti identici con lo stesso contenuto
Steel Brain

1
@SteelBrain: ben giocato signore, ben giocato.
JakeStrang,

12

Sì, possiamo usare il riferimento nel documento. Per popolare un altro documento proprio come sql i join. A mongo db non hanno join per mappare uno a molti documenti di relazione. Invece possiamo usare populate per soddisfare il nostro scenario.

var mongoose = require('mongoose')
  , Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Number,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Number, ref: 'Person' },
  title    : String,
  fans     : [{ type: Number, ref: 'Person' }]
});

La popolazione è il processo di sostituzione automatica dei percorsi specificati nel documento con documenti di altre raccolte. È possibile popolare un singolo documento, più documenti, un oggetto semplice, più oggetti semplici o tutti gli oggetti restituiti da una query. Diamo un'occhiata ad alcuni esempi.

Meglio che tu possa ottenere maggiori informazioni, visitare: http://mongoosejs.com/docs/populate.html


5
Mongoose emetterà una richiesta separata per ciascun campo popolato. Ciò è diverso dai JOIN SQL in quanto vengono eseguiti sul server. Ciò include il traffico aggiuntivo tra l'app server e il server mongodb. Ancora una volta, potresti considerare questo quando stai ottimizzando. Tuttavia, la tua risposta è ancora corretta.
Max

6

In realtà, sono abbastanza curioso del perché nessuno abbia parlato delle specifiche UML. Una regola empirica è che se si dispone di un'aggregazione, è necessario utilizzare i riferimenti. Ma se è una composizione, l'accoppiamento è più forte e dovresti usare documenti incorporati.

E capirai rapidamente perché è logico. Se un oggetto può esistere indipendentemente dal genitore, allora vorrai accedervi anche se il genitore non esiste. Dato che non puoi semplicemente incorporarlo in un genitore inesistente, devi renderlo attivo nella sua struttura di dati. E se esiste un genitore, basta collegarli insieme aggiungendo un riferimento all'oggetto nel genitore.

Non sai davvero qual è la differenza tra le due relazioni? Ecco un link che li spiega: Aggregazione vs Composizione in UML


Perché -1? Fornisci una spiegazione che chiarisca il motivo
Bonjour123,


1

Se voglio modificare un commento specificato, come ottengo il suo contenuto e la sua domanda?

Se avessi tenuto traccia del numero di commenti e dell'indice del commento che desideri modificare, puoi utilizzare l'operatore punto ( esempio SO ).

Potresti fare f.ex.

db.questions.update(
    {
        "title": "aaa"       
    }, 
    { 
        "comments.0.contents": "new text"
    }
)

(come un altro modo per modificare i commenti all'interno della domanda)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.