Uma das grandes questões de modelagem de bancos de dados de documentos é: até onde devo ir com a desnormalização?
Quando trabalhamos com bancos de dados relacionais, estamos acostumados a normalizar estritamente nossos dados: mantemos uma instância canônica, não duplicada, de cada item de dados. Isso nos dá um escopo quase ilimitado para a capacidade de consulta e facilita a consistência.
Em minha postagem anterior sobre modelagem de dadosNa seção "Bancos de dados de documentos", vimos como os bancos de dados de documentos adotam uma abordagem diferente. Os bancos de dados de documentos são otimizados para armazenar juntos os dados que acessamos juntos e, a partir disso, surgem vários outros benefícios, como velocidade, capacidade de distribuição, preservação da estrutura do documento e assim por diante.
Naquela postagem, dei o exemplo de um sistema de gerenciamento de brindes em que um pedido seria armazenado como um documento JSON, em vez de ser dividido em várias tabelas e depois reconstruído a cada vez usando uma consulta SQL.
Esse modelo, de armazenamento de agregados de dados, é naturalmente desnormalizado. Entretanto, isso não significa que devemos criar documentos monolíticos para cada contexto.
Em vez disso, devemos considerar duas opções: podemos incorporar dados de vários lugares em um documento ou podemos armazenar referências a outros documentos.
Acesso a documentos de valor-chave
Com o Couchbase, temos duas maneiras de acessar nossos dados: consultas e visualizações de valores-chave (índices secundários gerados automaticamente a partir de consultas de redução de mapas).
Quando N1QL estiver disponível de forma geral, serão três.
Tudo o que examinamos nesta postagem é baseado em pesquisas de valores-chave.
Uma rápida recapitulação de nosso sistema de exemplo
Em minha postagem anterior, vimos o exemplo de um sistema de gerenciamento de estoque para rastrear brindes com a marca Couchbase.
Vamos imaginar que o caminho padrão seja:
- Um cliente faz um pedido.
- Um selecionador de estoque recebe o pedido e embala os itens.
- Um despachante envia o pacote por meio de um serviço de entrega.
No momento em que o cliente faz um pedido, temos a opção de escolher como armazenar os dados do pedido no Couchbase:
- incorporar todas as informações do pedido em um único documento
- ou mantenha uma cópia principal de cada registro envolvido e faça referência a ele no documento do pedido.
Incorporação
Se optássemos por incorporar todos os dados em um documento, poderíamos ter um resultado como este:
"orderID": 200,
"cliente":
{
"nome": "Steve Rothery",
"endereço": "11-21 Paul Street",
"city": "London"
},
"produtos":
[
{
"itemCode": "RedTShirt",
"itemName": "Camiseta vermelha do Couchbase",
"fornecedor": "Lovely t-shirt company",
"localização": "warehouse 1, aisle 3, location 4",
"quantityOrdered": 3
},
{
"itemCode": "USB",
"fornecedor": "Memorysticks Foreva",
"itemName": "Pendrive USB preto de 8 GB com o logotipo vermelho do Couchbase",
"localização": "warehouse 1, aisle 42, location 12",
"quantityOrder": 51
}
],
"status": "paid"
}
Aqui, tudo o que precisamos para atender ao pedido é armazenado em um único documento.
Apesar de termos documentos separados de perfil do cliente e de detalhes do item, replicamos partes de seus dados no documento do pedido.
Isso pode parecer um desperdício ou até mesmo perigoso, se você vier do mundo relacional.
No entanto, isso é bastante normal em um banco de dados de documentos. Como vimos anteriormente, os bancos de dados de documentos operam com a ideia de que um documento pode armazenar tudo o que você precisa para uma situação específica.
No entanto, há algumas vantagens e desvantagens em incorporar dados dessa forma.
Primeiro, vamos ver o que é potencialmente ruim:
- Inconsistência: se Steve quiser atualizar seu endereço depois que o pedido for feito, estaremos contando com ele:
- nosso código de aplicativo deve ser robusto o suficiente para localizar todas as instâncias do endereço dele no banco de dados e atualizá-lo
- nada de errado na rede, no banco de dados ou em qualquer outro lugar que impeça a conclusão total da atualização.
- Capacidade de consulta: ao fazer várias cópias dos mesmos dados, pode ser mais difícil consultar os dados que replicamos, pois teremos que filtrar todas as cópias incorporadas.
- Tamanho: você pode acabar com documentos grandes que consistem em muitos dados duplicados.
- Mais documentos: essa não é uma preocupação importante, mas pode ter alguns impactos, como o tamanho de seu conjunto de trabalho em cache.
Então, quais são os benefícios que a incorporação nos proporciona? Principalmente, ela nos proporciona:
- Velocidade de acesso: incorporar tudo em um documento significa que precisamos de apenas uma consulta ao banco de dados.
- Tolerância a falhas potencialmente maior no momento da leitura: em um banco de dados distribuído, nossos documentos referidos estariam em várias máquinas, portanto, ao incorporar, estamos introduzindo menos oportunidades para que algo dê errado e estamos simplificando o lado do aplicativo.
Quando incorporar
Talvez você queira incorporar dados quando:
- As leituras superam em muito o número de escritas.
- Você se sente confortável com o pequeno risco de dados inconsistentes nas várias cópias.
- Você está otimizando a velocidade de acesso.
Por que estamos perguntando se as leituras superam as gravações?
Em nosso exemplo acima, cada vez que alguém lê nosso pedido, é provável que também atualize o estado do pedido:
- alguém no depósito lê o documento do pedido e atualiza o status para "Retirado", assim que terminar
- um dos membros da nossa equipe de despacho lê o documento e atualiza o status para "Despachado" quando o pacote estiver com a transportadora
- Quando recebemos um aviso de entrega automatizado do correio, nosso aplicativo atualiza o status do documento para "Entregue".
Portanto, aqui as leituras e gravações provavelmente serão bastante equilibradas.
Imagine, porém, que adicionamos um blog ao nosso sistema de gerenciamento de brindes e, em seguida, escrevemos uma postagem sobre nosso novo carregador USB com a marca Couchbase. Faríamos duas, talvez três, gravações no documento enquanto ajustávamos nossa postagem. Depois, durante o resto da vida útil do documento, tudo será lido. Se a publicação for popular, poderemos ver um número cem ou mil vezes maior de leituras do que de gravações.
Como os benefícios da incorporação ocorrem no momento da leitura e os riscos principalmente no momento da gravação, parece razoável incorporar todo o conteúdo da página do post do blog em um documento em vez de, por exemplo, extrair os detalhes do autor de um documento de perfil separado.
Há outro motivo convincente para incorporar dados:
- Na verdade, você deseja manter cópias separadas e divergentes dos dados.
Em nosso pedido de brindes acima, estamos usando o endereço do cliente como endereço de envio. Ao incorporar o endereço de envio, como estamos fazendo, podemos facilmente oferecer a opção de escolher um endereço de envio diferente para cada pedido. Também obtemos um registro histórico do destino de cada pedido, mesmo que o cliente altere posteriormente o endereço armazenado em seu perfil.
Necessidade de velocidade
O Couchbase é rápido.
Todas as gravações são feitas no cache gerenciado e, se dimensionarmos nossa RAM adequadamente, nosso conjunto de trabalho será servido a partir desse cache.
Isso altera a ponderação das compensações que fazemos. A incorporação principalmente por velocidade é menos atraente com o Couchbase.
Com o Couchbase, o tempo médio de operação de leitura/gravação é de meio milissegundo, portanto, não faz mal quando uma única consulta exige três ou quatro operações.
Referência
Outra maneira de representar nosso pedido seria fazer referência ao documento de perfil do usuário e ao documento de detalhes do item de estoque, mas não puxar seu conteúdo para o documento do pedido.
Imaginemos que os perfis de nossos clientes sejam digitados pelo endereço de e-mail do cliente e que nossos itens de estoque sejam digitados por um código de estoque. Podemos usá-los para nos referirmos aos documentos originais:
"orderID": 200,
"customer": "steve@gmail.com",
"produtos":
[
{
"itemCode": "RedTShirt",
"quantityOrdered": 3
},
{
"itemCode": "USB",
"quantityOrder": 51
}
],
"status": "paid"
}
Quando visualizamos o pedido de Steve, podemos preencher os detalhes com mais três leituras: seu perfil de usuário (digitado pelo endereço de e-mail) e os detalhes do item de estoque (digitados pelos códigos de item).
Isso requer três leituras adicionais, mas nos traz alguns benefícios:
- Consistência: estamos mantendo uma cópia canônica das informações do perfil de Steve e dos detalhes do item de estoque.
- Capacidade de consulta: desta vez, quando consultamos o conjunto de dados, podemos ter mais certeza de que os resultados são as versões canônicas dos dados em vez de cópias incorporadas.
- Melhor uso do cache: como estamos acessando os documentos canônicos com frequência, eles permanecerão em nosso cache, em vez de ter várias cópias incorporadas que são acessadas com menos frequência e, portanto, saem do cache.
- Uso mais eficiente do hardware: a incorporação de dados nos fornece documentos maiores com várias cópias dos mesmos dados; isso ajuda a reduzir o disco e a RAM necessários para o nosso banco de dados.
Há também desvantagens:
- Várias pesquisas: isso é principalmente uma consideração para as falhas de cache, pois o tempo de leitura aumenta sempre que precisamos ler do disco.
- A consistência é imposta: a referência a uma versão canônica de um documento significa que as atualizações desse documento serão refletidas em todos os contextos em que ele for usado.
Quando encaminhar
Referir-se a instâncias canônicas de documentos é um bom padrão ao modelar com o Couchbase. Você deve estar especialmente interessado em usar referências quando:
- A consistência dos dados é uma prioridade.
- Você quer garantir que seu cache seja usado de forma eficiente.
- A versão incorporada seria muito pesada.
Esse último ponto é particularmente importante quando seus documentos têm um potencial de crescimento ilimitado.
Imagine que estivéssemos armazenando registros de atividades relacionados a cada usuário do nosso sistema. A inclusão desses registros no perfil do usuário poderia resultar em um documento bastante grande.
É improvável que violemos o limite máximo de 20 MB do Couchbase para um documento individual, mas o processamento do documento no lado do aplicativo seria menos eficiente à medida que o elemento de registro do perfil crescesse. Seria muito mais eficiente fazer referência a um documento separado, ou talvez a documentos paginados, que contenham os registros.
Desnormalize todas as coisas! (Exceto quando você não o faz)
A desnormalização é uma parte fundamental do trabalho com bancos de dados de documentos, mas isso não significa que não possamos manter registros canônicos e fazer referência a eles em outros documentos.
De fato, a referência a documentos costuma ser a maneira mais eficiente de trabalhar com o Couchbase e devemos incorporá-la em situações específicas, como cargas de trabalho de leitura intensa.
Na próxima vez, examinarei os principais padrões de nomenclatura e como mantemos o esquema em um banco de dados de esquema flexível.
Não entendi por que o seguinte é uma desvantagem ou um problema em geral?
A consistência é imposta: a referência a uma versão canônica de um documento significa que as atualizações desse documento serão refletidas em todos os contextos em que ele for usado.
A parte 3 está aqui: https://www.couchbase.com/data-modelling-key-design/