Relaciones MongoDB: ¿incrustación o referencia?

524

Soy nuevo en MongoDB, proveniente de un fondo de base de datos relacional. Quiero diseñar una estructura de preguntas con algunos comentarios, pero no sé qué relación usar para los comentarios: ¿ embedo reference?

Una pregunta con algunos comentarios, como stackoverflow , tendría una estructura como esta:

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

Al principio, quiero usar comentarios incrustados (creo que embedse recomienda en MongoDB), así:

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

Está claro, pero me preocupa este caso: si quiero editar un comentario específico, ¿cómo obtengo su contenido y su pregunta? No hay _idque dejarme encontrar uno, ni question_refdejar que encuentre su pregunta. (Soy tan novato, que no sé si hay alguna forma de hacerlo sin _idy question_ref).

¿Tengo que usar refno embed? ¿Entonces tengo que crear una nueva colección para comentarios?

Viento libre
fuente
Todos los objetos Mongo se crean con un _ID, tanto si crea el campo como si no. Por lo tanto, técnicamente cada comentario seguirá teniendo una identificación.
Robbie Guilfoyle
25
@RobbieGuilfoyle no es cierto: consulte stackoverflow.com/a/11263912/347455
pennstatephil
14
Estoy corregido, gracias @pennstatephil :)
Robbie Guilfoyle
44
Lo que quizás quiere decir es que todos los objetos de mangosta se crean con un _id para aquellos que usan este marco - ver subdocs de mangosta
Luca Steeb
1
Un muy buen libro para aprender relaciones mongo db es "MongoDB Applied Design Patterns - O'Reilly". Capítulo uno, hablar sobre esta decisión, para incrustar o referencia?
Felipe Toledo

Respuestas:

769

Esto es más un arte que una ciencia. La documentación de Mongo sobre esquemas es una buena referencia, pero aquí hay algunas cosas a considerar:

  • Poner tanto como sea posible

    La alegría de una base de datos de documentos es que elimina muchas uniones. Su primer instinto debe ser colocar todo lo que pueda en un solo documento. Debido a que los documentos MongoDB tienen estructura y a que puede realizar consultas de manera eficiente dentro de esa estructura (esto significa que puede tomar la parte del documento que necesita, por lo que el tamaño del documento no debería preocuparle demasiado) no hay necesidad inmediata de normalizar datos como lo harías en SQL. En particular, cualquier dato que no sea útil aparte de su documento padre debe formar parte del mismo documento.

  • Separe los datos a los que se puede hacer referencia desde múltiples lugares en su propia colección.

    Esto no es tanto un problema de "espacio de almacenamiento" como un problema de "consistencia de datos". Si muchos registros se refieren a los mismos datos, es más eficiente y menos propenso a errores actualizar un solo registro y mantener referencias a él en otros lugares.

  • Consideraciones sobre el tamaño del documento

    MongoDB impone un límite de tamaño de 4 MB (16 MB con 1,8) en un solo documento. En un mundo de GB de datos, esto suena pequeño, pero también son 30 mil tweets o 250 respuestas típicas de desbordamiento de pila o 20 fotos parpadeantes. Por otro lado, esta es mucha más información de la que uno podría presentar al mismo tiempo en una página web típica. Primero considere lo que facilitará sus consultas. En muchos casos, la preocupación por el tamaño de los documentos será una optimización prematura.

  • Estructuras de datos complejas:

    MongoDB puede almacenar estructuras de datos anidadas profundas arbitrarias, pero no puede buscarlas de manera eficiente. Si sus datos forman un árbol, bosque o gráfico, efectivamente necesita almacenar cada nodo y sus bordes en un documento separado. (Tenga en cuenta que hay almacenes de datos diseñados específicamente para este tipo de datos que también se deben considerar)

    También se ha señalado que es imposible devolver un subconjunto de elementos en un documento. Si necesita seleccionar algunos bits de cada documento, será más fácil separarlos.

  • Consistencia de los datos

    MongoDB hace una compensación entre eficiencia y consistencia. La regla es que los cambios en un solo documento son siempre atómicos, mientras que las actualizaciones de varios documentos nunca deben suponerse que son atómicas. Tampoco hay forma de "bloquear" un registro en el servidor (puede construir esto en la lógica del cliente utilizando, por ejemplo, un campo "bloquear"). Cuando diseñe su esquema, considere cómo mantendrá sus datos consistentes. En general, cuanto más guarde en un documento, mejor.

