Relações MongoDB: incorporar ou referência?

524

Eu sou novo no MongoDB - proveniente de um fundo de banco de dados relacional. Quero criar uma estrutura de perguntas com alguns comentários, mas não sei qual relacionamento usar nos comentários: embedou reference?

Uma pergunta com alguns comentários, como stackoverflow , teria uma estrutura como esta:

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

No começo, quero usar comentários incorporados (acho que embedé recomendado no MongoDB), assim:

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

Está claro, mas estou preocupado com este caso: se eu quiser editar um comentário especificado, como obtenho o conteúdo e a pergunta? Não há _idpara me deixar encontrar um, nem question_refme deixar encontrar sua pergunta. (Eu sou tão novato, que não sei se há alguma maneira de fazer isso sem _ide question_ref.)

Eu tenho que usar refnão embed? Então eu tenho que criar uma nova coleção para comentários?

Vento livre
fonte
Todos os objetos do Mongo são criados com um _ID, independentemente de você criar o campo ou não. Portanto, tecnicamente, cada comentário ainda terá um ID.
Robbie Guilfoyle
25
@RobbieGuilfoyle true-- não vê stackoverflow.com/a/11263912/347455
pennstatephil
13
Eu estou corrigido, graças @pennstatephil :)
Robbie Guilfoyle
4
O que ele talvez significa é que todos os mangusto objetos são criados com um _id para aqueles que usam este quadro - veja Subdocs mangusto
Luca Steeb
1
Um livro muito bom para aprender relacionamentos com o mongo db é "MongoDB Applied Design Patterns - O'Reilly". Capítulo um, fale sobre essa decisão, incorporar ou referenciar?
Felipe Toledo

Respostas:

769

Isso é mais uma arte do que uma ciência. A documentação do Mongo sobre esquemas é uma boa referência, mas aqui estão algumas coisas a considerar:

  • Coloque o máximo possível

    A alegria de um banco de dados de documentos é que ele elimina muitas associações. Seu primeiro instinto deve ser colocar o máximo possível em um único documento. Como os documentos do MongoDB têm estrutura e porque você pode consultar com eficiência dentro dessa estrutura (isso significa que você pode fazer parte do documento de que precisa, para que o tamanho do documento não o preocupe muito), não há necessidade imediata de normalizar dados como você faria no SQL. Em particular, qualquer dado que não seja útil além do documento pai deve fazer parte do mesmo documento.

  • Separe os dados que podem ser referidos de vários locais em sua própria coleção.

    Esse não é um problema de "espaço de armazenamento", mas de "consistência de dados". Se muitos registros se referirem aos mesmos dados, é mais eficiente e menos propenso a erros atualizar um único registro e manter referências a ele em outros lugares.

  • Considerações sobre o tamanho do documento

    O MongoDB impõe um limite de tamanho de 4 MB (16 MB com 1,8) em um único documento. Em um mundo de GB de dados, isso parece pequeno, mas também são 30 mil tweets ou 250 respostas típicas do Stack Overflow ou 20 fotos intermitentes. Por outro lado, isso é muito mais informações do que se pode apresentar de uma vez em uma página da Web típica. Primeiro, considere o que facilitará suas consultas. Em muitos casos, a preocupação com o tamanho do documento será otimização prematura.

  • Estruturas de dados complexas:

    O MongoDB pode armazenar estruturas de dados aninhadas profundas arbitrárias, mas não pode pesquisá-las com eficiência. Se seus dados formarem uma árvore, floresta ou gráfico, você precisará efetivamente armazenar cada nó e suas bordas em um documento separado. (Observe que existem armazenamentos de dados projetados especificamente para esse tipo de dados que também devem ser considerados)

    Também foi apontado que é impossível retornar um subconjunto de elementos em um documento. Se você precisar escolher alguns bits de cada documento, será mais fácil separá-los.

  • A consistência dos dados

    O MongoDB faz uma troca entre eficiência e consistência. A regra é que as alterações em um único documento são sempre atômicas, enquanto as atualizações em vários documentos nunca devem ser consideradas atômicas. Também não há como "bloquear" um registro no servidor (você pode incorporar isso na lógica do cliente usando, por exemplo, um campo "lock"). Ao projetar seu esquema, considere como manterá seus dados consistentes. Geralmente, quanto mais você mantém em um documento, melhor.

