Blog

Elasticsearch ES|QL traz busca de texto completo para dados que você nunca indexou

MATCH e TO_TEXT trazem busca de texto completo para dados que você nunca indexou. Buscar colunas computadas, campos não mapeados e fontes federadas em ES|QL.

Get hands-on with Elasticsearch: Dive into our sample notebooks in the Elasticsearch Labs repo, start a free cloud trial, or try Elastic on your local machine now.

ES|QL MATCH agora executa busca de texto completo em dados que você nunca indexou. Colunas computadas, campos não mapeados, strings montadas em tempo real e até dados federados no S3. A nova função TO_TEXT diz ao ES|QL que trate qualquer string como texto analisável, para que MATCH possa tokenizar, normalizar maiúsculas e minúsculas, e fazer correspondência de termos em valores que existem somente durante a vida útil de uma consulta. Isso vai além da correspondência por padrões com LIKE e RLIKE que a maioria dos mecanismos de consulta oferece para strings não indexadas: é análise de verdade. Já disponível no Elastic Cloud Serverless e como prévia técnica no Elasticsearch 9.5.

Como o MATCH e TO_TEXT habilitam a busca de texto completo em qualquer expressão ES|QL

Vamos começar com uma consulta que era impossível no Elasticsearch 9.4, que usa o comando EVAL:

FROM cooking_blog
| EVAL summary = TO_TEXT(CONCAT(title, description))
| WHERE MATCH(summary, "pancakes")
| KEEP title, author

Neste exemplo, resumo não possui mapeamento ou configuração de analisador. Também não está associado a nenhum índice invertido. Ele existe apenas durante a vida útil dessa consulta, mas agora você pode buscar mesmo assim. Duas adições fazem isso funcionar.

Primeiro, MATCH agora aceita qualquer expressão como seu primeiro argumento, não apenas um campo mapeado. Isso inclui colunas produzidas pelo EVAL e resultados de funções usados em linha. Também inclui campos não mapeados carregados diretamente do documento original. Além disso, todos os tipos de dados normalmente aceitos pelo MATCH são suportados neste novo caso de uso.

A segunda parte disso é a nova função TO_TEXT, que é a primeira função de conversão ES|QL que produz saída do tipo texto. Até agora, colunas de texto só podiam vir de campos mapeados indexados, e todas as strings produzidas pelas expressões ES|QL eram valores de palavras-chave em vez de texto. A distinção importa porque o MATCH trata os dois de forma diferente: os valores do texto são analisados, enquanto os valores das palavras-chave são comparados exatamente, espelhando como uma consulta MATCH em um campo indexado se reescreve para uma consulta de termo. TO_TEXT(x) é como você diz ao ES|QL: trate essa sequência como texto completo.

Este é incluído como uma prévia técnica no Elasticsearch 9.5 e, por isso, possui algumas limitações:

  • No momento, funciona apenas para filtragem. Um MATCH em uma expressão ainda não contribui para a pontuação de relevância; apenas correspondências em campos indexados afetam a pontuação.

  • Opções de consulta como fuzziness e outras ainda não são suportadas ao combinar uma expressão.

  • O texto em tempo de execução é analisado com o analisador padrão. Isso ainda não é configurável.

Trabalhos estão em andamento para abordar essas limitações.

Por que usar a busca em texto completo em vez de LIKE ou RLIKE no ES|QL?

ES|QL já tinha duas formas de buscar strings sem índice: LIKE (padrões curinga) e RLIKE (expressões regulares). Ambos funcionam em qualquer expressão de string, então é justo perguntar o que o MATCH adiciona. A resposta é análise, uma forma mais avançada de busca que utiliza técnicas como stemming e sinônimos. Também utiliza o tratamento de stopwords.

LIKE é simples correspondência de substrings, sem qualquer compreensão das palavras que compõem uma string. Digamos, por exemplo, que você esteja procurando mensagens de log sobre uma raposa:

FROM app_logs
| WHERE message LIKE "*fox*"

Isso perde "Fox avistado perto do galinheiro" devido à capitalização, ao mesmo tempo em que iguala "Outfoxed pela competição", que não tem nada a ver com uma raposa. Ele falha em ambas as direções, com falsos negativos devido à capitalização e falsos positivos em substrings contidas em outras palavras.

Expressões regulares podem corrigir o problema do caso, mas o problema da fronteira de palavras fica feio rapidamente. Algo como:

FROM app_logs
| WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"

E nem isso está certo ainda. Ele deixa de encontrar "fox" no final de uma frase seguida de ! ou ?, e não considera tabulações, aspas ou parênteses. Cada correção faz o padrão mais longo, e a próxima pessoa que ler a consulta precisa fazer engenharia reversa do que ele realmente está fazendo.

