Principios para modelar documentos de CouchDB

120

Tengo una pregunta que he intentado responder durante algún tiempo pero no puedo resolver:

¿Cómo diseña o divide los documentos de CouchDB?

Tome una publicación de blog, por ejemplo.

La forma semi "relacional" de hacerlo sería crear algunos objetos:

  • Enviar
  • Usuario
  • Comentario
  • Etiqueta
  • Retazo

Esto tiene mucho sentido. Pero estoy tratando de usar couchdb (por todas las razones por las que es genial) para modelar lo mismo y ha sido extremadamente difícil.

La mayoría de las publicaciones de blogs te ofrecen un ejemplo sencillo de cómo hacer esto. Básicamente lo dividen de la misma manera, pero dicen que puede agregar propiedades 'arbitrarias' a cada documento, lo que definitivamente es bueno. Entonces tendrías algo como esto en CouchDB:

  • Publicar (con etiquetas y fragmentos "pseudo" modelos en el documento)
  • Comentario
  • Usuario

Algunas personas incluso dirían que podría lanzar el comentario y el usuario allí, por lo que tendría esto:


post {
    id: 123412804910820
    title: "My Post"
    body: "Lots of Content"
    html: "<p>Lots of Content</p>"
    author: {
        name: "Lance"
        age: "23"
    }
    tags: ["sample", "post"]
    comments {
        comment {
            id: 93930414809
            body: "Interesting Post"
        } 
        comment {
            id: 19018301989
            body: "I agree"
        }
    }
}

Eso se ve muy bien y es fácil de entender. También entiendo cómo puede escribir vistas que extraigan solo los comentarios de todos sus documentos de publicación, para incluirlos en modelos de comentarios, lo mismo con los usuarios y las etiquetas.

Pero luego pienso, "¿por qué no poner todo mi sitio en un solo documento?":


site {
    domain: "www.blog.com"
    owner: "me"
    pages {
        page {
            title: "Blog"
            posts {
                post {
                    id: 123412804910820
                    title: "My Post"
                    body: "Lots of Content"
                    html: "<p>Lots of Content</p>"
                    author: {
                        name: "Lance"
                        age: "23"
                    }
                    tags: ["sample", "post"]
                    comments {
                        comment {
                            id: 93930414809
                            body: "Interesting Post"
                        } 
                        comment {
                            id: 19018301989
                            body: "I agree"
                        }
                    }
                }
                post {
                    id: 18091890192984
                    title: "Second Post"
                    ...
                }
            }
        }
    }
}

Podrías crear vistas fácilmente para encontrar lo que querías con eso.

Entonces, la pregunta que tengo es, ¿cómo se determina cuándo dividir el documento en documentos más pequeños o cuándo hacer "RELACIONES" entre los documentos?

Creo que sería mucho más "Orientado a objetos" y más fácil de asignar a Objetos de valor, si se dividiera así:


posts {
    post {
        id: 123412804910820
        title: "My Post"
        body: "Lots of Content"
        html: "<p>Lots of Content</p>"
        author_id: "Lance1231"
        tags: ["sample", "post"]
    }
}
authors {
    author {
        id: "Lance1231"
        name: "Lance"
        age: "23"
    }
}
comments {
    comment {
        id: "comment1"
        body: "Interesting Post"
        post_id: 123412804910820
    } 
    comment {
        id: "comment2"
        body: "I agree"
        post_id: 123412804910820
    }
}

... pero luego comienza a parecerse más a una base de datos relacional. Y muchas veces heredo algo que se parece al "sitio completo en un documento", por lo que es más difícil modelarlo con relaciones.

He leído muchas cosas sobre cómo / cuándo usar bases de datos relacionales frente a bases de datos de documentos, así que ese no es el problema principal aquí. Me pregunto más, ¿cuál es una buena regla / principio para aplicar al modelar datos en CouchDB?