Pelo que você está descrevendo, eu incorporaria os comentários e daria a cada comentário um campo de identificação com um ObjectID. O ObjectID possui um carimbo de data / hora incorporado para que você possa usá-lo em vez de criar se quiser.

John F. Miller
fonte
1
Eu gostaria de adicionar à pergunta do OP: Meu modelo de comentários contém o nome de usuário e o link para o avatar dele. Qual seria a melhor abordagem, considerando que um usuário pode modificar seu nome / avatar?
usar o seguinte comando
5
Em relação a 'Estruturas complexas de dados', parece possível retornar um subconjunto de elementos em um documento usando a estrutura de agregação (tente $ desenrolar).
Eyal Roth
4
Errr, essa técnica não era possível ou não era amplamente conhecida no MongoDB no início de 2012. Dada a popularidade dessa pergunta, eu encorajo você a escrever sua própria resposta atualizada. Receio ter me afastado do desenvolvimento ativo no MongoDB e não estou em uma boa posição para abordar seus comentários na minha postagem original.
John F. Miller
54
16MB = 30 milhões de tweets? ths menas cerca de 0,5 byte por tweet ?!
Paolo
8
Sim, parece que eu estava com um fator de 1000 e algumas pessoas acham isso importante. Vou editar a postagem. WRT 560bytes por tweet, quando eu escrevi isso em 2011, o twitter ainda estava vinculado a mensagens de texto e strings do Ruby 1.4; em outras palavras, ainda apenas caracteres ASCII.
John F. Miller
39

Em geral, a incorporação é boa se você tiver relacionamentos um para um ou um para muitos entre entidades e a referência é boa se você tiver relacionamentos muitos para muitos.

ywang1724
fonte
10
você pode adicionar um link de referência? Obrigado.
db80
Como você encontra um comentário específico com esse design de um para muitos?
Mauricio Pastorini
29

Se eu quiser editar um comentário especificado, como obter seu conteúdo e sua pergunta?

Você pode consultar por sub-documento: db.question.find({'comments.content' : 'xxx'}).

Isso retornará todo o documento da pergunta. Para editar o comentário especificado, você precisa encontrar o comentário no cliente, fazer a edição e salvá-lo no banco de dados.

Em geral, se seu documento contiver uma matriz de objetos, você descobrirá que esses subobjetos precisarão ser modificados no lado do cliente.

Gates VP
fonte
4
isso não funcionará se dois comentários tiverem conteúdo idêntico. pode-se argumentar que também poderia adicionar autor para a consulta de pesquisa, que ainda não iria funcionar se o autor fez duas observações idênticas com o mesmo conteúdo
Aço cerebrais
@ SteelBrain: se ele mantivesse o índice de comentários, a notação de pontos poderia ajudar. consulte stackoverflow.com/a/33284416/1587329
serv-inc
13
Não entendo como essa resposta tem 34 votos positivos, a segunda vez que várias pessoas comentam a mesma coisa que todo o sistema quebraria. Este é um design absolutamente terrível e nunca deve ser usado. O caminho que o usuário faz é o caminho a seguir #
user2073973
21

Bem, estou um pouco atrasado, mas ainda gostaria de compartilhar minha maneira de criar esquemas.

Eu tenho esquemas para tudo o que pode ser descrito por uma palavra, como você faria no POO clássico.

POR EXEMPLO

  • Comente
  • Conta
  • Do utilizador
  • Postagem no blog
  • ...

Todos os esquemas podem ser salvos como um Documento ou Subdocumento, então declaro isso para cada esquema.

Documento:

  • Pode ser usado como referência. (Por exemplo, o usuário fez um comentário -> o comentário tem uma referência "feita por" ao usuário)
  • É uma "raiz" no seu aplicativo. (Por exemplo, o blogpost -> há uma página sobre o blogpost)

Subdocumento:

  • Só pode ser usado uma vez / nunca é uma referência. (Por exemplo, o comentário é salvo no post do blog)
  • Nunca é uma "raiz" no seu aplicativo. (O comentário aparece na página do blog, mas a página ainda é sobre o blog)