MATCH faz o problema desaparecer, pois executa tanto a consulta quanto o valor por um analisador, que tokeniza texto em termos minúsculos e então faz a correspondência termo contra termo:

FROM app_logs
| WHERE MATCH(TO_TEXT(message), "fox")

Essa consulta corresponderá a valores como "A rápida raposa marrom" e "FOX avistada perto do galinheiro", mas não a "Enganada pela concorrência" ou "protocolo FOXTROT ativado", independentemente de qualquer pontuação ao redor das palavras. Claro, tudo isso funciona para consultas de múltiplos termos, como MATCH(TO_TEXT(message), "brown fox"), exatamente do jeito que você esperaria.

Estão em andamento trabalhos para viabilizar o uso dos 36 analisadores de linguagem dedicados, com suporte para linguagens naturais em dados que nunca foram indexados ou mapeados.

Casos de uso de busca de texto completo para dados não indexados e não mapeados

Os exemplos acima pesquisaram valores calculados a partir de campos mapeados. Os casos de uso mais interessantes do ES|QL MATCH em expressões envolvem dados que nunca foram pesquisáveis. Vamos examinar alguns.

Como buscar campos não mapeados no ES|QL sem adicionar um mapeamento

Às vezes, você deliberadamente deixa um campo fora dos seus mapeamentos, como um rastreamento de pilha verboso ou uma carga útil da requisição bruta. Você pode até mesmo omitir um blob para depuração. Indexar um desses consumiria espaço em disco e na heap em cada documento e não valeria a pena para um campo que você pode consultar uma vez por trimestre.

Essa decisão sempre foi final, porque campos não mapeados eram totalmente invisíveis para consultas. No Elasticsearch 9.5, você pode usar SET unmapped_fields="load" para fazer o ES|QL carregar campos não mapeados diretamente do documento de origem como palavras-chave. Em seguida, envolva isso em TO_TEXT, e agora você pode executar uma busca de texto completo:

SET unmapped_fields="load";
FROM app_logs
| WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
| KEEP @timestamp, service.name, message

Aqui, stack_trace nunca foi mapeado. Cada valor é obtido dos documentos originais e analisado em tempo real. Eles são combinados linha por linha. Isso dá trabalho de verdade e nunca será tão rápido quanto uma pesquisa por índice invertido. Mas agora, aquele campo que você não indexou pode ser pesquisado. Você pode manter o mapeamento pequeno para o caso do dia a dia e ainda responder à pergunta uma vez por trimestre quando importa.

Busca de texto completo em um campo de palavra-chave sem reindexação

Os campos de palavras-chave podem fazer muito. Eles oferecem correspondência exata, agregações rápidas e ordenação, por isso tantos campos acabam mapeados dessa forma. Mas os mapeamentos são decididos quando os dados chegam, e é fácil acabar em uma situação em que você queira fazer algo diferente do que pretendia originalmente com seus dados. Talvez product_name tenha sido mapeado como palavra-chave porque os dashboards fazem agregações com ele e, depois de receber um ano de dados de produto, alguém queira poder buscar valores dentro de product_name.

A resposta antiga era mudar o mapeamento para texto (ou adicionar um multicampo) e reindexar tudo. Isso pode ser demorado e caro, e em muitos casos, os usuários simplesmente não vão querer se preocupar com isso. A nova resposta é uma chamada de função:

FROM products
| WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
| KEEP product_name, brand, price

TO_TEXT converte os valores das palavras-chave em texto em tempo real, então o MATCH os analisa em vez de compará-los exatamente. Isso permite consultar um campo de palavra-chave sem criar um mapeamento ou reindexar o documento de origem. Se a busca se tornar uma consulta diária, indexar o campo como texto ainda é a melhor opção a longo prazo, mas TO_TEXT lhe dá uma resposta hoje, sem nenhum esforço extra.

Buscar no mesmo campo entre índices com mapeamentos diferentes

ES|QL pode abranger muitos índices, e o mesmo campo não precisa ser sempre igual em todos eles. Quando o mesmo campo tem tipos diferentes em índices distintos, ES|QL o trata como um tipo de união, e uma função de conversão resolve o conflito. Vamos considerar um exemplo em que o campo mensagem é do tipo texto no modelo de índice deste ano, mas era uma palavra-chave no do ano passado:

FROM logs-2025, logs-2026
| EVAL msg = TO_TEXT(message)
| WHERE MATCH(msg, "connection reset")