Otro ejemplo es con archivos / datos XML. Algunos datos XML tienen anidación de más de 10 niveles de profundidad, y me gustaría visualizar eso usando el mismo cliente (Ajax on Rails, por ejemplo, o Flex) que haría para renderizar JSON desde ActiveRecord, CouchRest o cualquier otro Object Relational Mapper. A veces obtengo archivos XML enormes que son la estructura completa del sitio, como el que se muestra a continuación, y necesito asignarlos a Value Objects para usarlos en mi aplicación Rails para no tener que escribir otra forma de serializar / deserializar datos :


<pages>
    <page>
        <subPages>
            <subPage>
                <images>
                    <image>
                        <url/>
                    </image>
                </images>
            </subPage>
        </subPages>
    </page>
</pages>

Entonces, las preguntas generales de CouchDB son:

  1. ¿Qué reglas / principios utiliza para dividir sus documentos (relaciones, etc.)?
  2. ¿Está bien poner todo el sitio en un solo documento?
  3. Si es así, ¿cómo maneja la serialización / deserialización de documentos con niveles de profundidad arbitrarios (como el ejemplo grande de json anterior o el ejemplo xml)?
  4. ¿O no los convierte en VO, simplemente decide "estos están demasiado anidados en Object-Relational Map, así que accederé a ellos usando métodos XML / JSON sin procesar"?

Muchas gracias por su ayuda, el tema de cómo dividir sus datos con CouchDB ha sido difícil para mí decir "así es como debo hacerlo de ahora en adelante". Espero llegar pronto.

He estudiado los siguientes sitios / proyectos.

  1. Datos jerárquicos en CouchDB
  2. Wiki CouchDB
  3. Sofá - Aplicación CouchDB
  4. CouchDB La guía definitiva
  5. PeepCode CouchDB Screencast
  6. Sofá
  7. Léame de CouchDB

... pero aún no han respondido a esta pregunta.

Lance Pollard
fuente
2
wow, has escrito un ensayo completo aquí ... :-)
Eero
8
oye, esa es una buena pregunta
elmarco

Respuestas:

26

Ya ha habido excelentes respuestas a esto, pero quería agregar algunas características más recientes de CouchDB a la combinación de opciones para trabajar con la situación original descrita por viatropos.

El punto clave en el que dividir los documentos es donde puede haber conflictos (como se mencionó anteriormente). Nunca debe mantener juntos documentos masivamente "enredados" en un solo documento, ya que obtendrá una única ruta de revisión para actualizaciones completamente no relacionadas (la adición de comentarios agrega una revisión a todo el documento del sitio, por ejemplo). Administrar las relaciones o conexiones entre varios documentos más pequeños puede resultar confuso al principio, pero CouchDB ofrece varias opciones para combinar piezas dispares en respuestas únicas.

La primera gran es la clasificación de vistas. Cuando emite pares clave / valor en los resultados de una consulta de mapa / reducción, las claves se ordenan según la intercalación UTF-8 ("a" viene antes de "b"). Puede también claves complejas salida de su map / reduce como matrices JSON: ["a", "b", "c"]. Hacer eso le permitiría incluir una especie de "árbol" construido a partir de claves de matriz. Usando su ejemplo anterior, podemos generar el post_id, luego el tipo de cosa que estamos referenciando, luego su ID (si es necesario). Si luego generamos la identificación del documento referenciado en un objeto en el valor que se devuelve, podemos usar el parámetro de consulta 'include_docs' para incluir esos documentos en el mapa / reducir la salida:

