Separando dados que são acessados juntos
Lauren Schaefer, Daniel Coupal7 min read • Published Feb 12, 2022 • Updated Oct 01, 2024
Avalie esse Artigo
Estamos analisando rapidamente os antipadrões de design do esquema MongoDB. Até agora, nesta série, discutimos quatro dos seis antipadrões:
Normalizar dados e dividi-los em partes diferentes para otimizar o espaço e reduzir a duplicação de dados pode parecer uma segunda natureza para aqueles com histórico de banco de dados relacional. No entanto, separar dados que são frequentemente acessados juntos é, na verdade, um antipadrão no MongoDB. Nesta publicação, descobriremos o motivo e discutiremos o que você deve fazer em vez disso.
Se você preferir aprender por vídeo (ou se quiser me ouvir repetir, " Os dados acessados juntos devem ser armazenados juntos "), assista ao vídeo acima.
Assim como você usaria um
join
para combinar informações de diferentes tabelas em um relational database, o MongoDB tem uma operação$lookup que permite unir informações de mais de uma collection. $lookup
é ótimo para operações pouco frequentes e raramente usadas ou queries analíticas que podem ser executadas durante a noite sem um limite de tempo. No entanto, $lookup
não é tão bom quando você o usa com frequência em seus aplicativos. Por que? As operações
$lookup
são lentas e consomem muitos recursos em comparação às operações que não precisam combinar dados de mais deuma collection.A regra geral ao modelar seus dados no MongoDB é:
Os dados acessados juntos devem ser armazenados juntos.
Em vez de separar os dados que são usados com frequência entre várias collections, aproveite a incorporação e as matrizes para manter os dados juntos em uma única collection.
Por exemplo, ao modelar uma relação um-para-um, você pode incorporar um documento de uma collection como um subdocumento em um documento de outra. Ao modelar uma relação um-para-muitos, você pode incorporar informações de vários documentos em uma collection como uma array de documentos em outra.
Tenha em mente os outros antipadrões que já discutimos ao começar a combinar dados de diferentes collection. Matrizes enormes e ilimitadas e documentos inchados podem ser problemáticos.
Se a combinação de dados de coleções separadas em uma única coleção resultar em matrizes enormes e ilimitadas ou documentos inchados, talvez você queira manter as coleções separadas e duplicar alguns dos dados que são usados com frequência juntos em ambas as coleções. Você pode usar o Padrão de Subconjunto para duplicar um subconjunto dos documentos de uma coleção em outra. Você também pode usar o Padrão de Referência Estendida para duplicar uma parte dos dados em cada documento de uma coleção em outra. Em ambos os padrões, você tem a opção de criar referências entre os documentos em ambas as coleções. Lembre-se de que, sempre que precisar combinar informações de ambas as coleções, você provavelmente precisará usar
$lookup
. Além disso, sempre que você duplicar dados, você será responsável por garantir que os dados duplicados permaneçam sincronizados.Como dissemos ao longo desta série, cada caso de uso é diferente. Ao modelar seu esquema, considere cuidadosamente como você consultará os dados e qual será a aparência realista dos dados que você armazenará.
O que seria de uma publicação contra padrões sem um exemplo da Parks and Recreation? Não estou nem ai para pensar. Então, vamos voltar a leslie.
Leslie decide organizar um Modelo das Nações Unidas para estudantes locais do ensino médio e recruta alguns de seus colegas de trabalho para participar também. Cada participante atuará como delegado de um país durante o evento. Ela designa Andy e Donna para serem delegados da Finlândia.
leslie decide armazenar informações relacionadas ao Modelo das naes unidas em um banco de MongoDB database. Ela deseja armazenar as seguintes informações em seu banco de dados:
- Estatísticas básicas sobre cada país
- Uma lista de recursos que cada país tem disponíveis para trocar
- Uma lista de delegados para cada país
- Declarações de política para cada país
- Informações sobre cada evento modelo das Nações Unidas que ela organiza
Com essas informações, ela quer poder gerar rapidamente os seguintes relatórios:
- Um relatório do país que contém estatísticas básicas, recursos atualmente disponíveis para o comércio, uma lista de delegados, os nomes e datas dos últimos cinco documentos de política e uma lista de todos os eventos modelo das Nações Unidas nos quais este país participou
- Um relatório de evento que contém informações sobre o evento e os nomes dos países que participaram
O evento Modelo das Nações Unidas começa e Andy está animado para participar. Ele decide que não quer nenhum dos recursos "chatos" de seu país, então começa a negociar com outros países para adquirir todos os leões do mundo.
Leslie decide criar collection para cada uma das categorias de informações que ela precisa armazenar em seu banco de dados. Depois que Andy termina de negociar,Leslie tem documentos como os seguintes.
1 // Countries collection 2 3 { 4 "_id": "finland", 5 "official_name": "Republic of Finland", 6 "capital": "Helsinki", 7 "languages": [ 8 "Finnish", 9 "Swedish", 10 "Sámi" 11 ], 12 "population": 5528737 13 }
1 // Resources collection 2 3 { 4 "_id": ObjectId("5ef0feeb0d9314ac117d2034"), 5 "country_id": "finland", 6 "lions": 32563, 7 "military_personnel": 0, 8 "pulp": 0, 9 "paper": 0 10 }
1 // Delegates collection 2 3 { 4 "_id": ObjectId("5ef0ff480d9314ac117d2035"), 5 "country_id": "finland", 6 "first_name": "Andy", 7 "last_name": "Fryer" 8 }, 9 { 10 "_id": ObjectId("5ef0ff710d9314ac117d2036"), 11 "country_id": "finland", 12 "first_name": "Donna", 13 "last_name": "Beagle" 14 }
1 // Policies collection 2 3 { 4 "_id": ObjectId("5ef34ec43e5f7febbd3ed7fb"), 5 "date-created": ISODate("2011-11-09T04:00:00.000+00:00"), 6 "status": "draft", 7 "title": "Country Defense Policy", 8 "country_id": "finland", 9 "policy": "Finland has formally decided to use lions in lieu of military for all self defense..." 10 }
1 // Events collection 2 3 { 4 "_id": ObjectId("5ef34faa3e5f7febbd3ed7fc"), 5 "event-date": ISODate("2011-11-10T05:00:00.000+00:00"), 6 "location": "Pawnee High School", 7 "countries": [ 8 "Finland", 9 "Denmark", 10 "Peru", 11 "The Moon" 12 ], 13 "topic": "Global Food Crisis", 14 "award-recipients": [ 15 "Allison Clifford", 16 "Bob Jones" 17 ] 18 }
Quando leslie deseja gerar um relatório sobre a Finlândia, ela precisa usar
$lookup
para combinar informações de todas as cinco coletas. Ela deseja otimizar o desempenho do banco de dados, portanto, decide aproveitar a incorporação para combinar informações de suas cinco collections em uma única.leslie começa a trabalhar para melhorar seu esquema de forma incremental. Ao examinar seu esquema, ela realiza que tem um relacionamento de um para um entre os documentos em sua coleção
Countries
e sua coleção Resources
. Ela decide incorporar as informações da coleçãoResources
como subdocumentos nos documentos em sua coleçãoCountries
.Agora o documento para a Finlândia se parece com o seguinte.
1 // Countries collection 2 3 { 4 "_id": "finland", 5 "official_name": "Republic of Finland", 6 "capital": "Helsinki", 7 "languages": [ 8 "Finnish", 9 "Swedish", 10 "Sámi" 11 ], 12 "population": 5528737, 13 "resources": { 14 "lions": 32563, 15 "military_personnel": 0, 16 "pulp": 0, 17 "paper": 0 18 } 19 }
Como você pode ver acima, ela manteve as informações sobre recursos juntas como um subdocumento em seu documento para a Finlândia. Essa é uma maneira fácil de manter os dados organizados.
Ela não precisa mais de sua coleção de
Resources
, então ela a exclui.Neste ponto, ela pode recuperar informações sobre um país e seus recursos sem ter que usar o
$lookup
.leslie continua analisando seu esquema. Ela entende que tem um relacionamento de um para muitos entre países e delegados, então decide criar uma array chamada
delegates
em seus documentos doCountries
. Cada arraydelegates
armazenará objetos com informações de delegado. Agora o documento dela para a Finlândia é o seguinte:1 // Countries collection 2 3 { 4 "_id": "finland", 5 "official_name": "Republic of Finland", 6 "capital": "Helsinki", 7 "languages": [ 8 "Finnish", 9 "Swedish", 10 "Sámi" 11 ], 12 "population": 5528737, 13 "resources": { 14 "lions": 32563, 15 "military_personnel": 0, 16 "pulp": 0, 17 "paper": 0 18 }, 19 "delegates": [ 20 { 21 "first_name": "Andy", 22 "last_name": "Fryer" 23 }, 24 { 25 "first_name": "Donna", 26 "last_name": "Beagle" 27 } 28 ] 29 }
leslie se sentirá confiante em armazenar as informações dos delegados nos documentos de seu país, pois cada país terá apenas alguns poucos delegados (o que significa que sua array não crescerá infinitamente) e ela não acessará com frequência informações sobre os delegados separadamente de seus países associados .
Leslie não precisa mais de sua coleção
Delegates
, portanto, ela a exclui.leslie continua otimizando seu esquema e começa a examinar sua coleção
Policies
. Ela tem uma relação de um para muitos entre países e políticas. Ela precisa incluir os títulos e datas dos cinco documentos de política mais recentes de cada país em seu relatório. Ela considera incorporar os documentos de política nos documentos de seu país, mas os documentos podem ficar muito grandes rapidamente com base no tamanho das políticas. Ela não quer cair na armadilha do Antipadrão Bloated Documents, mas também quer evitar usar $lookup
toda vez que gera um relatório.leslie decide aproveitar o padrão de subconjunto. Ela armazena os títulos e datas dos cinco documentos de política mais recentes em seu documento de país. Ela também cria uma referência para o documento da política, para que possa coletar facilmente todas as informações de cada política quando necessário. Ela deixa a collection
Policies
como está. Ela sabe que terá que manter algumas informações duplicadas entre os documentos na collectionCountries
e na collection Policies
, mas decide duplicar um pouco de informações é uma boa compensação para garantir queries rápidas.Seu documento para a Finlândia agora se parece com o seguinte:
1 // Countries collection 2 3 { 4 "_id": "finland", 5 "official_name": "Republic of Finland", 6 "capital": "Helsinki", 7 "languages": [ 8 "Finnish", 9 "Swedish", 10 "Sámi" 11 ], 12 "population": 5528737, 13 "resources": { 14 "lions": 32563, 15 "military_personnel": 0, 16 "pulp": 0, 17 "paper": 0 18 }, 19 "delegates": [ 20 { 21 "first_name": "Andy", 22 "last_name": "Fryer" 23 }, 24 { 25 "first_name": "Donna", 26 "last_name": "Beagle" 27 } 28 ], 29 "recent-policies": [ 30 { 31 "_id": ObjectId("5ef34ec43e5f7febbd3ed7fb"), 32 "date-created": ISODate("2011-11-09T04:00:00.000+00:00"), 33 "title": "Country Defense Policy" 34 }, 35 { 36 "_id": ObjectId("5ef357bb3e5f7febbd3ed7fd"), 37 "date-created": ISODate("2011-11-10T04:00:00.000+00:00"), 38 "title": "Humanitarian Food Policy" 39 } 40 ] 41 }
Leslie continua examinando sua consulta para seu relatório sobre cada país. O último
$lookup
que ela tem combina informações da coleçãoCountries
e da coleçãoEvents
. Ela tem uma relação muitos-para-muitos entre países e eventos. Ela precisa ser capaz de gerar relatórios rapidamente sobre cada evento como um todo, portanto, deseja manter a coleçãoEvents
separada. Ela decide usar o Padrão de Referência Estendida para resolver seu dilema. Ela inclui as informações de que precisa sobre cada evento nos documentos de seu país e mantém uma referência ao documento completo do evento, para que possa obter mais informações quando precisar. Ela duplicará a data do evento e o tópico do evento nas coleções Countries
e Events
, mas ela se sente confortável com isso, pois é muito improvável que esses dados sejam alterados.Depois de todas as atualizações, o documento dela para a Finlândia agora é assim:
1 // Countries collection 2 3 { 4 "_id": "finland", 5 "official_name": "Republic of Finland", 6 "capital": "Helsinki", 7 "languages": [ 8 "Finnish", 9 "Swedish", 10 "Sámi" 11 ], 12 "population": 5528737, 13 "resources": { 14 "lions": 32563, 15 "military_personnel": 0, 16 "pulp": 0, 17 "paper": 0 18 }, 19 "delegates": [ 20 { 21 "first_name": "Andy", 22 "last_name": "Fryer" 23 }, 24 { 25 "first_name": "Donna", 26 "last_name": "Beagle" 27 } 28 ], 29 "recent-policies": [ 30 { 31 "policy-id": ObjectId("5ef34ec43e5f7febbd3ed7fb"), 32 "date-created": ISODate("2011-11-09T04:00:00.000+00:00"), 33 "title": "Country Defense Policy" 34 }, 35 { 36 "policy-id": ObjectId("5ef357bb3e5f7febbd3ed7fd"), 37 "date-created": ISODate("2011-11-10T04:00:00.000+00:00"), 38 "title": "Humanitarian Food Policy" 39 } 40 ], 41 "events": [ 42 { 43 "event-id": ObjectId("5ef34faa3e5f7febbd3ed7fc"), 44 "event-date": ISODate("2011-11-10T05:00:00.000+00:00"), 45 "topic": "Global Food Crisis" 46 }, 47 { 48 "event-id": ObjectId("5ef35ac93e5f7febbd3ed7fe"), 49 "event-date": ISODate("2012-02-18T05:00:00.000+00:00"), 50 "topic": "Pandemic" 51 } 52 ] 53 }
Os dados acessados juntos devem ser armazenados juntos. Se você estiver lendo ou atualizando informações com frequência, considere armazenar as informações juntas usando documentos aninhados ou matrizes. Considere cuidadosamente seu caso de uso e avalie as vantagens e desvantagens da duplicação de dados à medida que você reúne os dados.
Fique atento a uma postagem sobre o antipadrão final de design de esquema do MongoDB!
Quando estiver pronto para criar um esquema no MongoDB, confira o MongoDB Atlas, o banco de dados como serviço totalmente gerenciado do MongoDB. O Atlas é a maneira mais fácil de começar a usar o MongoDB e tem uma camada rica e gratuita para sempre.
Confira os seguintes recursos para obter mais informações: