Design de esquema do MongoDB: melhores práticas de modelagem de dados
Avalie esse Tutorial
Você já se perguntou: "Como modelar um esquema para meu aplicativo?" É uma das perguntas mais comuns que os desenvolvedores têm em relação ao MongoDB. E a resposta é: depende. Isso ocorre porque os bancos de dados de documentos possuem um vocabulário rico que é capaz de expressar relacionamentos de dados de maneiras mais diferenciadas do que o SQL. Há muitas coisas a serem consideradas ao escolher um esquema. Seu aplicativo é pesado para leitura ou gravação? Quais dados são frequentemente acessados juntos? Quais são suas considerações de desempenho? Como seu conjunto de dados crescerá e será dimensionado?
Nesta publicação, discutiremos os fundamentos da modelagem de dados usando exemplos do mundo real. Você aprenderá metodologias e vocabulário comuns que podem ser usados ao projetar o esquema de banco de dados para seu aplicativo.
Ok, em primeiro lugar, você estava ciente de que o projeto de esquema do MongoDB adequado é a parte mais importante da implantação de um banco de dados dimensionável, rápido e acessível? É verdade, e o projeto de esquema geralmente é uma das facetas mais ignoradas da administração do MongoDB. Por que o projeto de esquema do MongoDB é tão importante? Bem, há alguns bons motivos. Na minha experiência, a maioria das pessoas que chega ao MongoDB tende a pensar no projeto do esquema do MongoDB como o mesmo projeto do esquema relacional legado, o que não permite que você aproveite ao máximo tudo o que os bancos de dados MongoDB têm a oferecer. Primeiro, vejamos como o projeto de banco de dados relacional legado se compara ao projeto de esquema do MongoDB.
Quando se trata de projeto de esquema do MongoDB database, é nisso que a maioria dos desenvolvedores pensa quando está considerando projetar um esquema relacional e um esquema MongoDB.
Tenho que admitir que entendo o impulso de projetar o esquema do MongoDB da mesma forma que você sempre projetou o esquema do SQL. É completamente normal querer fazer a divisão dos seus dados em pequenas tabelas organizadas como você sempre fez antes. Eu fazia isso quando estava aprendendo a usar o MongoDB. Entretanto, como veremos em breve, você perde muitos dos recursos incríveis do MongoDB quando projeta seu esquema como um esquema SQL.
E é assim que me sinto.
No entanto, acho que é melhor comparar o projeto de esquema do MongoDB com o projeto do esquema relacional, pois é daí que muitos desenvolvedores que estão chegando ao MongoDB estão vindo. Então, vamos ver como esses dois padrões de projetos se diferem.
Ao projetar um esquema relacional, normalmente os desenvolvedores modelam seu esquema independente das queries. Eles se perguntam: "Que dados eu tenho?" Em seguida, usando abordagens prescritas, eles normalizarão (geralmente em 3ª forma normal). O tl;dr da normalização é dividir seus dados em tabelas, para não duplicar dados. Vamos dar uma olhada em um exemplo de como você modelaria alguns dados de usuário em um banco de dados relacional.
Neste exemplo, você pode ver que os dados do usuário estão divididos em tabelas separadas e podem ser unidos usando chaves estrangeiras na coluna
user_id
da tabela Professions and Cars. Agora, vamos dar uma olhada em como podemos modelar esses mesmos dados no MongoDB.Agora, o projeto de esquema do MongoDB funciona de forma muito diferente do projeto de esquema relacional. Com o projeto de esquema do MongoDB não há:
- Nenhum processo formal
- Nenhum algoritmo
- Nenhuma regra
Quando estiver criando o projeto de esquema do MongoDB, a única coisa que importa é projetar um esquema que funcione bem para o seu aplicativo. Dois aplicativos diferentes que usam exatamente os mesmos dados podem ter esquemas muito diferentes se os aplicativos forem usados de forma diferente. Ao projetar um esquema, devemos levar em consideração o seguinte:
- Armazenar os dados
- Fornecer bom desempenho de query
- Exigir uma quantidade razoável de hardware
Vamos dar uma olhada em como podemos formar o modelo de usuário relacional no MongoDB.
1 { 2 "first_name": "Paul", 3 "surname": "Miller", 4 "cell": "447557505611", 5 "city": "London", 6 "location": [45.123, 47.232], 7 "profession": ["banking", "finance", "trader"], 8 "cars": [ 9 { 10 "model": "Bentley", 11 "year": 1973 12 }, 13 { 14 "model": "Rolls Royce", 15 "year": 1965 16 } 17 ] 18 }
Você pode ver que, em vez de dividir os dados em coleções ou documentos separados, aproveitamos o design baseado em documentos do MongoDB para incorporar dados em arrays e objetos dentro do objeto de usuário. Agora podemos fazer uma query simples para reunir todos esses dados para nosso aplicativo.
O design do esquema MongoDB, na verdade, se resume a apenas duas opções para cada dado. Você pode incorporar esses dados diretamente ou fazer referência a outro dado usando o operador $lookup (semelhante a um JOIN). Vejamos os prós e os contras de usar cada opção em seu esquema.
- Você pode recuperar todas as informações relevantes em uma única query.
- Atualize informações relacionadas como uma única operação atômica. Por padrão, todas as operações CRUD em um único documento são compatíveis com ACID.
- No entanto, se você precisar de uma transação em várias operações, poderá usar o operador de transação.
- Embora as transações estejam disponíveis a partir de 4.0, devo acrescentar que é um antipadrão ser excessivamente dependente delas em seu aplicativo.
- Documentos grandes significam mais sobrecarga se a maioria dos campos não for relevante. Você pode aumentar o desempenho da query limitando o tamanho dos documentos que você está enviando pela rede para cada query.
- Há um limite de tamanho de documento de 16 MB no MongoDB. Se você estiver incorporando muitos dados em um único documento, poderá atingir esse limite.
Ok, então a outra opção para projetar nosso esquema é referenciar outro documento usando um ID de objeto exclusivo de um documento e conectá-los usando o operador $lookup. A referência funciona da mesma forma que o operador JOIN em uma consulta SQL. Ele nos permite fazer a divisão dos dados para fazer queries mais eficientes e dimensionáveis e, ainda assim, manter os relacionamentos entre os dados.
- Ao dividir os dados, você terá documentos menores.
- Informações acessadas com pouca frequência, não necessárias em todas as queries.
- Reduzir a quantidade de duplicação de dados. No entanto, é importante observar que a duplicação de dados não deve ser evitada se resultar em um esquema melhor.
- Para recuperar todos os dados nos documentos referenciados, são necessárias, no mínimo duas queries ou $lookup para recuperar todas as informações.
Agora que exploramos as duas maneiras de fazer a divisão dos dados ao projetar nossos esquemas no MongoDB, vamos examinar os relacionamentos comuns com os quais você provavelmente está familiarizado com a modelagem se tiver experiência em SQL. Começaremos com os relacionamentos mais simples e trabalharemos até chegar a alguns padrões e relacionamentos interessantes e como os modelamos com exemplos do mundo real. Observe que aqui vamos apenas arranhar a superfície do relacionamento de modelagem no MongoDB.
Também é importante observar que, mesmo que seu aplicativo tenha os mesmos dados exatos que os exemplos listados abaixo, você pode ter um esquema completamente diferente do esquema que descrevi aqui. Isso ocorre porque a consideração mais importante que você faz sobre o esquema é como seus dados serão usados pelo sistema. Em cada exemplo, descreverei os requisitos de cada aplicativo e por que determinado esquema foi usado para esse exemplo. Se você quiser discutir as especificidades do seu esquema, certifique-se de abrir uma conversa no Fórum da MongoDB Community, e todos nós podemos discutir o que funcionará melhor para seu aplicativo exclusivo.
Vamos dar uma olhada no nosso documento de usuário. Este exemplo contém ótimos dados individualizados. Por exemplo, em nosso sistema, um usuário só pode ter um nome. Portanto, este seria um exemplo de um relacionamento um-para-um. Podemos modelar todos os dados um-para-um como pares de valor-chave em nosso banco de dados.
1 { 2 "_id": "ObjectId('AAA')", 3 "name": "Joe Karlsson", 4 "company": "MongoDB", 5 "twitter": "@JoeKarlsson1", 6 "twitch": "joe_karlsson", 7 "tiktok": "joekarlsson", 8 "website": "joekarlsson.com" 9 }
O DJ Khalid aprovaria.
Um para um tl;dr:
- Prefira o par chave-valor incorporado no documento.
- Por exemplo, um funcionário pode trabalhar em um e apenas um departamento.
Digamos que estejamos lidando com uma pequena sequência de dados associados aos nossos usuários. Por exemplo, talvez precisemos armazenar vários endereços associados a um determinado usuário. É improvável que um usuário do nosso aplicativo tenha mais do que alguns endereços diferentes. Para relacionamentos como esse, o definiríamos como um relacionamento de um para poucos.
1 { 2 "_id": "ObjectId('AAA')", 3 "name": "Joe Karlsson", 4 "company": "MongoDB", 5 "twitter": "@JoeKarlsson1", 6 "twitch": "joe_karlsson", 7 "tiktok": "joekarlsson", 8 "website": "joekarlsson.com", 9 "addresses": [ 10 { "street": "123 Sesame St", "city": "Anytown", "cc": "USA" }, 11 { "street": "123 Avenue Q", "city": "New York", "cc": "USA" } 12 ] 13 }
Lembra quando eu disse que não há regras para o projeto de esquema do MongoDB? Bem, eu menti. Criei algumas regras úteis para ajudar você a projetar o esquema para seu aplicativo.
Regra 1: favoreça a incorporação, a menos que haja um motivo convincente para não fazê-lo.
De modo geral, minha ação padrão é incorporar dados em um documento. Eu o retiro e faço referência a ele somente se precisar acessá-lo sozinho, se for muito grande, se raramente precisar dele ou por qualquer outro motivo.
Um para poucos tl;dr:
- Prefira a incorporação para relacionamentos de um para poucos.
Digamos que você esteja criando uma página de produto para um site de comércio eletrônico e precise projetar um esquema capaz de exibir informações sobre o produto. Em nosso sistema, salvamos informações sobre todas as muitas peças que compõem cada produto para serviços de reparo. Como você criaria um esquema para salvar todos esses dados, mas ainda assim manter o desempenho da página do produto? Talvez você queira considerar um esquema um-para-muitos, já que seu único produto é composto de muitas partes.
Agora, com um esquema que pode estar salvando milhares de subpeças, provavelmente não precisamos ter todos os dados das peças em cada solicitação, mas ainda é importante que esse relacionamento seja mantido em nosso esquema. Assim, podemos ter uma coleção de produtos com dados sobre cada produto em nossa loja de e-commerce e, para manter os dados da peça vinculados, podemos manter uma array de IDs de objeto vinculados a um documento que tenha informações sobre a peça. Essas peças podem ser salvas na mesma coleção ou em uma coleção separada, se necessário. Vamos dar uma olhada em como isso ficaria.
Produtos:
1 { 2 "name": "left-handed smoke shifter", 3 "manufacturer": "Acme Corp", 4 "catalog_number": "1234", 5 "parts": ["ObjectID('AAAA')", "ObjectID('BBBB')", "ObjectID('CCCC')"] 6 }
Partes:
1 { 2 "_id" : "ObjectID('AAAA')", 3 "partno" : "123-aff-456", 4 "name" : "#4 grommet", 5 "qty": "94", 6 "cost": "0.94", 7 "price":" 3.99" 8 }
Regra 2: a necessidade de acessar um objeto por conta própria é uma razão convincente para não incorporá-lo.
Regra 3: evite junções/lookups se possível, mas use-as se elas puderem fornecer um melhor projeto de esquema.
E se tivermos um esquema em que possa haver zilhões de subdocumentos, ou mais? É aí que chegamos ao esquema de um a zilhões. E sei o que você está pensando: "zilhões é uma palavra real?"E a resposta é sim, é uma palavra real.
Vamos imaginar que você tenha sido solicitado a criar um aplicativo de registro de servidor. Cada servidor poderia potencialmente salvar uma enorme quantidade de dados, dependendo do quão detalhado é o registro e por quanto tempo você armazena os registros do servidor.
Com o MongoDB, rastrear dados dentro de um array ilimitado é perigoso, já que podemos atingir esse limite de 16-MB por documento. Qualquer host específico pode gerar mensagens suficientes para estourar o tamanho do documento de 16-MB, mesmo que apenas ObjectIDs sejam armazenados em um array. Então, precisamos repensar como podemos acompanhar esse relacionamento sem esbarrar em nenhum limite rígido.
Portanto, em vez de rastrear o relacionamento entre o host e a mensagem de registro no documento do host, vamos permitir que cada mensagem de registro armazene o host ao qual sua mensagem está associada. Ao armazenar os dados no registro, não precisamos mais nos preocupar com a possibilidade de um array ilimitado interferir em nossa aplicação! Vamos dar uma olhada em como isso pode funcionar.
Hosts:
1 { 2 "_id": ObjectID("AAAB"), 3 "name": "goofy.example.com", 4 "ipaddr": "127.66.66.66" 5 }
Mensagem de log:
1 { 2 "time": ISODate("2014-03-28T09:42:41.382Z"), 3 "message": "cpu is on fire!", 4 "host": ObjectID("AAAB") 5 }
Regra 4: Arrays não devem crescer sem limites. Se houver centenas de documentos no lado “muitos”, não os incorpore; se houver milhares de documentos no lado "muitos", não use um array de referências ObjectID. Arrays de alta cardinalidade são um motivo convincente para não incorporar.
O último padrão de projeto de esquema que abordaremos nesta publicação é a relação de muitos para muitos. Esse é outro padrão de esquema muito comum que vemos o tempo todo em projetos de esquemas relacionais e do MongoDB. Para esse padrão, vamos imaginar que estamos criando um aplicativo de tarefas pendentes. Em nosso aplicativo, um usuário pode ter muitas tarefas e uma tarefa pode ter muitos usuários atribuídos a ela.
Para preservar esses relacionamentos entre usuários e tarefas, será necessário haver referências de um usuário para as muitas tarefas e referências de uma tarefa para os muitos usuários. Vamos ver como isso pode funcionar para um aplicativo de lista de tarefas.
Usuários:
1 { 2 "_id": ObjectID("AAF1"), 3 "name": "Kate Monster", 4 "tasks": [ObjectID("ADF9"), ObjectID("AE02"), ObjectID("AE73")] 5 }
Tarefas:
1 { 2 "_id": ObjectID("ADF9"), 3 "description": "Write blog post about MongoDB schema design", 4 "due_date": ISODate("2014-04-01"), 5 "owners": [ObjectID("AAF1"), ObjectID("BB3G")] 6 }
A partir deste exemplo, você pode ver que cada usuário tem uma subarray de tarefas vinculadas, e cada tarefa tem uma subarray de proprietários para cada item em nosso aplicativo de tarefas.
Como você pode ver, existem várias maneiras diferentes de expressar seu projeto de esquema, e que vão além da normalização de seus dados, como você costuma fazer no SQL. Ao aproveitar a incorporação de dados em um documento ou fazer referência a documentos usando o operador $lookup, você pode executar queries de banco de dados realmente eficazes, dimensionáveis e eficientes que são totalmente focadas no seu caso de uso. Na verdade, mal começamos a ver todas as maneiras pelas quais você pode modelar seus dados no MongoDB. Se você quiser saber mais sobre projeto de esquema do MongoDB, não deixe de conferir nossa série contínua sobre projeto de esquema no MongoDB:
Queremos encerrar esta publicação com a regra mais importante para o projeto de esquema MongoDB até agora.
Regra 5: como sempre, com o MongoDB, a forma como você modela seus dados depende – inteiramente – dos padrões de acesso a dados do seu aplicativo específico. Você quer estruturar seus dados para corresponder às maneiras como seu aplicativo os consulta e atualiza.
Lembre-se de que cada aplicativo tem necessidades e requisitos exclusivos, portanto, o projeto do esquema deve refletir as necessidades desse aplicativo específico. Use os exemplos listados nesta publicação como ponto de partida para sua aplicativo. Reflita sobre o que você precisa fazer e como pode usar seu esquema para ajudá-lo a chegar lá.
Recapitulação:
- One-to-One - Prefira pares de valor-chave dentro do documento
- Um para poucos - prefira a incorporação
- Um para muitos - Prefira a incorporação
- Um para um bilhão - Prefira Referenciar
- Muitos para muitos - preferência por referências
Regras gerais para projeto de esquema no MongoDB:
- Regra 1: favoreça a incorporação, a menos que haja um motivo convincente para não fazê-lo.
- Regra 2: a necessidade de acessar um objeto por conta própria é uma razão convincente para não incorporá-lo.
- Regra 3: evite junções e pesquisas se possível, mas não tenha medo se elas puderem fornecer melhores projetos de esquema.
- Regra 4: os arrays não devem crescer sem limite. Se houver algumas centenas de documentos nos muitos lados, não os incorpore, se houver milhares de documentos nos muitos lados, não use um array de referências ObjectID. Arrays de alta cardinalidade são uma razão convincente para não incorporar.
- Regra 5: como sempre, com o MongoDB, a forma como você modela seus dados depende inteiramente dos padrões de acesso a dados do seu aplicativo específico. Você deseja estruturar seus dados para que correspondam às formas como seu aplicativo os consulta e atualiza.
Apenas raspamos a superfície dos padrões de projetos no MongoDB. Na verdade, ainda nem começamos a explorar padrões que não são nem remotamente possíveis de serem executados em um modelo relacional legado. Se você quiser saber mais sobre esses padrões, confira os recursos abaixo.
- Agora que você sabe como projetar um MongoDB database dimensionável e de alto desempenho, confira nossa série sobre antipadrões de projeto de esquema do MongoDB para saber o que NÃO fazer ao criar seu esquema do MongoDB database: https://developer.mongodb.com/article/schema-design-anti-pattern-massive-arrays
- Você gosta de mais vídeos? Confira nossa série de vídeos no YouTube para saber mais sobre os antipadrões do esquema MongoDB: https://www.youtube.com/watch?v=8CZs-0it9r4& feature=youtu.be