Dominando o Elasticsearch Query DSL: Comandos Essenciais para Recuperação de Dados
Desbloqueie o poder da recuperação do Elasticsearch dominando o Query DSL. Este guia detalha estruturas essenciais de consulta JSON, focando no uso prático das consultas `match`, `term` e range. Aprenda a diferença crítica entre as cláusulas `must` (pontuação) e `filter` (cache) dentro da consulta fundamental `bool`, permitindo construir pesquisas de dados complexas e de alto desempenho de forma eficiente.
Dominando o Elasticsearch Query DSL: Comandos Essenciais para Recuperação de Dados
O Elasticsearch Query DSL é a linguagem JSON que você usa quando uma caixa de pesquisa simples não é suficiente. Ele permite misturar pesquisa de texto completo, filtros exatos, intervalos de datas, ordenação, paginação e agregações em uma única requisição. Essa flexibilidade é útil, mas também torna fácil escrever uma consulta que retorna os documentos errados ou que funciona bem em testes e fica lenta em produção.
A melhor maneira de aprender o Query DSL é manter duas perguntas em mente: "Estou pesquisando texto por relevância?" e "Estou filtrando valores exatos?" A maioria das escolhas de consulta segue a partir dessa divisão.
A Anatomia de uma Requisição de Pesquisa no Elasticsearch
Todas as pesquisas do Elasticsearch são realizadas no endpoint _search de um índice específico (ou índices). Uma requisição de pesquisa básica é uma requisição POST contendo um corpo JSON que define os parâmetros da consulta. A parte mais crítica deste corpo é o objeto query.
Estrutura Básica:
POST /your_index_name/_search
{
"query": { ... Defina sua estrutura de consulta aqui ... },
"size": 10,
"from": 0
}
Tipos de Consulta Principais: Precisão e Relevância
O Query DSL oferece uma vasta gama de consultas adaptadas para diferentes tipos de dados e necessidades de correspondência. A escolha da consulta impacta significativamente tanto a pontuação de relevância quanto o desempenho.
1. Pesquisa de Texto Completo: A Consulta match
A consulta match é o padrão para pesquisa de texto completo em campos analisados. Ela tokeniza o termo de pesquisa e verifica tokens correspondentes no(s) campo(s) especificado(s).
Caso de Uso: Pesquisar texto em linguagem natural onde a pontuação de relevância é importante.
Exemplo: Encontrar documentos onde o campo 'description' contém a palavra 'cloud' ou 'computing'.
GET /products/_search
{
"query": {
"match": {
"description": "cloud computing"
}
}
}
2. Correspondência de Valor Exato: A Consulta term
A consulta term pesquisa documentos que contêm o termo exato especificado. Ao contrário de match, ela não realiza análise na string de pesquisa, tornando-a ideal para correspondências exatas em palavras-chave, IDs ou campos indexados numericamente.
Caso de Uso: Filtrar por valores exatos em campos não analisados (como campos keyword ou números).
Exemplo: Recuperar um produto com o ID exato SKU10021.
GET /products/_search
{
"query": {
"term": {
"product_id": "SKU10021"
}
}
}
3. Consultas de Intervalo (Range)
As consultas de intervalo permitem filtrar documentos onde o valor de um campo está dentro de um intervalo especificado (numérico, data ou string).
Sintaxe: Usa gt (maior que), gte (maior ou igual a), lt (menor que) e lte (menor ou igual a).
Exemplo: Encontrar pedidos feitos após 1º de janeiro de 2024.
GET /orders/_search
{
"query": {
"range": {
"order_date": {
"gte": "2024-01-01",
"lt": "2025-01-01"
}
}
}
}
4. Filtrando por Presença: A Consulta exists
A consulta exists identifica documentos onde um campo específico está presente (ou seja, não é nulo ou ausente).
Exemplo: Encontrar todos os usuários que forneceram um endereço de e-mail.
GET /users/_search
{
"query": {
"exists": {
"field": "email_address"
}
}
}
Construindo Lógica Complexa com a Consulta bool
Para praticamente todas as aplicações de pesquisa do mundo real, você precisa combinar múltiplos critérios. A consulta bool é a ferramenta essencial para isso, permitindo combinar outras cláusulas de consulta usando lógica booleana.
Cláusulas dentro de bool
A consulta bool aceita quatro cláusulas principais:
must: Todas as cláusulas dentro deste array devem corresponder. Cláusulas emmustcontribuem para a pontuação de relevância.filter: Todas as cláusulas dentro deste array devem corresponder, mas são executadas em um contexto sem pontuação. Isso as torna muito mais rápidas para critérios estritos de inclusão/exclusão.should: Pelo menos uma cláusula neste array deveria corresponder. Essas cláusulas influenciam a pontuação de relevância, mas são opcionais para a correspondência.must_not: Nenhuma das cláusulas neste array deve corresponder (o equivalente a um NOT lógico).
Exemplo Prático de Consulta bool
Vamos combinar vários conceitos para encontrar documentos de alta prioridade que mencionem 'security', mas excluam rascunhos e estejam disponíveis na região 'US'.
GET /logs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "security breach"
}
}
],
"filter": [
{
"term": {
"region.keyword": "US"
}
}
],
"should": [
{
"term": {
"priority": 5
}
}
],
"must_not": [
{
"term": {
"status.keyword": "DRAFT"
}
}
]
}
}
}
Explicação do Exemplo:
- Must: O documento deve conter a frase "security breach" no campo de conteúdo analisado.
- Filter: O documento deve ser marcado para a região 'US' (uma correspondência exata e rápida).
- Should: Documentos correspondentes a
priority: 5receberão um aumento em sua pontuação de relevância, mas documentos com prioridades mais baixas que atendam às cláusulasmustefilterainda serão retornados. - Must Not: Documentos marcados como 'DRAFT' são estritamente excluídos.
Melhores Práticas para Construção de Consultas
Para garantir que suas pesquisas sejam precisas e eficientes, siga estas diretrizes:
- Prefira
filterem vez demustpara critérios sem pontuação. Se você está apenas verificando inclusão/exclusão (por exemplo, filtrando por ID, data exata ou status), sempre use a cláusulafilterdentro de uma consultabool. Isso aproveita o cache e evita cálculos de pontuação caros. - Use Consultas Exatas com Sabedoria: Para campos mapeados como
text(analisados), usematch. Para campos mapeados comokeyword(não analisados), usetermou consultas de intervalo. - Evite Aninhamento Profundo: Embora possível, consultas
boolprofundamente aninhadas podem se tornar difíceis de ler e depurar, e às vezes podem levar à degradação do desempenho. - Aproveite
minimum_should_match: Para cláusulasshould, definirminimum_should_match(por exemplo, para1ou2) força um certo número desses critérios opcionais a serem atendidos, transformando-os efetivamente em critérios obrigatórios, enquanto ainda permite que contribuam para a pontuação.
O Mapeamento Decide Qual Consulta Faz Sentido
A maioria dos erros no Query DSL começa com o mapeamento. Uma consulta pode parecer correta e ainda assim retornar resultados confusos se o campo estiver mapeado de forma diferente do que você pensa.
Um padrão comum é um campo de texto com um subcampo de palavra-chave:
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"status": { "type": "keyword" },
"created_at": { "type": "date" },
"price": { "type": "double" }
}
}
}
Use match em title quando quiser o comportamento de texto completo analisado. Use term em title.keyword quando precisar do valor exato do título. Use term em status porque já é uma palavra-chave. Use range em created_at ou price porque esses campos são valores de data e numéricos.
Se uma consulta term em um campo de texto não funcionar como esperado, o problema geralmente é a análise. Os tokens armazenados podem estar em minúsculas, divididos, com radical ou alterados de outra forma. Verifique o mapeamento antes de alterar a consulta.
GET /products/_mapping
Para problemas de análise de texto, _analyze é útil:
GET /products/_analyze
{
"field": "description",
"text": "Cloud Computing"
}
Isso mostra quais tokens o Elasticsearch irá pesquisar.
match, match_phrase e multi_match
match é a consulta de texto completo do dia a dia, mas não é a única que você usará.
Use match_phrase quando a ordem das palavras for importante:
GET /products/_search
{
"query": {
"match_phrase": {
"description": "wireless charging stand"
}
}
}
Isso é útil para nomes de produtos, mensagens de log, títulos de documentos e frases onde a sequência exata carrega significado. É mais restritivo que match, então pode retornar menos documentos.
Use multi_match quando a mesma entrada do usuário deve pesquisar vários campos:
GET /products/_search
{
"query": {
"multi_match": {
"query": "noise cancelling headphones",
"fields": ["title^3", "description", "brand^2"]
}
}
}
Os boosts ^3 e ^2 dizem ao Elasticsearch que correspondências em title e brand devem contar mais do que correspondências em description. O boosting não é uma garantia de que um documento será classificado em primeiro lugar; é uma dica de pontuação. Teste com consultas reais antes de ajustar os boosts de forma muito agressiva.
Paginação Sem Prejudicar o Cluster
Os parâmetros básicos from e size são adequados para paginação rasa:
GET /products/_search
{
"from": 20,
"size": 10,
"query": {
"match": {
"description": "laptop sleeve"
}
}
}
Paginação profunda é diferente. Pedir a página 1.000 força o Elasticsearch a classificar e pular muitos resultados. Para pesquisas voltadas ao usuário, evite paginação profunda ilimitada. Para exportações ou varreduras em segundo plano, use search_after com uma classificação estável:
GET /products/_search
{
"size": 100,
"sort": [
{ "created_at": "asc" },
{ "_id": "asc" }
],
"search_after": ["2025-01-10T12:00:00Z", "abc123"],
"query": {
"term": {
"status": "active"
}
}
}
Os valores em search_after vêm do array sort do último resultado na resposta anterior. Essa abordagem é mais estável para percorrer grandes conjuntos de resultados.
Filtragem de Origem Mantém as Respostas Úteis
O desempenho da pesquisa não é apenas a execução da consulta. Retornar documentos enormes pode tornar o cliente, a rede e o nó coordenador mais lentos. Se a interface do usuário precisar apenas de alguns campos, solicite esses campos:
GET /orders/_search
{
"_source": ["order_id", "customer_id", "total", "created_at", "status"],
"query": {
"bool": {
"filter": [
{ "term": { "status": "paid" } },
{ "range": { "created_at": { "gte": "now-7d/d" } } }
]
}
}
}
Isso torna a resposta mais fácil de ler e pode reduzir o tamanho do payload. Não substitui um bom design de índice, mas ajuda quando os documentos contêm descrições grandes, blobs de metadados ou arrays aninhados que a página atual não precisa.
Classificação e Agregações Precisam dos Campos Certos
Classificar em texto analisado geralmente é um erro. Classifique em campos de palavra-chave, numéricos ou de data:
GET /products/_search
{
"sort": [
{ "price": "asc" },
{ "title.keyword": "asc" }
],
"query": {
"term": {
"status": "active"
}
}
}
O mesmo se aplica a muitas agregações. Se você quiser contagens por status, agregue em um campo de palavra-chave:
GET /orders/_search
{
"size": 0,
"aggs": {
"orders_by_status": {
"terms": {
"field": "status"
}
}
},
"query": {
"range": {
"created_at": {
"gte": "now-30d/d"
}
}
}
}
size: 0 diz ao Elasticsearch que você só quer resultados de agregação, não documentos correspondentes. É um pequeno hábito que mantém as respostas mais limpas.
Depurar Consultas Com explain e profile
Quando um resultado é classificado de forma estranha, use explain em um único documento:
GET /products/_explain/SKU10021
{
"query": {
"match": {
"description": "cloud computing"
}
}
}
Quando uma consulta é lenta, use profile em um teste de não produção ou produção cuidadosamente controlada:
GET /products/_search
{
"profile": true,
"query": {
"bool": {
"must": [
{ "match": { "description": "cloud computing" } }
],
"filter": [
{ "term": { "status": "active" } }
]
}
}
}
A saída do perfil é detalhada, mas pode mostrar se o tempo é gasto em uma consulta de texto, um filtro, um script ou outra parte da requisição. Não deixe a criação de perfil habilitada no código da aplicação; use-a como uma ferramenta de depuração.
Um Hábito Sensato de Construção de Consultas
Para a maioria das pesquisas de aplicação, construa a requisição nesta ordem:
- Coloque restrições exatas em
filter: ID do locatário, status, região, janela de data, permissões. - Coloque o texto inserido pelo usuário em
mustcommatch,match_phraseoumulti_match. - Use
shouldpara preferências de classificação, não requisitos rígidos, a menos que você definaminimum_should_match. - Limite
_sourceaos campos que o chamador precisa. - Adicione uma classificação estável se paginação ou exportações forem importantes.
- Verifique o mapeamento antes de culpar o Elasticsearch.
O Query DSL é poderoso porque separa filtragem, pontuação, classificação e modelagem de resposta. Depois que você mantém esses trabalhos separados, as consultas se tornam mais fáceis de ler, mais fáceis de ajustar e menos surpreendentes em produção.
Um Pequeno Exemplo de Solução de Problemas
Suponha que um usuário pesquise por ACME-1000 e não obtenha resultado, mesmo que o produto exista. Não adicione curingas imediatamente. Primeiro verifique o mapeamento. Se sku for uma keyword, isso deve funcionar:
GET /products/_search
{
"query": {
"term": {
"sku": "ACME-1000"
}
}
}
Se sku foi acidentalmente mapeado como text, a análise pode ter dividido ou alterado o valor. Você ainda pode consultá-lo em alguns casos, mas a melhor correção geralmente é uma alteração de mapeamento para índices futuros. Identificadores exatos, status, regiões e IDs de locatários devem ser campos do tipo palavra-chave. Descrições e títulos escritos por humanos devem ser campos de texto. O Query DSL fica muito mais fácil quando o mapeamento corresponde à maneira como as pessoas realmente recuperam os dados.