Compreendendo o desempenho da Lookup de MongoDB $ – Comunidade de Dev
Depois de encontrar alguns problemas com a pesquisa $ em nosso aplicativo bancário, decidi me aprofundar em como ele realmente funciona e por que às vezes acaba sendo tão lento. O problema foi apenas um dia normal. Eu estava trabalhando em um novo recurso quando meu líder de tecnologia me mencionou no Slack com algo como: “Lucas, sua consulta é muito lenta” (e depois colou o plano de explicação) logo depois disso, ele acrescentou casualmente: “Esta consulta levou 33 minutos para terminar”. Na minha cabeça, eu fiquei tipo: “Uau … como isso passou? O código já está lá há três meses!” Foi quando eu decidi investigar o problema mais profundamente. Aqui está a consulta que ele me enviou:
[
{
“$match”: “simple start filtering”
},
{
“$lookup”: {
“from”: “Company”,
“let”: {
“company”: “$company”
},
“pipeline”: [
{
“$match”: {
“removedAt”: null,
“$expr”: {
“$and”: [
{
“$eq”: [
“$_id”,
“$$company”
]
}]}}}]”AS”: “Company”}}, {“$ Lookup”: {“de”: “Cliente”, “Let”: {“Customer”: “$ Customer”}, “Pipeline”: [
{
“$match”: {
“removedAt”: null,
“$expr”: {
“$and”: [
{
“$eq”: [
“$_id”,
“$$customer”
]
}]}}}]”como”: “cliente”}}, {“$ match”: “mais filtragem”},]Digite o modo de saída de tela cheia da tela cheia logo após ler a consulta, meu primeiro pensamento foi: “Por que estamos usando $ expr e pipelines dentro da pesquisa? Então, a primeira coisa que fiz foi colar esse pipeline de agregação na bússola do MongoDB para explicar e tentar visualizar por que a consulta era tão lenta. E para minha surpresa … Acabei de receber uma tela de carregamento infinito 😅 Eu prometo, esperei muito tempo esperando ver alguma saída. Mas no final, tudo o que recebi foi um erro de tempo limite. Nesse ponto, decidi quebrar as coisas passo a passo, isolando cada estágio para descobrir onde o gargalo estava escondido. Testando primeiro, corri uma explicação apenas na partida $. Tudo funcionou perfeitamente: sem problemas. A consulta levou apenas 10ms e usou um índice simples da empresa.
[
{
“$match”: {
“removedAt”: null,
“company”: ObjectId(“some_ID”),
“type”: “TYPE_FILTERING”
}
}
]
Digite o modo de saída do modo de tela cheia até agora, tão bom. Em seguida, adicionei a primeira pesquisa de $ – e aqui é onde as coisas começaram a cheirar engraçadas. O tempo de execução saltou de 10ms para 314ms. Antes de prosseguir, decidi testar os dois pesquisados juntos, apenas para confirmar onde estava o problema. E … boom! 6 minutos para devolver o resultado. Tão claramente, o problema tinha que estar nas pesquisas de $. Compreendendo a pesquisa, tudo aqui é baseado na documentação oficial da pesquisa no MongoDB. Então, para começar, o $ Lookup tem essa sintaxe e esses campos {$ Lookup: {de:, Localfield :, Favoradfield :, Let: {:,… ,:}, pipeline: [ ]como:}} Digite o modo de saída de tela cheia de tela cheia no Basic, a maneira mais fácil de usar uma pesquisa é com o Localfield e Foreignfield, mas em nossa consulta estamos usando Let e Pipeline para encontrar os resultados. Mas por que? No aplicativo, usamos o SOFT Excluir para evitar a exclusão de dados importantes por um erro, para que tudo continue armazenado, mas com a bandeira removida, mas realmente precisamos filtrar com o pipeline? Compreendendo o problema de $ exPR de acordo com a documentação oficial do $ expr: Quando o $ EXPR aparece em um estágio de partida $ que faz parte de uma subpípela de pesquisa $, o $ EXPR pode se referir a deixar variáveis definidas pelo estágio de pesquisa $ o problema aqui é que, quando o MongoDB aplica o filtro, ele deve avaliar o documento de condição por documento dentro da coleção. À medida que a coleção cresce, essa operação fica mais lenta e lenta, pois não pode confiar nos índices da mesma maneira que uma correspondência direta. Em outras palavras: $ EXPR dentro de um pipeline de lookup de $ obriga o MongoDB a fazer mais trabalho, o que rapidamente se torna doloroso em escala. A melhor abordagem é usar o Localfield e Foreignfield sempre que possível. Dessa forma, o MongoDB pode aproveitar os índices com eficiência. Por exemplo: {“$ Lookup”: {“de”: “Company”, “Localfield”: “Company”, “Fourlfield”: “_id”, “Pipeline”: [
{
“$match”: {
“removedAt”: null,
}
}
]”AS”: “Company”}}, digite o modo de saída de tela cheia e aqui vamos nós! 🎉 Agora, o tempo de resposta é totalmente aceitável: melhorando ainda mais o pipeline após alguns experimentos, descobri que sempre que usamos um pipeline dentro de uma pesquisa $, a consulta diminui muito – às vezes 5x a 10x mais lentamente. Portanto, se o seu aplicativo realmente não precisar de validações extras, recomendo fortemente evitar o pipeline. Em vez disso, mantenha -se no campo local/estrangeiro. Isso é muito mais simples e muito mais eficiente quando você está se juntando a _ids. No meu caso, ao remover o pipeline desnecessário, melhorei a consulta de 259ms para 36ms. Aqui está a versão simplificada:
[
{
“$match”: “simple filtering”
},
{
“$lookup”: {
“from”: “Company”,
“localField”: “company”,
“foreignField”: “_id”,
“as”: “company”
}
},
]
Enter fullscreen mode Exit fullscreen mode Conclusion After all the tweaks, the final query ended up looking like this:[ { “$match”: “simple filtering” }, { “$lookup”: { “from”: “Company”, “localField”: “company”, “foreignField”: “_id”, “as”: “company”, } }, { “$lookup”: { “from”: “Customer”, “localField”: “customer”, “foreignField”: “_id”, “as”: “customer”, } }, { “$match”: “more filtering” }, Enter fullscreen mode Exit fullscreen mode And that’s it a (not so short) story, but with some practical tips on how to improve $lookup performance: ✅ Prefer localField / foreignField whenever possible ⚠️ $expr can slow down queries a lot 🛠️ For more complex Pesquisas, combine o oleoduto com o campo local/Foreignfield
Fonte