Para lo que está describiendo, incrustaría los comentarios y le daría a cada comentario un campo de identificación con un ObjectID. El ObjectID tiene una marca de tiempo incrustada para que pueda usarlo en lugar de crearlo si lo desea.

John F. Miller
fuente
1
Me gustaría agregar a la pregunta de OP: Mi modelo de comentarios contiene el nombre de usuario y el enlace a su avatar. ¿Cuál sería el mejor enfoque, considerando que un usuario puede modificar su nombre / avatar?
user1102018
55
Con respecto a 'Estructuras de datos complejas', parece que es posible devolver un subconjunto de elementos en un documento utilizando el marco de agregación (pruebe $ unwind).
Eyal Roth
44
Errr, esta técnica no era posible o no era ampliamente conocida en MongoDB a principios de 2012. Dada la popularidad de esta pregunta, le animo a que escriba su propia respuesta actualizada. Me temo que me he alejado del desarrollo activo en MongoDB y no estoy en una buena posición para abordar sus comentarios en mi publicación original.
John F. Miller
54
16MB = 30 millones de tweets? ¡¿Estas menas alrededor de 0,5 bytes por tweet ?!
Paolo
8
Sí, parece que estuve fuera por un factor de 1000 y algunas personas consideran esto importante. Editaré la publicación. WRT 560bytes por tweet, cuando lo memoricé en 2011 Twitter todavía estaba vinculado a mensajes de texto y cadenas Ruby 1.4; en otras palabras, solo caracteres ASCII solamente.
John F. Miller
39

En general, incrustar es bueno si tiene relaciones uno a uno o uno a muchos entre entidades, y la referencia es buena si tiene relaciones muchos a muchos.

ywang1724
fuente
10
¿puedes agregar un enlace de referencia? Gracias.
db80
¿Cómo encuentras un comentario específico con este diseño de uno a muchos?
Mauricio Pastorini
29

Si quiero editar un comentario específico, ¿cómo obtener su contenido y su pregunta?

Se pueden realizar consultas por sub-documento: db.question.find({'comments.content' : 'xxx'}).

Esto devolverá todo el documento de la Pregunta. Para editar el comentario especificado, debe buscar el comentario en el cliente, realizar la edición y guardarlo nuevamente en la base de datos.

En general, si su documento contiene una matriz de objetos, encontrará que esos subobjetos deberán modificarse en el lado del cliente.

Vicepresidente de Gates
fuente
44
esto no funcionará si dos comentarios tienen contenidos idénticos. se podría argumentar que también podríamos agregar un autor a la consulta de búsqueda, que aún no funcionaría si el autor realizara dos comentarios idénticos con el mismo contenido
Steel Brain,
@SteelBrain: si hubiera mantenido el índice de comentarios, la notación de puntos podría ayudar. ver stackoverflow.com/a/33284416/1587329
serv-inc
13
No entiendo cómo esta respuesta tiene 34 votos a favor, la segunda persona comenta lo mismo que rompería todo el sistema. Este es un diseño absolutamente terrible y nunca debe usarse. La forma en que @user lo hace es el camino a seguir
user2073973
21

Bueno, llego un poco tarde pero todavía me gustaría compartir mi forma de crear esquemas.

Tengo esquemas para todo lo que se puede describir con una palabra, como lo haría en la OOP clásica.

P.EJ

  • Comentario
  • Cuenta
  • Usuario
  • Entrada en el blog
  • ...

Cada esquema se puede guardar como un documento o subdocumento, por lo que declaro esto para cada esquema.

Documento:

  • Se puede usar como referencia. (Por ejemplo, el usuario hizo un comentario -> el comentario tiene una referencia "hecha por" al usuario)
  • Es una "raíz" en su aplicación. (Por ejemplo, la publicación del blog -> hay una página sobre la publicación del blog)

Subdocumento:

  • Solo se puede usar una vez / nunca es una referencia. (Por ejemplo, el comentario se guarda en la publicación del blog)
  • Nunca es una "raíz" en su aplicación. (El comentario solo aparece en la página de la publicación de blog, pero la página aún trata sobre la publicación de blog)
