Uma introdução aos índices para MongoDB Atlas Search
Avalie esse Tutorial
Imagine ler um livro longo como "As Crônicas de Gelo e Fogo", "O Senhor dos Anéis" ou "Harry Potter". Agora imagine que havia um detalhe específico em um desses livros que você precisava reler. Não seria uma boa opção pesquisar todas as páginas desses livros longos para encontrar o que estava procurando. Em vez disso, você gostaria de usar algum tipo de índice de livros para ajudar a localizar rapidamente o que estava procurando. Esse mesmo conceito de indexação de conteúdo em um livro pode ser carregado para o MongoDB Atlas Search com índices de pesquisa.
O Atlas Search facilita a criação de uma busca rápida, relevante e de texto completo sobre seus dados na nuvem. Ele é totalmente integrado, gerenciado e disponível em todos os clusters do MongoDB Atlas que executam a versão 4.2 ou superior do MongoDB.
Definir corretamente seus índices é importante porque eles são responsáveis por garantir que você receba resultados relevantes ao usar o Atlas Search. Não existe uma solução única para todos os casos e diferentes índices trazem benefícios diferentes.
Neste tutorial, veremos uma leve introdução à criação de índices que serão valiosos para vários casos de uso de pesquisa de texto completo.
Antes de investirmos muito nesta introdução, é importante observar que o Atlas Search usa Apache Lucene. Isso significa que os índices de pesquisa não são exclusivos do Atlas Search e se você já estiver familiarizado com o Apache Lucene, seu conhecimento existente sobre indexação será transferido. No entanto, o tutorial pode funcionar como uma atualização sólida de qualquer maneira.
Antes de começarmos a criar índices, provavelmente devemos definir qual será nosso modelo de dados para o exemplo. Em um esforço para cobrir vários cenários de indexação, o modelo de dados será complexo.
Veja o exemplo a seguir:
1 { 2 "_id": "cea29beb0b6f7b9187666cbed2f070b3", 3 "name": "Pikachu", 4 "pokedex_entry": { 5 "red": "When several of these Pokemon gather, their electricity could build and cause lightning storms.", 6 "yellow": "It keeps its tail raised to monitor its surroundings. If you yank its tail, it will try to bite you." 7 }, 8 "moves": [ 9 { 10 "name": "Thunder Shock", 11 "description": "A move that may cause paralysis." 12 }, 13 { 14 "name": "Thunder Wave", 15 "description": "An electrical attack that may paralyze the foe." 16 } 17 ], 18 "location": { 19 "type": "Point", 20 "coordinates": [-127, 37] 21 } 22 }
O exemplo de documento acima é sobre Pokemon, mas o Atlas Search pode ser usado em qualquer documento que faça parte do seu aplicativo.
Documentos de exemplo como o acima nos permitem usar pesquisa de texto, pesquisa geográfica e potencialmente outras. Para cada um desses diferentes cenários de pesquisa, o índice pode mudar.
Quando criamos um índice para o Atlas Search, ele é criado no nível da coleção.
Há duas maneiras de mapear campos em um documento ao criar um índice:
- Mapeamentos dinâmicos
- Mapeamentos estáticos
Se o esquema do documento ainda estiver mudando ou o caso de uso não permitir que ele seja rigidamente definido, convém optar por mapear dinamicamente os campos do documento. Um mapeamento dinâmico atribuirá campos automaticamente quando novos dados forem inseridos.
Veja o exemplo a seguir:
1 { 2 "mappings": { 3 "dynamic": true 4 } 5 }
O JSON acima representa um índice válido. Quando você o adiciona a uma coleção, está mapeando essencialmente todos os campos existentes nos documentos e qualquer campo que possa existir no futuro.
Podemos fazer uma pesquisa simples usando este índice, como da seguinte forma:
1 db.pokemon.aggregate([ 2 { 3 "$search": { 4 "text": { 5 "query": "thunder", 6 "path": ["moves.name"] 7 } 8 } 9 } 10 ]);
Não definimos explicitamente os campos para esse índice, mas a tentativa de pesquisar "thunder" dentro da array
moves
nos dará resultados correspondentes com base em nossos dados de exemplo.Para ser claro, mapeamentos dinâmicos podem ser aplicados no nível do documento ou do campo. No nível do documento, um mapeamento dinâmico indexa automaticamente todos os tipos de dados comuns. Em ambos os níveis, ele indexa automaticamente todos os dados novos e existentes.
Embora seja conveniente, ter um índice de mapeamento dinâmico em todos os campos de um documento tem um custo. Esses índices ocuparão mais espaço em disco e podem ter menor desempenho.
A alternativa é usar um mapeamento estático; nesse caso, você especifica os campos a serem mapeados e que tipo de campos são. Veja o exemplo a seguir:
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "name": { 6 "type": "string" 7 } 8 } 9 } 10 }
No exemplo acima, o único campo do nosso documento que está sendo indexado é o campo
name
.A seguinte query de pesquisa retornaria resultados:
1 db.pokemon.aggregate([ 2 { 3 "$search": { 4 "text": { 5 "query": "pikachu", 6 "path": ["name"] 7 } 8 } 9 } 10 ]);
Se tentarmos pesquisar em qualquer outro campo em nosso documento, não obteremos resultados porque esses campos não estão mapeados estaticamente nem o esquema do documento está mapeado dinamicamente.
No entanto, existe uma maneira de obter o melhor dos dois mundos, se precisarmos.
Considere o seguinte que usa mapeamentos estáticos e dinâmicos:
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "name": { 6 "type": "string" 7 }, 8 "pokedex_entry": { 9 "type": "document", 10 "dynamic": true 11 } 12 } 13 } 14 }
No exemplo acima, ainda estamos usando um mapeamento estático para o campo
name
. No entanto, estamos usando um mapeamento dinâmico no campopokedex_entry
. O campo pokedex_entry
é um objeto, portanto, qualquer campo dentro desse objeto receberá o tratamento de mapeamento dinâmico. Isso significa que todos os subcampos são mapeados automaticamente, bem como quaisquer novos campos que possam existir no futuro. Isso pode ser útil se você quiser especificar os campos de nível superior a serem mapeados, mas também mapear todos os campos de um determinado objeto.Tome a seguinte query de pesquisa como exemplo:
1 db.pokemon.aggregate([ 2 { 3 "$search": { 4 "text": { 5 "query": "pokemon", 6 "path": ["name", "pokedex_entry.red"] 7 } 8 } 9 } 10 ]);
A pesquisa acima retornará resultados se "pokemon" aparecer no campo
name
ou no campo red
dentro do objeto pokedex_entry
.Ao usar um mapeamento estático, você precisa especificar um tipo para o campo ou ter
dynamic
definido como verdadeiro no campo. Se você especificar apenas um tipo, dynamic
terá como padrão falso. Se você especificar apenas dynamic
como verdadeiro, o Atlas Search poderá definir automaticamente como padrão determinados tipos de campo (por exemplo, string, data, número).Com a discussão de mapeamento dinâmico versus estático resolvida para os índices do MongoDB Atlas Search, agora podemos nos concentrar em cenários mais complicados ou específicos.
Vejamos primeiro como seria o índice totalmente mapeado para o documento do nosso exemplo:
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "name": { 6 "type": "string" 7 }, 8 "moves": { 9 "type": "document", 10 "fields": { 11 "name": { 12 "type": "string" 13 }, 14 "description": { 15 "type": "string" 16 } 17 } 18 }, 19 "pokedex_entry": { 20 "type": "document", 21 "fields": { 22 "red": { 23 "type": "string" 24 }, 25 "yellow": { 26 "type": "string" 27 } 28 } 29 }, 30 "location": { 31 "type": "geo" 32 } 33 } 34 } 35 }
No exemplo acima, estamos usando um mapeamento estático para cada campo de nossos documentos. Uma coisa interessante a se notar é o array
moves
e o objetopokedex_entry
no documento de exemplo. Mesmo que um seja um array e o outro um objeto, o índice é um document
para ambos. Embora escrever pesquisas não seja o foco deste tutorial, pesquisar um array e um objeto seria semelhante usando a notação de ponto.Se algum dos campos estivesse aninhado mais profundamente no documento, a mesma abordagem seria aplicada. Por exemplo, poderíamos ter algo assim:
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "pokedex_entry": { 6 "type": "document", 7 "fields": { 8 "gameboy": { 9 "type": "document", 10 "fields": { 11 "red": { 12 "type": "string" 13 }, 14 "yellow": { 15 "type": "string" 16 } 17 } 18 } 19 } 20 } 21 } 22 } 23 }
No exemplo acima, o campo
pokedex_entry
foi ligeiramente alterado para ter outro nível de objetos. Provavelmente não é uma maneira realista de modelar os dados para esse conjunto de dados, mas deve servir para mostrar o ponto sobre o mapeamento de campos aninhados mais profundos.Até agora, cada um dos índices tinha apenas seus tipos definidos no mapeamento. As opções padrão estão sendo aplicadas a todos os campos. As opções são uma maneira de refinar ainda mais o índice com base em seus dados para, em última análise, obter resultados de pesquisa mais relevantes. Vamos experimentar algumas das opções dentro dos mapeamentos do nosso índice.
A maioria dos campos em nosso exemplo usa o tipo de dados de string, então há muito mais que podemos fazer usando opções. Vamos ver quais são algumas delas.
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "name": { 6 "type": "string", 7 "searchAnalyzer": "lucene.spanish", 8 "ignoreAbove": 3000 9 } 10 } 11 } 12 }
No exemplo acima, estamos especificando que queremos usar um analisador de linguagem no campo
name
em vez do analisador padrão. Também estamos dizendo que o campo name
não deve ser indexado se o valor do campo for maior que 3000 caracteres.Os 3000 caracteres são apenas um número aleatório neste exemplo, mas adicionar um limite, dependendo do seu caso de uso, pode melhorar o desempenho ou o tamanho do índice.
Em um próximo tutorial, vamos explorar em detalhes o que são os analisadores de pesquisa e o que podem fazer.
Essas são apenas algumas das opções disponíveis para o tipo de dados de string. Cada tipo de dados terá seu próprio conjunto de opções. Se você quiser usar o padrão para qualquer opção específica, ela não precisará ser explicitamente adicionada ao campo mapeado.
Você acabou de receber o que, espero, tenha sido uma introdução suave à criação de índices a serem usados no Atlas Search. Para usar o Atlas Search, você precisará de pelo menos um índice em sua coleção, mesmo que seja um índice dinâmico padrão. No entanto, se você conhece seu esquema e consegue criar mapeamentos estáticos, geralmente é a melhor maneira de ajustar a relevância e o desempenho.
Para saber mais sobre os índices do Atlas Search e os vários tipos de dados, opções e analisadores disponíveis, consulte a documentação oficial.
Para saber como criar mais no Atlas Search, confira meus outros tutoriais: Criando um elemento de formulário com preenchimento automático com Atlas Search e JavaScript e Mostrando visualmente os destaques do Atlas Search com JavaScript e HTML.
Tem alguma dúvida ou feedback sobre este tutorial? Acesse os Fóruns da MongoDB Community e vamos conversar!