Lições aprendidas ao aprimorar a busca de texto completo na Snyk com Elasticsearch
Sergey Vasilkov
4 de novembro de 2021
0 minutos de leituraElasticsearch é um mecanismo de busca de código aberto bastante popular. Por oferecer velocidade em tempo real e uma API robusta, é uma escolha frequente entre desenvolvedores que precisam adicionar recursos de busca de texto completo aos projetos. Além de ser popular em geral, é também o mecanismo para o qual estamos migrando a funcionalidade de relatórios da Snyk para issues! Quando tudo estiver ajustado em issues, começaremos a usar o Elasticsearch em outras áreas de relatórios.

Embora o Elasticsearch seja poderoso, ele também pode parecer complicado no início (a menos que você já tenha experiência com mecanismos de busca). Como aprendi bastante sobre essa ferramenta recentemente, enquanto a implementava na Snyk, achei que valia a pena compartilhar um pouco desse conhecimento com você — de desenvolvedor para desenvolvedor!
Às vezes, um analisador padrão não é suficiente
O Elasticsearch mantém a velocidade tratando dados diferentes de formas diferentes. Entre os vários tipos de campos, há os campos de texto — campos comuns para conteúdo textual (ou seja, strings). Para tornar pesquisáveis as informações armazenadas nesse campo, o Elasticsearch faz a análise do texto durante a ingestão, convertendo os dados em tokens (termos) e armazenando esses tokens e outras informações relevantes, como comprimento e posição, no índice. Por padrão, ele usa um analisador padrão para a análise de texto.
Da documentação oficial: “O analisador padrão oferece suporte imediato à maioria dos idiomas naturais e casos de uso. Se você optar por usar o analisador padrão sem alterações, não será necessária nenhuma configuração adicional.”
Em casos simples, a análise padrão pode ser suficiente. Vamos ver como ela funciona: com o comando a seguir, podemos conferir a análise gerada para qualquer texto que fornecermos:
Aqui, podemos ver exatamente o que o analisador padrão faz: ele divide o texto em tokens e gera informações de serviço para cada trecho, que são armazenadas no índice.
Pesquisando o texto indexado
Depois de indexado o conteúdo, podemos testar a busca de texto completo com um analisador padrão. Vamos criar um documento de exemplo com dois campos: title e description. O Elasticsearch mapeará o documento automaticamente e identificará os campos de texto.
Agora, vamos pesquisar por “express” com a seguinte consulta:
Nenhum documento será retornado, embora “expression” esteja no título. Esse resultado é esperado (pelo menos para mim, já que já passei por isso!), pois o analisador não criou nenhum token “express” (como podemos ver nos tokens gerados que consultamos acima).
Se alterarmos a consulta de busca para “expression”, o documento será retornado:
Há consultas mais custosas que podem contornar esse problema de correspondência de tokens, como as consultas fuzzy e wildcard, mas elas exigem muito poder computacional. Então, em vez de gastar ciclos de CPU com o problema, precisamos reavaliar nosso analisador.
Fica claro que um analisador padrão pode não ser suficiente se quisermos resultados de busca melhores e mais flexíveis. Para alcançar esse objetivo, vamos configurar um analisador personalizado. Estes serão nossos requisitos:
Usar consultas menores
Busca sem distinção entre maiúsculas e minúsculas
Para isso, os próximos passos serão:
Criar um analisador personalizado
Configurar um tokenizador mais adequado ao caso de uso
Ativar o novo analisador personalizado para os campos escolhidos no índice do Elasticsearch
Analisadores em mais detalhes
Com o analisador padrão, facilitamos as coisas e não precisamos entender exatamente o que ele fazia. Agora que vamos criar um analisador personalizado, precisamos entender o que acontece durante a análise de texto:
Os filtros de caracteres (opcionais) são aplicados ao texto analisado para remover caracteres.
Um tokenizador divide o texto em tokens ou termos. Isso pode ser feito de diferentes formas, gerando tokens por espaços em branco, letras etc.
Os filtros de tokens (opcionais) fazem alterações adicionais nos tokens, como convertê-los para minúsculas, remover tokens específicos e muito mais.
Um analisador é uma combinação de tokenizadores e filtros. Ele analisa o texto e o prepara para a busca. Todo analisador precisa ter um único tokenizador, mas pode ter poucos ou muitos filtros dos dois tipos. A ordem das operações é sempre a mesma: filtro de caracteres > tokenizador > filtro de tokens.
Criando nosso primeiro analisador personalizado
Como vimos antes, por padrão, todos os campos de texto usam o analisador padrão. O Elasticsearch oferece uma ampla variedade de analisadores integrados, que podem ser usados em qualquer índice sem configuração adicional, e também permite criar os seus próprios. Para este caso de uso, vamos precisar criar um.
Os analisadores podem ser configurados em diferentes níveis: índice, campo ou consulta. Vamos criar um analisador personalizado e defini-lo para um campo. Veja como configurar um analisador personalizado. Observação: as configurações são armazenadas nos ajustes do índice:
Veja o que fizemos:
Um novo analisador personalizado chamado
my_analyzeré definido para o índicemy_analyzerusa o tokenizadorstandarde o filtrolowercasemy_analyzerfoi ativado como analisador da propriedade title no mapeamento do índice
Este exemplo é bem simples e não altera o comportamento do analisador padrão. Agora podemos ajustá-lo para permitir buscas sem distinção entre maiúsculas e minúsculas usando palavras incompletas.
Escolha e configure um tokenizador melhor
No exemplo anterior, usamos o tokenizador padrão. Agora é hora de substituí-lo pelo edge_ngram, que divide os tokens de acordo com o comprimento (e não pelos espaços em branco). Se você está se perguntando por que estou usando edge_ngram em vez de stemmer, continue lendo!
Veja o que fizemos:
my_tokenizerusa o tokenizadoredge_ngramEle criará tokens com comprimento entre
3e8caracteresOs tokens incluirão apenas letras e dígitos
Escolha e configure um filtro de tokens melhor
O analisador que criamos acima agora permite buscar palavras incompletas com 3 a 8 caracteres. Falta apenas permitir buscas sem distinção entre maiúsculas e minúsculas. Para isso, basta adicionar o filtro de tokens lowercase adequado a my_analyzer:
Resultado final
Depois de todas as alterações, já podemos criar um novo índice com um analisador personalizado. Vale lembrar que, antes de alterar o analisador, não é possível atualizar essa configuração em campos existentes usando a API de atualização de mapeamento.
Veja como ficarão as configurações e os mapeamentos do nosso índice de teste básico:
Agora podemos adicionar um documento e executar diferentes consultas para garantir que nossa busca funcione como esperado:
Pesquise títulos que contenham “express” ou outros tokens incompletos, sem distinção entre maiúsculas e minúsculas:
Observe que não configuramos um analisador personalizado para a propriedade de texto do nosso índice. Portanto, nesse campo, ainda temos a boa e velha busca com o analisador padrão.
E agora?
Neste artigo simples, configuramos um analisador personalizado para ter uma busca de texto mais flexível no Elasticsearch e abordamos algumas configurações básicas e específicas. O Elasticsearch oferece uma ampla variedade de analisadores, tokenizadores, filtros, normalizadores e configurações de stop words integrados, entre muitos outros recursos.
Combinando esses recursos, podemos obter ótimos resultados de busca personalizados. Se você está começando a usar o Elasticsearch, espero que este artigo tenha sido útil. Fique de olho, pois planejamos compartilhar mais sobre nossa jornada com buscas. O próximo assunto serão as agregações! (Pode contar com isso...)
Se você gostaria de fazer parte da Snyk e criar ótimas ferramentas de segurança para desenvolvedores, confira nossas vagas abertas em engenharia.



