Princípios para modelagem de documentos do CouchDB


120

Tenho uma pergunta que estou tentando responder há algum tempo, mas não consigo descobrir:

Como você projeta ou divide documentos do CouchDB?

Veja uma postagem no blog, por exemplo.

A maneira semi-relacional de fazer isso seria criar alguns objetos:

  • Postar
  • Do utilizador
  • Comente
  • Tag
  • Snippet

Isso faz muito sentido. Mas estou tentando usar o couchdb (por todas as razões pelas quais isso é ótimo) para modelar a mesma coisa e tem sido extremamente difícil.

A maioria das postagens do blog fornece um exemplo fácil de como fazer isso. Eles basicamente dividem da mesma maneira, mas dizem que você pode adicionar propriedades 'arbitrárias' a cada documento, o que é definitivamente bom. Então, você teria algo parecido com isto no CouchDB:

  • Postagem (com tags e trechos de modelos "pseudo" no documento)
  • Comente
  • Do utilizador

Algumas pessoas diriam até que você poderia lançar o Comentário e o Usuário, então você teria o seguinte:


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"
        }
    }
}

Parece muito bom e fácil de entender. Também entendo como você pode escrever visualizações que extraíram apenas os comentários de todos os seus documentos de postagem, para inseri-los nos modelos de comentários, o mesmo com Usuários e Tags.

Mas então penso: "por que não colocar todo o site em um único 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"
                    ...
                }
            }
        }
    }
}

Você pode facilmente criar visualizações para encontrar o que deseja com isso.

Então a pergunta que tenho é: como você determina quando dividir o documento em documentos menores ou quando fazer "RELAÇÕES" entre os documentos?

Eu acho que seria muito mais "orientado a objetos" e mais fácil mapear para objetos de valor, se estivesse dividido assim:


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
    }
}

... mas depois começa a parecer mais um Banco de Dados Relacional. E, muitas vezes, herdo algo que se parece com o "site inteiro em um documento", por isso é mais difícil modelá-lo com relações.

Eu já li muitas coisas sobre como / quando usar bancos de dados relacionais versus bancos de dados de documentos, portanto esse não é o principal problema aqui. Só estou pensando: qual é uma boa regra / princípio a ser aplicado ao modelar dados no CouchDB.

Outro exemplo é com arquivos / dados XML. Alguns dados XML possuem mais de 10 níveis de profundidade e eu gostaria de visualizar usando o mesmo cliente (Ajax on Rails, por exemplo, ou Flex) que renderizaria JSON do ActiveRecord, CouchRest ou qualquer outro mapeador de objetos. Às vezes, recebo enormes arquivos XML que são toda a estrutura do site, como a abaixo, e eu preciso mapeá-lo para o Value Objects para usar no meu aplicativo Rails, para que não precise escrever outra maneira de serializar / desserializar dados :


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

Portanto, as perguntas gerais do CouchDB são:

  1. Quais regras / princípios você usa para dividir seus documentos (relacionamentos, etc)?
  2. É possível colocar o site inteiro em um documento?
  3. Em caso afirmativo, como você lida com a serialização / desserialização de documentos com níveis arbitrários de profundidade (como o grande exemplo de json acima ou o exemplo de xml)?
  4. Ou você não os transforma em VOs, apenas decide "estes são aninhados demais ao Mapa Objeto-Relacional, então eu os acessarei usando métodos XML / JSON brutos"?

Muito obrigado por sua ajuda, a questão de como dividir seus dados com o CouchDB tem sido difícil para mim dizer "é assim que devo fazê-lo a partir de agora". Espero chegar lá em breve.

Estudei os seguintes sites / projetos.

  1. Dados hierárquicos no CouchDB
  2. CouchDB Wiki
  3. Sofá - CouchDB App
  4. CouchDB O Guia Definitivo
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. Leia-me do CouchDB

... mas eles ainda não responderam a esta pergunta.


2
wow você escreveu um ensaio inteiro aqui ... :-)
Eero

8
hey, isso é uma boa pergunta
Elmarco

Respostas:


26

Já existem ótimas respostas para isso, mas eu queria adicionar alguns recursos mais recentes do CouchDB à mistura de opções para trabalhar com a situação original descrita por viatropos.

O ponto principal no qual a divisão de documentos é onde pode haver conflitos (como mencionado anteriormente). Você nunca deve manter documentos massivamente "emaranhados" juntos em um único documento, pois terá um único caminho de revisão para atualizações completamente não relacionadas (além de comentários, adicionando uma revisão ao documento inteiro do site, por exemplo). Gerenciar os relacionamentos ou conexões entre vários documentos menores pode ser confuso no início, mas o CouchDB fornece várias opções para combinar partes diferentes em respostas únicas.

