Mongoose e Padrões de Design: Armazene o que você consulta

Image for post
Image for post

Créditos

Uma pegadinha comum ao usar .populate do Mongoose é que você não pode filtrar por campos na coleção estrangeira. Por exemplo, suponha que você tenha 2 modelos: Book e Author e você deseja filtrar os livros pelo nome do autor.

No exemplo acima, mesmo que os filtros do .populate com match sejam baseados no nome do autor, o Mongoose ainda retorna todos os livros, incluindo aqueles author.name que não correspondem ao valor usado. Caso author.name não seja 'Ian Fleming', a propriedade author do livro será null.

Isso ocorre porque, sob o capô, o Mongoose transforma Book.find().populate('author') em duas consultas:

  1. const books = await Book.find({})
  2. Author.find({ _id: { $in: books.map(b => b.author) }, name: 'Ian Fleming' })

Então, .populate() encontra todos os livros primeiro e depois encontra os autores correspondentes.

Armazene o que você consulta

Se você precisar filtrar livros pelo nome do autor de maneira eficiente, o caminho certo é armazenar o nome do autor no documento do livro:

Dessa forma, você pode filtrar e classificar pelo nome do autor sem um extra .populate(). O padrão de armazenar o nome do autor no bookSchema e atualizar a coleção de livros sempre que o nome de um autor mudar é chamado de desreferenciação . A desreferenciação ou incorporação de dados de uma coleção em outra coleção é como você pode executar o MongoDB em grande escala sem usar soluções de cache como o memcached.

Se você estiver criando uma aplicação que faz muitas leitura ao banco, é provável que atualize os nomes dos autores com pouca frequência, mas filtrar e classificar os livros por nome de autor é algo frequente. Uma dica de memorização útil para essa regra geral é “armazenar o que você consulta”. Faça as consultas que você executa com mais frequência com rapidez, com o custo de fazer atualizações pouco frequentes um pouco mais lentas.

Diferenças ao usar $lookup

O MongoDB 3.6 introduziu o operador de agregação $lookup que se comporta de maneira semelhante a uma junção externa esquerda (SQL LEFT JOIN) . Em outras palavras, mesmo se você não desreferir a propriedade author, poderá usar a estrutura de agregação do $lookup e classificar os livros pelo nome do autor.

Por que o Mongoose não faz uso do $lookup? A questão se resume a um desempenho consistente. Como o $lookup executa uma pesquisa separada para todos os documentos que entram no estágio de $lookup, seu desempenho diminui para O(n^2) no caso de erros no índice, o que, por sua vez, pode causar queries lentas.

Por outro lado, o Mongoose executa 1 consulta para cada .populate(), o que leva a uma melhor taxa de transferência e apenas uma varredura de coleção no evento de uma falha no índice.

Mas existem anomalias de atualização!

Se você atualizar manualmente o banco de dados ou tiver um aplicativo separado que não atualiza corretamente o authorName, poderá haver um caso em que o nome do autor no modelo Book não esteja alinhado com o modelo Author. Embora anomalias de atualização como essa sejam certamente possíveis, elas são raras na produção: as causas mais prováveis ​​são uma atualização manual no banco de dados que ignora o aplicativo ou um desenvolvedor usando um padrão que ignora o middleware do Mongoose.

Se ocorrerem anomalias de atualização, elas serão fáceis de corrigir com uma migração. É muito mais fácil identificar e corrigir anomalias de atualização do que a degradação generalizada do desempenho da sua aplicação.

Além disso, às vezes os dados “desatualizados” são um recurso, não um bug. Meu exemplo preferido é o seguinte: digamos que você esteja criando um aplicativo de entrega de gás. Cada solicitação está associada a um veículo. Se um cliente atualiza seu veículo de um Toyota Camry 2015 para um BMW X1 2018, isso deve afetar as solicitações feitas há dois anos?

Continuando

“Armazene o que você consulta” é como garantir um desempenho consistente do MongoDB ao usar o Mongoose. Na minha experiência, a maioria dos problemas de desempenho do MongoDB se deve a índices ausentes e/ou a uma agregação excessivamente complexa que pode ser substituída por uma única consulta com alguns ajustes no esquema. Da próxima vez que você estiver coçando a cabeça, se perguntando por que sua agregação é lenta, pense em quais propriedades você pode desreferenciar para otimizar suas necessidades de consulta.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store