Quando o MongoDB foi introduzido, o principal recurso destacado foi a capacidade de ser “sem esquema”. O que isso significa? Isso significa que é possível armazenar documentos JSON, cada um com estrutura diferente, na mesma coleção. Isso é bem legal. Mas o problema começa quando você precisa recuperar os documentos. Como você sabe que um documento recuperado é de uma determinada estrutura, ou se contém um campo específico ou não? Você precisa percorrer todos os documentos e procurar esse campo específico. É por isso que é útil planejar cuidadosamente o esquema do MongoDB, especialmente para os grandes aplicativos.
Quando se trata do MongoDB, não há uma maneira específica de projetar o esquema. Tudo depende do seu aplicativo e de como seu aplicativo usará os dados. No entanto, existem algumas práticas comuns que você pode seguir ao projetar seu esquema de banco de dados. Aqui, vou discutir essas práticas e seus prós e contras.
Modelagem de um para poucos (incorporação)
Esse design é um bom exemplo de incorporação de documentos. Considere este exemplo de uma coleção Person para ilustrar essa modelagem.
{
name: "Amy Cooper",
hometown: "Seoul",
addresses: [
{ city: 'New York', state: 'NY', cc: 'USA' },
{ city: 'Jersey City', state: 'NJ', cc: 'USA' }
]
}
Prós:
- Você pode obter todas as informações em uma única consulta.
Contras:
- Os dados incorporados dependem completamente do documento pai. Você não pode pesquisar os dados incorporados de forma independente.
- Considere o exemplo em que você está criando um sistema de rastreamento de tarefas usando essa abordagem. Em seguida, você incorporará todas as tarefas específicas de uma pessoa na coleção Person. Se você quiser disparar uma consulta como:Mostre-me todas as tarefas que têm amanhã como prazo final. Isso pode ser muito difícil, mesmo que seja uma consulta simples. Nesse caso, você deve considerar outras abordagens.
Modelagem de um para muitos (referência)
Nesse tipo de modelagem, o documento pai conterá o ID de referência (ObjectID) dos documentos filho. Você precisa usar junções no nível do aplicativo (combinando dois documentos após recuperá-los do banco de dados no nível do aplicativo) para recuperar documentos, portanto, não há junções no nível do banco de dados. Assim, a carga em um banco de dados será reduzida. Considere este exemplo:
// Parts collection
{
_id: ObjectID(1234),
partno: '1',
name: ‘Intel 100 Ghz CPU',
qty: 100,
cost: 1000,
price: 1050
}
// Products collection
{
name: 'Computer WQ-1020',
manufacturer: 'ABC Company',
catalog_number: 1234,
parts: [
ObjectID(‘1234’), <- Ref. for Part No: 1
ObjectID('2345'),
ObjectID('3456')
]
}
Suponha que cada produto possa ter vários milhares de peças associadas a ele. Para esse tipo de banco de dados, a referência é o tipo ideal de modelagem. Você coloca os IDs de referência de todas as peças associadas no documento do produto. Em seguida, você pode usar junções de nível de aplicativo para obter as peças de um produto específico.
Prós:
- Neste tipo de modelagem, cada parte é um documento separado para que você possa aplicar todas as consultas relacionadas a peças nesses documentos. Não há necessidade de depender do documento pai.
- Muito fácil de executar operações CRUD (Criar, Ler, Atualizar, Gravar) em cada documento de forma independente.
Contras:
- Uma grande desvantagem desse método é que você precisa realizar uma consulta extra para obter os detalhes da peça. Para que você possa realizar junções no nível do aplicativo com o documento do produto para obter o conjunto de resultados necessário. Portanto, pode levar a uma queda no desempenho do banco de dados.
Modelagem de um para milhões (referência pai)
Quando você precisa armazenar toneladas de dados em cada documento, não pode usar nenhuma das abordagens acima porque o MongoDB tem uma limitação de tamanho de 16 MB por documento. Um exemplo perfeito desse tipo de cenário pode ser um sistema de log de eventos que coleta logs de diferentes tipos de máquinas e os armazena em coleções de Logs e Máquinas.
Aqui, você nem pode pensar em usar a abordagem Embedding, que armazena todas as informações de logs de uma determinada máquina em um único documento. Isso ocorre porque em apenas algumas horas, o tamanho do documento será superior a 16 MB. Mesmo que você armazene apenas os IDs de referência de todos os documentos de logs, você ainda esgotará o limite de 16 MB porque algumas máquinas podem gerar milhões de mensagens de logs em um único dia.
Portanto, neste caso, podemos usar a abordagem de referência de pai. Nesta abordagem, em vez de armazenar IDs de referência de documentos filho no documento pai, armazenaremos o ID de referência do documento pai em todos os documentos filho. Portanto, para nosso exemplo, armazenaremos o ObjectID da máquina nos documentos de logs. Considere este exemplo:
// Machines collection
{
_id : ObjectID('AAA'),
name : 'mydb.example.com',
ipaddr : '127.66.0.4'
}
// Logs collection
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'WARNING: CPU usage is critical!',
host: ObjectID('AAA') -> references Machine document
}
Suponha que você queira encontrar os 3.000 logs mais recentes da Máquina 127.66.0.4:
machine = db.machines.findOne({ipaddr : '127.66.0.4'});
msgs = db.logmsg.find({machine: machine._id}).sort({time : -1}).limit(3000).toArray()
Referência bidirecional
Nesta abordagem, armazenamos as referências em ambos os lados, o que significa que a referência do pai será armazenada no documento filho e a referência do filho será armazenada no documento pai. Isso torna a pesquisa relativamente fácil em uma ou várias modelagens. Por exemplo, podemos pesquisar em documentos pai e tarefa. Por outro lado, essa abordagem requer duas consultas separadas para atualizar um documento.
// person
{
_id: ObjectID("AAAA"),
name: "Bear",
tasks [
ObjectID("AAAD"),
ObjectID("ABCD"), -> Reference of child document
ObjectID("AAAB")
]
}
// tasks
{
_id: ObjectID("ABCD"),
description: "Read a Novel",
due_date: ISODate("2015-11-01"),
owner: ObjectID("AAAA") -> Reference of parent document
}
Conclusão
No final, tudo depende dos requisitos da sua aplicação. Você pode projetar o esquema do MongoDB de uma maneira que seja mais benéfica para seu aplicativo e ofereça alto desempenho. Aqui estão algumas considerações resumidas que você pode considerar ao projetar seu esquema.
- Projete o esquema com base nos padrões de acesso a dados do seu aplicativo.
- Não é necessário incorporar documentos sempre. Combine documentos apenas se for usá-los juntos.
- Considere a duplicação de dados porque o armazenamento é mais barato que o poder de computação hoje em dia.
- Otimize o esquema para casos de uso mais frequentes.
- As matrizes não devem crescer fora do limite. Se houver mais de algumas centenas de documentos filhos, não os incorpore.
- Prefira junções em nível de aplicativo a junções em nível de banco de dados. Com a indexação adequada e o uso adequado dos campos de projeção, você pode economizar muito tempo.