Silom
fuente
20

Encontré esta pequeña presentación mientras investigaba esta pregunta por mi cuenta. Me sorprendió lo bien que estaba presentada, tanto la información como la presentación de la misma.

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

Resumió:

Como regla general, si tiene muchos [documentos secundarios] o si son grandes, una colección separada podría ser mejor.

Los documentos más pequeños y / o menos tienden a ser un ajuste natural para la incrustación.

Chris Bloom
fuente
11
¿Cuánto es a lot? 3? 10? 100? ¿Qué es large? 1kb? 1MB? 3 campos? 20 campos? ¿Qué es smaller/ fewer?
Traxo
1
Esa es una buena pregunta, y no tengo una respuesta específica. La misma presentación incluyó una diapositiva que decía "Un documento, incluidos todos sus documentos y matrices incrustados, no puede exceder los 16 MB", por lo que podría ser su límite, o simplemente ir con lo que parece razonable / cómodo para su situación específica. En mi proyecto actual, la mayoría de los documentos incrustados son para relaciones 1: 1, o 1: muchos donde los documentos incrustados son realmente simples.
Chris Bloom
Vea también el comentario principal actual de @ john-f-miller, que si bien no proporciona números específicos para un umbral contiene algunos indicadores adicionales que deberían ayudarlo a guiar su decisión.
Chris Bloom
16

Sé que esto es bastante antiguo, pero si está buscando la respuesta a la pregunta del OP sobre cómo devolver solo un comentario especificado, puede usar el operador $ (consulta) de esta manera:

db.question.update({'comments.content': 'xxx'}, {'comments.$': true})
finspin
fuente
44
esto no funcionará si dos comentarios tienen contenidos idénticos. se podría argumentar que también podríamos agregar un autor a la consulta de búsqueda, que aún no funcionaría si el autor realizara dos comentarios idénticos con el mismo contenido
Steel Brain,
1
@SteelBrain: Bien jugado señor, bien jugado.
JakeStrang
12

Sí, podemos usar la referencia en el documento. Para rellenar el otro documento al igual que sql i une. En mongo db no tienen uniones para mapear uno a muchos documentos de relación. En lugar de eso, podemos usar rellenar para cumplir con nuestro escenario ...

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 población es el proceso de reemplazar automáticamente las rutas especificadas en el documento con documentos de otras colecciones. Podemos completar un solo documento, múltiples documentos, objetos simples, múltiples objetos simples o todos los objetos devueltos por una consulta. Veamos algunos ejemplos.

Para obtener más información, visite: http://mongoosejs.com/docs/populate.html

Narendran
fuente
55
Mongoose emitirá una solicitud separada para cada campo poblado. Esto es diferente a SQL JOINS ya que se realizan en el servidor. Esto incluye tráfico adicional entre el servidor de aplicaciones y el servidor mongodb. Nuevamente, puede considerar esto cuando esté optimizando. Sin embargo, su respuesta sigue siendo correcta.
Max
6

En realidad, tengo curiosidad por qué nadie habló sobre las especificaciones UML. Una regla general es que si tiene una agregación, debe usar referencias. Pero si se trata de una composición, el acoplamiento es más fuerte y debe usar documentos incrustados.

Y comprenderá rápidamente por qué es lógico. Si un objeto puede existir independientemente del padre, entonces querrá acceder a él incluso si el padre no existe. Como simplemente no puede incrustarlo en un padre no existente, debe hacerlo vivir en su propia estructura de datos. Y si existe un elemento primario, solo vincúlelos agregando una referencia del objeto en el elemento primario.

¿Realmente no sé cuál es la diferencia entre las dos relaciones? Aquí hay un enlace que los explica: Agregación vs Composición en UML

Bonjour123
fuente
¿Por qué -1? Por favor, dé una explicación que aclare la razón
Bonjour123
1

Si quiero editar un comentario específico, ¿cómo obtengo su contenido y su pregunta?

Si ha realizado un seguimiento de la cantidad de comentarios y el índice del comentario que desea modificar, puede utilizar el operador de punto ( ejemplo SO ).

Podrías hacer f.ex.

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

(como otra forma de editar los comentarios dentro de la pregunta)

serv-inc
fuente