O primeiro grande problema é o agrupamento de visualizações. Quando você emite pares de chave / valor nos resultados de uma consulta de mapa / redução, as chaves são classificadas com base no agrupamento UTF-8 ("a" vem antes de "b"). Você pode também chaves complexas de saída do seu mapa / reduzir, matrizes JSON: ["a", "b", "c"]. Isso permitiria incluir uma "árvore" dos tipos criados com base nas chaves da matriz. Usando seu exemplo acima, podemos gerar o post_id, o tipo de coisa que estamos referenciando e o ID (se necessário). Se, em seguida, enviarmos o ID do documento referenciado para um objeto no valor retornado, podemos usar o parâmetro de consulta 'include_docs' para incluir esses documentos no mapa / reduzir a saída:

{"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 a mesma visualização com '? Include_docs = true' adicionará uma chave 'doc' que usará o '_id' referenciado no objeto 'value' ou se isso não estiver presente no objeto 'value', ele usará o '_id' do documento a partir do qual a linha foi emitida (neste caso, o documento 'post'). Observe que esses resultados incluem um campo 'id' que faz referência ao documento de origem a partir do qual a emissão foi feita. Deixei de fora por espaço e legibilidade.

Em seguida, podemos usar os parâmetros 'start_key' e 'end_key' para filtrar os resultados nos dados de uma única postagem:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
Ou, ainda, extraia especificamente a lista para um determinado tipo:
? start_key = ["123412804910820", "comentário"] & end_key = ["123412804910820", "comentário", {}]
Essas combinações de parâmetros de consulta são possíveis porque um objeto vazio (" {}") está sempre na parte inferior do agrupamento e nulo ou "" sempre na parte superior.

A segunda adição útil do CouchDB nessas situações é a função _list. Isso permitiria que você executasse os resultados acima por meio de um sistema de modelo de algum tipo (se você quiser HTML, XML, CSV ou qualquer outro material de volta) ou produza uma estrutura JSON unificada se desejar solicitar o conteúdo de uma postagem inteira (incluindo dados de autor e comentário) com uma única solicitação e retornado como um único documento JSON que corresponde ao que o seu código do cliente / UI precisa. Isso permitiria que você solicitasse o documento de saída unificado da postagem da seguinte maneira:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
Sua função _list (nesse caso denominada "unificada") pegaria os resultados do mapa de visualização / redução (nesse caso denominada "postagens") e executaria-os através de uma função JavaScript que enviaria de volta a resposta HTTP no tipo de conteúdo que você necessidade (JSON, HTML, etc).

Combinando essas coisas, você pode dividir seus documentos em qualquer nível que achar útil e "seguro" para atualizações, conflitos e replicação e, em seguida, reuni-los conforme necessário quando solicitados.

Espero que ajude.


2
Não tenho certeza se isso ajudou Lance, mas sei uma coisa; definitivamente me ajudou bastante! Isso é incrível!
Mark

17

Sei que essa é uma pergunta antiga, mas me deparei com ela tentando descobrir a melhor abordagem para esse mesmo problema. Christopher Lenz escreveu um bom post sobre métodos de modelagem de "junções" no CouchDB . Uma das minhas conclusões foi: "A única maneira de permitir a adição não conflitante de dados relacionados é colocando esses dados em documentos separados". Então, por uma questão de simplicidade, você gostaria de se inclinar para a "desnormalização". Mas você atingirá uma barreira natural devido a gravações conflitantes em determinadas circunstâncias.

No seu exemplo de Postagens e Comentários, se uma única postagem e todos os seus comentários residissem em um documento, duas pessoas tentando postar um comentário ao mesmo tempo (ou seja, contra a mesma revisão do documento) causariam um conflito. Isso pioraria ainda mais no cenário de "site inteiro em um único documento".

Portanto, acho que a regra geral seria "desnormalizar até doer", mas o ponto em que "doerá" é onde você tem uma alta probabilidade de várias edições serem postadas na mesma revisão de um documento.


Resposta interessante. Com isso em mente, deve-se questionar se algum site de tráfego razoavelmente alto teria todos os comentários para uma única postagem de blog em um documento. Se eu leio direito, significa que sempre que houver pessoas adicionando comentários em rápida sucessão, talvez seja necessário resolver conflitos. Claro, eu não sei o quão rápido eles teriam que ser para desencadear isso.
pc1oad1etter

1
No caso em que os comentários fazem parte do documento no Couch, as postagens simultâneas podem entrar em conflito porque o seu escopo de versão é a "postagem" com todos os seus comentários. No caso em que cada um dos seus objetos são coleções de documentos, eles simplesmente se tornam dois novos documentos de 'comentário' com links para a postagem e sem preocupação com colisões. Eu também apontaria que a criação de visualizações no design de documentos "orientado a objetos" é simples - você passa a chave de uma postagem, por exemplo, e emite todos os comentários, classificados por algum método, para essa publicação.
Riyad Kalla

16

O livro diz, se bem me lembro, desnormalizar até "dói", mantendo em mente a frequência com que seus documentos podem ser atualizados.

  1. Quais regras / princípios você usa para dividir seus documentos (relacionamentos, etc)?

Como regra geral, incluo todos os dados necessários para exibir uma página referente ao item em questão. Em outras palavras, tudo o que você imprimiria em um pedaço de papel do mundo real que entregaria a alguém. Por exemplo, um documento de cotação de ações incluiria o nome da empresa, a bolsa, a moeda, além dos números; um documento de contrato incluiria os nomes e endereços das contrapartes, todas as informações sobre datas e signatários. Porém, cotações de ações de datas distintas formariam documentos separados, contratos separados formariam documentos separados.

  1. É possível colocar o site inteiro em um documento?

Não, isso seria bobagem, porque:

  • você teria que ler e escrever o site inteiro (o documento) em cada atualização, e isso é muito ineficiente;
  • você não se beneficiaria de nenhum cache de exibição.

3
Obrigado por entrar um pouco comigo. Tenho a ideia de "incluir todos os dados necessários para exibir uma página sobre o item em questão", mas isso ainda é muito difícil de implementar. Uma "página" pode ser uma página de Comentários, uma página de Usuários, uma página de Postagens ou uma página de Comentários e Postagens, etc. Como você as dividiria, principalmente? Você também pode exibir seu contrato com os usuários. Recebo os documentos "em forma de formulário", que faz sentido mantê-los separados.
Lance Pollard

6

Acho que a resposta de Jake identifica um dos aspectos mais importantes do trabalho com o CouchDB que pode ajudá-lo a tomar a decisão do escopo: conflitos.

No caso em que você tem comentários como uma propriedade de matriz da própria postagem, e você apenas possui um banco de dados 'post' com vários documentos 'post' enormes, como Jake e outros apontaram corretamente, você pode imaginar um cenário em uma postagem de blog muito popular, na qual dois usuários enviam edições para o documento de postagem simultaneamente, resultando em uma colisão e um conflito de versão para esse documento.

AO LADO: Como este artigo indica , também considere que, toda vez que você estiver solicitando / atualizando esse documento, precisará obter / definir o documento por inteiro, distribuindo documentos enormes que representam o site inteiro ou uma publicação com muito de comentários pode se tornar um problema que você deseja evitar.

No caso em que as postagens são modeladas separadamente dos comentários e duas pessoas enviam um comentário em uma história, elas simplesmente se tornam dois documentos de "comentário" nesse banco de dados, sem problemas de conflito; apenas duas operações PUT para adicionar dois novos comentários ao banco de dados "comment".

Em seguida, para escrever as visualizações que retornam os comentários para uma postagem, você passaria o ID do post e emitiria todos os comentários que referenciam esse ID da postagem pai, classificados em alguma ordem lógica. Talvez você passe algo como [postID, byUsername] como a chave para a exibição 'comments' para indicar a postagem pai e como você deseja que os resultados sejam classificados ou algo nesse sentido.

O MongoDB lida com os documentos de maneira um pouco diferente, permitindo que os índices sejam construídos nos subelementos de um documento, para que você possa ver a mesma pergunta na lista de discussão do MongoDB e alguém dizendo "apenas faça dos comentários uma propriedade da postagem principal".

Devido ao bloqueio de gravação e à natureza de mestre único do Mongo, a questão conflitante de revisão de duas pessoas adicionando comentários não surgiria lá e a capacidade de consulta do conteúdo, como mencionado, não é afetada muito mal por causa de sub- índices.

Dito isto, se seus subelementos em ambos os bancos de dados forem enormes (digamos 10s de milhares de comentários), acredito que é a recomendação de ambos os campos fazer esses elementos separados; Certamente vi que esse é o caso do Mongo, pois existem alguns limites superiores quanto ao tamanho de um documento e seus subelementos.


Muito útil. Obrigado
Ray Suelzer
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.