Cada valor é analisado no momento da consulta, seja do índice de texto ou do índice de palavras-chave. Os valores das palavras-chave dos índices antigos são tokenizados e em minúsculas como todo o resto, então "connection reset" encontra "Connection RESET by peer", não importa em qual índice esteja.

Outro caso interessante é quando um campo é mapeado em apenas um índice, mas também presente (e não mapeado) no outro:

SET unmapped_fields="load";
FROM logs-2025, logs-2026
| WHERE MATCH(TO_TEXT(error_details), "timeout")

Há uma nuance que vale a pena destacar aqui. Se error_details estiver mapeado em logs-2026, mas não em logs-2025, o Elasticsearch não pode enviar essa consulta para Lucene, porque os índices onde o campo não está mapeado retornariam silenciosamente nenhuma correspondência. Em vez disso, o planejador percebe que o campo pode não estar mapeado e avalia toda a MATCH linha por linha, de onde quer que as linhas tenham vindo. Você não precisa saber quais índices têm o campo mapeado; a consulta apenas responde à questão.

Como ES|QL analisa texto no momento da consulta sem um índice invertido

Quando o ES|QL planeja uma correspondência (match) contra uma expressão, ele analisa a string de consulta uma vez, no início, em um conjunto de termos. A forma como cada linha é então avaliada depende do tipo da expressão:

Tipo de expressão

Processamento

Comportamento de correspondência

texto (via TO_TEXT)

O analisador divide o valor em termos minúsculos

Comparação token-contra-token; uma linha corresponde se qualquer token for igual a qualquer termo de consulta (semântica OU)

palavra-chave, IP, data, numérico

Sem análise; constante de consulta convertida uma vez para o tipo nativo

Comparação exata por linha

Ambos os caminhos ignoram completamente o Lucene e avaliam os valores linha por linha. O caminho não textual reflete exatamente o que uma consulta match faz quando executada no Lucene para esses tipos de campo, de modo que a semântica permanece consistente independentemente de a sua consulta atingir ou não um índice.

Uma consulta de índice invertido faz seu trabalho no momento da ingestão e nunca acessa documentos que não correspondem no momento da consulta. Um match em tempo de execução faz essa análise no momento da consulta, para cada linha que a alcança. Uma é rápida porque o trabalho já aconteceu; a outra é flexível porque os dados não precisam ter sido indexados de forma alguma.

O que vem a seguir para a busca de texto completo no ES|QL

Tudo o que está neste post é a primeira parte de um esforço maior para fazer a busca no ES|QL funcionar em qualquer coisa, não apenas no que você indexou antes. As limitações mencionadas anteriormente estão sendo trabalhadas ativamente, e o roadmap vai além:

  • Pontuação. Correspondências em tempo de execução contribuem para _score, então você pode ordenar por relevância mesmo quando os dados nunca foram indexados.

  • MATCH_PHRASE em expressões. Já disponível no Elastic Cloud Serverless, e chegando ao Elastic Stack na versão 9.6.

  • Analisadores configuráveis. Suporte a analisadores para MATCH e MATCH_PHRASE em expressões, permitindo analisadores de linguagem, derivação (stemming) e sinônimos no momento da consulta.

  • Opções do MATCH. Opções como fuzziness e operador para correspondências em tempo real.

  • Busca vetorial. Gerando vetores de incorporação por linha e executando k-nearest neighbors (kNN) em expressões dense_vector em tempo de execução, trazendo também a pesquisa semântica para dados não indexados.

Experimente hoje a busca de texto completo do ES|QL em expressões

Você pode experimentar a busca em tempo de execução hoje mesmo. Já está disponível no Elastic Cloud Serverless, onde as novas capacidades do ES|QL são lançadas primeiro, e ele é disponibilizado como uma prévia técnica no Elasticsearch 9.5. Comece consultando a referência das funções de pesquisa e verifique a página de limitações do ES|QL para conhecer os limites atuais. É uma prévia técnica porque queremos seu feedback: se você buscar algo que nunca foi indexado e isso o surpreender, seja positiva ou negativamente, gostaríamos muito de saber.

Conteúdo relacionado

Do prompt ao dashboard em menos de um minuto, 5 vezes mais barato: dashboards de IA e gráficos personalizados com Vega-Lite no Kibana

Marta Bondyra

LINQ para Elasticsearch ES|QL: escreva Consultas em C# e Consulte o Elasticsearch

Florian Bernd

Estatísticas ES|QL mais rápidas com tabelas hash no estilo Swiss Tables

Chris Hegarty

Introdução do suporte ao Elasticsearch no Google MCP Toolbox for Databases

Enrico Zimuel

ES|QL na 9.2: Smart Lookup Joins e compatibilidade com séries temporais

Tyler Perkins