{"rows":[
  {"key":["123412804910820", "post"], "value":null},
  {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
  {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
  {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

Solicitar esa misma vista con '? Include_docs = true' agregará una clave 'doc' que usará el '_id' al que se hace referencia en el objeto 'valor' o si no está presente en el objeto 'valor', usará el '_id' del documento desde el que se emitió la fila (en este caso, el documento 'post'). Tenga en cuenta que estos resultados incluirían un campo 'id' que hace referencia al documento fuente a partir del cual se realizó la emisión. Lo dejé por espacio y legibilidad.

Luego podemos usar los parámetros 'start_key' y 'end_key' para filtrar los resultados a los datos de una sola publicación:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
O incluso extraer específicamente la lista para un determinado tipo:
? start_key = ["123412804910820", "comentario"] & end_key = ["123412804910820", "comentario", {}]
Estas combinaciones de parámetros de consulta son posibles porque un objeto vacío (" {}") siempre está en la parte inferior de la intercalación y nulo o "" siempre están en la parte superior.

La segunda adición útil de CouchDB en estas situaciones es la función _list. Esto le permitiría ejecutar los resultados anteriores a través de un sistema de plantillas de algún tipo (si desea volver a HTML, XML, CSV o lo que sea), o generar una estructura JSON unificada si desea poder solicitar el contenido de una publicación completa (incluido autor y datos de comentario) con una sola solicitud y devuelto como un único documento JSON que coincide con lo que necesita su código de interfaz de usuario / cliente. Hacer eso le permitiría solicitar el documento de salida unificado de la publicación de esta manera:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
Su función _list (en este caso llamada "unificada") tomaría los resultados del mapa de vista / reducir (en este caso llamado "publicaciones") y los ejecutaría a través de una función de JavaScript que enviaría la respuesta HTTP en el tipo de contenido que usted necesita (JSON, HTML, etc.).

Combinando estas cosas, puede dividir sus documentos en cualquier nivel que considere útil y "seguro" para actualizaciones, conflictos y replicación, y luego volver a juntarlos según sea necesario cuando se soliciten.

Espero que ayude.

BigBlueHat
fuente
2
No estoy seguro de si esto ayudó a Lance, pero sé una cosa; ¡Definitivamente me ayudó mucho! ¡Esto es asombroso!
Mark
17

Sé que esta es una pregunta antigua, pero me la encontré tratando de encontrar el mejor enfoque para este mismo problema. Christopher Lenz escribió una bonita publicación de blog sobre métodos de modelado de "combinaciones" en CouchDB . Una de mis conclusiones fue: "La única forma de permitir la adición no conflictiva de datos relacionados es colocando esos datos relacionados en documentos separados". Entonces, en aras de la simplicidad, querrá inclinarse hacia la "desnormalización". Pero chocará con una barrera natural debido a escrituras conflictivas en ciertas circunstancias.

En su ejemplo de Publicaciones y Comentarios, si una sola publicación y todos sus comentarios vivieran en un documento, entonces dos personas que intentaran publicar un comentario al mismo tiempo (es decir, contra la misma revisión del documento) causarían un conflicto. Esto empeoraría aún más en el escenario de "todo el sitio en un solo documento".

Así que creo que la regla general sería "desnormalizar hasta que duela", pero el punto en el que "duele" es donde hay una alta probabilidad de que se publiquen varias ediciones en la misma revisión de un documento.

jake l
fuente
Interesante respuesta. Con eso en mente, uno debería preguntarse si cualquier sitio de tráfico razonablemente alto tendría todos los comentarios para una sola publicación de blog en un documento. Si leo esto bien, significa que cada vez que haya personas que agreguen comentarios en rápida sucesión, es posible que tenga que resolver conflictos. Por supuesto, no sé qué tan rápido en sucesión tendrían que ser para activar esto.
pc1oad1etter
1
En el caso de que los comentarios formen parte del documento en Couch, las publicaciones de comentarios simultáneas podrían entrar en conflicto porque su alcance de control de versiones es la "publicación" con todos sus comentarios. En el caso de que cada uno de sus objetos sea una colección de documentos, estos simplemente se convertirían en dos nuevos documentos de 'comentarios' con enlaces a la publicación y sin preocupaciones de colisión. También me gustaría señalar que la creación de vistas sobre el diseño de documentos "orientado a objetos" es sencillo: se pasa la clave de una publicación, por ejemplo, y luego se emiten todos los comentarios, ordenados por algún método, para esa publicación.
Riyad Kalla
16

El libro dice, si mal no recuerdo, desnormalizar hasta que "duela", teniendo en cuenta la frecuencia con la que sus documentos pueden actualizarse.

  1. ¿Qué reglas / principios utiliza para dividir sus documentos (relaciones, etc.)?

Como regla general, incluyo todos los datos necesarios para mostrar una página sobre el elemento en cuestión. En otras palabras, todo lo que imprimiría en una hoja de papel del mundo real que le entregaría a alguien. Por ejemplo, un documento de cotización de acciones incluiría el nombre de la empresa, el cambio, la moneda, además de los números; un documento de contrato incluiría los nombres y direcciones de las contrapartes, toda la información sobre fechas y signatarios. Pero las cotizaciones de acciones de distintas fechas formarían documentos separados, los contratos separados formarían documentos separados.

  1. ¿Está bien poner todo el sitio en un solo documento?

No, eso sería una tontería, porque:

  • tendría que leer y escribir el sitio completo (el documento) en cada actualización, y eso es muy ineficiente;
  • no se beneficiaría de ningún almacenamiento en caché de vistas.
Eero
fuente
3
Gracias por hablar un poco conmigo. Tengo la idea de "incluir todos los datos que se necesitan para mostrar una página con respecto al elemento en cuestión", pero eso sigue siendo muy difícil de implementar. Una "página" podría ser una página de Comentarios, una página de Usuarios, una página de Publicaciones o una página de Comentarios y Publicaciones, etc. ¿Cómo los dividiría entonces, principalmente? También puede mostrar su contrato con los usuarios. Obtengo los documentos 'en forma', tiene sentido mantenerlos separados.
Lance Pollard
6

Creo que la respuesta de Jake destaca uno de los aspectos más importantes de trabajar con CouchDB que puede ayudarlo a tomar la decisión del alcance: los conflictos.

En el caso de que tenga comentarios como una propiedad de matriz de la publicación en sí, y solo tenga una base de datos de 'publicación' con un montón de documentos de 'publicación' enormes, como Jake y otros señalaron correctamente, podría imaginar un escenario en una publicación de blog realmente popular en la que dos usuarios envían ediciones al documento de la publicación simultáneamente, lo que resulta en una colisión y un conflicto de versión para ese documento.

A UN LADO: Como se señala en este artículo , también considere que cada vez que solicita / actualiza ese documento debe obtener / configurar el documento en su totalidad, por lo que debe pasar documentos masivos que representen el sitio completo o una publicación con mucho de comentarios al respecto puede convertirse en un problema que desearía evitar.

En el caso de que las publicaciones se modelen por separado de los comentarios y dos personas envíen un comentario sobre una historia, simplemente se convierten en dos documentos de "comentarios" en esa base de datos, sin ningún problema de conflicto; sólo dos operaciones PUT para agregar dos nuevos comentarios a la base de datos "comentario".

Luego, para escribir las vistas que le devuelven los comentarios de una publicación, pasaría el postID y luego emitiría todos los comentarios que hacen referencia a la ID de la publicación principal, ordenados en un orden lógico. Tal vez incluso pase algo como [postID, byUsername] como clave de la vista de 'comentarios' para indicar la publicación principal y cómo desea que se ordenen los resultados o algo por el estilo.

MongoDB maneja los documentos de manera un poco diferente, permitiendo que los índices se construyan en subelementos de un documento, por lo que es posible que vea la misma pregunta en la lista de correo de MongoDB y alguien que diga "simplemente haga que los comentarios sean una propiedad de la publicación principal".

Debido al bloqueo de escritura y la naturaleza de maestro único de Mongo, el problema de revisión conflictivo de dos personas que agregan comentarios no surgiría allí y la capacidad de consulta del contenido, como se mencionó, no se ve afectada demasiado mal debido a sub- índices.

Dicho esto, si sus subelementos en cualquiera de las bases de datos van a ser enormes (digamos decenas de miles de comentarios), creo que es la recomendación de ambos bandos hacer esos elementos separados; Ciertamente he visto que ese es el caso con Mongo, ya que existen algunos límites superiores sobre el tamaño que puede tener un documento y sus subelementos.

Riyad Kalla
fuente
Muy útil. Gracias
Ray Suelzer