Silom
fonte
20

Me deparei com essa pequena apresentação enquanto pesquisava sozinha essa pergunta. Fiquei surpreso com o quão bem foi apresentado, tanto a informação quanto a apresentação.

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

Resumiu:

Como regra geral, se você possui muitos [documentos filhos] ou se eles são grandes, uma coleção separada pode ser a melhor.

Documentos menores e / ou menos tendem a ser um ajuste natural para incorporação.

Chris Bloom
fonte
11
Quanto custa a lot? 3? 10? 100? O que é large? 1kb? 1MB? 3 campos? 20 campos? O que é smaller/ fewer?
Traxo 24/10
1
Essa é uma boa pergunta e para a qual não tenho uma resposta específica. A mesma apresentação incluiu um slide que dizia "Um documento, incluindo todos os seus documentos e matrizes incorporados, não pode exceder 16 MB", portanto esse pode ser o seu ponto de corte ou apenas seguir o que parece razoável / confortável para sua situação específica. No meu projeto atual, a maioria dos documentos incorporados são para relacionamentos 1: 1 ou 1: muitos, onde os documentos incorporados são realmente simples.
Chris Bloom
Veja também o principal comentário atual de @ john-f-miller, que, embora não forneça números específicos para um limite, contém alguns indicadores adicionais que devem ajudar a orientar sua decisão.
Chris Bloom
16

Sei que isso é bastante antigo, mas se você estiver procurando a resposta para a pergunta do OP sobre como retornar apenas comentários especificados, poderá usar o operador $ (query) como este:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
alicate
fonte
4
isso não funcionará se dois comentários tiverem conteúdo idêntico. pode-se argumentar que também poderia adicionar autor para a consulta de pesquisa, que ainda não iria funcionar se o autor fez duas observações idênticas com o mesmo conteúdo
Aço cerebrais
1
@SteelBrain: Bem jogado senhor, bem jogado.
JakeStrang
12

Sim, nós podemos usar a referência no document.To preencher o outro documento apenas como sql i joins.In mongo db eles não têm junta-se ao mapeamento um para muitos relação document.Instead que podemos usar populate para cumprir nosso cenário ..

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' }]
});

População é o processo de substituir automaticamente os caminhos especificados no documento por documentos de outras coleções. Podemos preencher um único documento, vários documentos, objeto simples, vários objetos simples ou todos os objetos retornados de uma consulta. Vejamos alguns exemplos.

Melhor você pode obter mais informações, visite: http://mongoosejs.com/docs/populate.html

Narendran
fonte
5
O Mongoose emitirá uma solicitação separada para cada campo preenchido. Isso é diferente do SQL JOINS, pois eles são executados no servidor. Isso inclui tráfego extra entre o servidor de aplicativos e o servidor mongodb. Novamente, você pode considerar isso ao otimizar. No entanto, sua resposta ainda está correta.
Max
6

Na verdade, estou bastante curioso por que ninguém falou sobre as especificações UML. Uma regra prática é que, se você tiver uma agregação, deverá usar referências. Mas se for uma composição, o acoplamento será mais forte e você deverá usar documentos incorporados.

E você entenderá rapidamente por que é lógico. Se um objeto puder existir independentemente do pai, será necessário acessá-lo, mesmo que o pai não exista. Como você simplesmente não pode incorporá-lo em um pai inexistente, é necessário torná-lo vivo em sua própria estrutura de dados. E se um pai existir, basta vinculá-los, adicionando uma ref do objeto no pai.

Realmente não sei qual é a diferença entre os dois relacionamentos? Aqui está um link para explicá-los: Agregação vs Composição em UML

Bonjour123
fonte
Por que -1? Por favor, forneça uma explicação que esclareça o motivo
Bonjour123
1

Se eu quiser editar um comentário especificado, como obtenho seu conteúdo e sua pergunta?

Se você acompanhou o número de comentários e o índice do comentário que deseja alterar, use o operador de ponto ( exemplo SO ).

Você poderia fazer f.ex.

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

(como outra maneira de editar os comentários dentro da pergunta)

serv-inc
fonte