Implementando atlas search da maneira correta

O atlas search é uma full-text search poderosa incorporada no MongoDB Atlas, plataforma que oferece o mongodb como serviço (DBaaS). No entanto, é importante tomar alguns cuidados ao implementar esse recurso em seu projeto. Nesse artigo vou falar sobre um erro comum ao começar a utilizar o atlas search.

Operador $search + $match no mesmo pipeline

Em projetos que utilizam o mongodb, é muito comum existirem aggregations com filtragem de documentos e paginação. Em um determinado momento, pode ser necessário incluir uma busca textual em algum campo do documento e a depender da complexidade da consulta, ser necessário incluir uma busca com atlas search ao invés de usar apenas regex. Nesse momento, decidir apenas incluir o operador $search no seu pipeline já existente, pode impactar drasticamente a performance da sua aplicação.

Exemplificando o problema

A titulo de exemplo, vamos trabalhar com uma API que retorna livros. Nessa API já existem os filtros comuns como filtro por ano, autor, etc… Abaixo, apresento p exemplo de um documento:

{
  "_id": ObjectId("65f5a3b8c9e77a001c2d3e4f"),
  "title": "O Senhor dos Anéis",
  "author": "J.R.R. Tolkien",
  "genre": "Fantasia",
  "release_date": 1954,
  "pages": 1178
}

Abaixo, temos o exemplo de uma consulta filtrando pelo gênero “Fantasia” e uma páginação comum:

[
  {
    "$match": {"genre": "Fantasia"}
  },
  {"$skip": 10},
  {"$limit": 10}
]

Agora precisamos permitir uma busca textual no campo title. Um dos mótivos clássicos para optar pela implementação do atlas search para essa tarefa, é permitir erros de digitação por parte do usuário, classificar os resultados por relevância, busca parcial e performance. Tomada essa decisão, abaixo mostro como ficaria o pipeline se os desenvolvedores decidirem apenas inserir a a busca com atlas search no pipeline já existente. A etapa responsável pela busca textual fica dentro do estágio com $search. Observe abaixo:

[
  {
    "$search": {
      "text": {
        "query": "senhor dos aneis", # texto que o usuário inseriu
        "path": "title", # campo filtrado
        "fuzzy": {
          "maxEdits": 2 # permite erros de digitação
        }
      }
    }
  },
  {
    "$match": {"genre": "Fantasia"}
  },
  {"$skip": 10},
  {"$limit": 10}
]

Caso o usuário filtre pelo gênero e agora filtre também pelo título, a primeira etapa utiliza o $search para uma busca textual e em seguida é realizada uma filtragem por número de gênero no operador $match (a query antiga ainda é a mesma). Ao executar esse pipeline, internamente o banco utiliza o motor de busca do atlas search para processar o operador $search e em seguida outro para o $match. A troca de informações entre esses esses motores é extremamente custosa e deve ser evitada.

Resolvendo o problema de performance

No exemplo citado acima, o ideal é utilizar toda a consulta no atlas $search pois ele dá suporte para as mesmas buscas que o mongodb oferece. O novo pipeline otimizado seria:

[
  {
    "$search": {
      "index": "default",
      "compound": {
        "must": [
          {
            "text": {
              "query": "senhor dos aneis",
              "path": "title",
              "fuzzy": {
                "maxEdits": 2
              }
            }
          },
          {
            "equals": {
              "path": "genre",
              "value": "Fantasia"
            }
          }
        ]
      }
    }
  },
  {"$skip": 10},
  {"$limit": 10}
]

Nesse novo pipeline, a busca textual e o filtro por gênero são executadas em apenas 1 máquina e terá um desempenho muito superior ao cenário anterior. Um ponto interessante é que os operadores $skip e $limit trabalham bem após o $search mas existem meios mais performáticos para fazer páginação no atlas search com searchAfter e searchBefore.

Considerações finais

Além da combinação $search + $match causar um enorme impacto na performance, ( mais explicações do porque acontece isso na sessão Exemplificando o problema), algo semelhante acontece com os operadores $group, $sort e $count. Porém, para esses operadores o impacto é menor mas ainda assim deve ser evitado. Sempre considere testar sua query em collections grandes, simulando um cenário real para evitar descobrir esse tipo de problema em produção.

referências:

https://www.mongodb.com/pt-br/docs/atlas/atlas-search/
https://www.mongodb.com/pt-br/docs/atlas/atlas-search/paginate-results/
https://www.mongodb.com/pt-br/docs/atlas/atlas-search/performance/query-performance/

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *