Elasticsearch: implementando buscas full-text poderosas
1. Fundamentos do Elasticsearch e Índices Invertidos
Elasticsearch é um mecanismo de busca distribuído baseado em Apache Lucene, projetado especificamente para full-text search em larga escala. Diferentemente de bancos relacionais que realizam buscas lineares com operadores LIKE, o Elasticsearch utiliza uma estrutura chamada índice invertido (inverted index).
Um índice invertido funciona como um glossário: para cada termo único encontrado nos documentos, armazena uma lista de referências (documentos e posições) onde aquele termo aparece. Quando você faz uma busca por "machine learning", o Elasticsearch consulta rapidamente o índice invertido, localiza os documentos que contêm esses termos e calcula a relevância.
O processo de tokenização divide o texto em tokens (palavras individuais), aplica normalizações como lowercasing e remove pontuações. Enquanto um banco SQL precisa escanear linha por linha com WHERE texto LIKE '%termo%', o Elasticsearch localiza os resultados em milissegundos, mesmo em coleções com bilhões de documentos.
2. Mapeamento e Análise de Texto
Para que o Elasticsearch entenda como tratar seus dados, você define mappings. Campos do tipo text são analisados e indexados para full-text, enquanto campos keyword são armazenados como valores exatos para agregações e filtros.
PUT /artigos
{
"mappings": {
"properties": {
"titulo": { "type": "text", "analyzer": "standard" },
"conteudo": { "type": "text", "analyzer": "brazilian" },
"autor": { "type": "keyword" },
"data_publicacao": { "type": "date" }
}
}
}
Analisadores customizados permitem controle granular. Você pode combinar tokenizers (como standard ou whitespace) com filtros como lowercase, asciifolding, stemmer (para reduzir palavras à raiz) e stop (para remover palavras comuns como "de", "para", "com").
PUT /artigos/_settings
{
"analysis": {
"analyzer": {
"meu_analisador": {
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "portuguese_stemmer"]
}
},
"filter": {
"portuguese_stemmer": { "type": "stemmer", "language": "portuguese" }
}
}
}
O uso de sinônimos melhora a recuperação: uma busca por "carro" pode retornar documentos com "automóvel" ou "veículo" se configurado no filtro synonym.
3. Consultas Full-text: Match, Query String e Multi-match
A query match é a mais simples e poderosa para full-text search. Ela analisa o texto de busca e encontra documentos relevantes:
GET /artigos/_search
{
"query": {
"match": {
"conteudo": "inteligência artificial aplicada"
}
}
}
Para buscar frases exatas, use match_phrase:
GET /artigos/_search
{
"query": {
"match_phrase": {
"conteudo": "aprendizado supervisionado"
}
}
}
A query multi_match permite buscar em vários campos simultaneamente, atribuindo pesos diferentes:
GET /artigos/_search
{
"query": {
"multi_match": {
"query": "redes neurais",
"fields": ["titulo^3", "conteudo", "resumo^2"]
}
}
}
No exemplo acima, o campo titulo tem peso 3 (três vezes mais importante), resumo tem peso 2, e conteudo peso padrão 1.
4. Relevância e Scoring (TF-IDF e BM25)
O Elasticsearch utiliza o algoritmo BM25 (Okapi BM25) como padrão para calcular a relevância desde a versão 5.0. BM25 é uma evolução do TF-IDF que considera:
- TF (Term Frequency): quantas vezes o termo aparece no documento
- IDF (Inverse Document Frequency): raridade do termo na coleção
- Normalização por comprimento: documentos mais curtos tendem a ser mais relevantes
O boosting permite priorizar resultados. Você pode aplicar boost em nível de campo (como visto no multi_match) ou em nível de query:
GET /artigos/_search
{
"query": {
"bool": {
"must": [
{ "match": { "conteudo": "machine learning" } }
],
"should": [
{ "match": { "titulo": "machine learning" } }
]
}
}
}
Para controle avançado, use function_score:
GET /artigos/_search
{
"query": {
"function_score": {
"query": { "match": { "conteudo": "deep learning" } },
"functions": [
{ "filter": { "term": { "categoria": "tutorial" } }, "weight": 2 },
{ "gauss": { "data_publicacao": { "origin": "now", "scale": "30d" } } }
]
}
}
}
5. Filtros, Agregações e Facetas
Filtros no Elasticsearch são executados em contexto filter dentro de uma query bool. Eles não afetam o score, mas eliminam documentos:
GET /artigos/_search
{
"query": {
"bool": {
"must": [
{ "match": { "conteudo": "processamento linguagem natural" } }
],
"filter": [
{ "term": { "categoria": "pesquisa" } },
{ "range": { "data_publicacao": { "gte": "2024-01-01" } } }
]
}
}
}
Agregações permitem criar facetas de navegação em tempo real:
GET /artigos/_search
{
"size": 0,
"aggs": {
"por_categoria": {
"terms": { "field": "categoria" }
},
"por_ano": {
"date_histogram": {
"field": "data_publicacao",
"calendar_interval": "year"
}
}
}
}
Combine buscas full-text com agregações para construir dashboards interativos onde o usuário filtra por texto e vê as distribuições atualizadas instantaneamente.
6. Sugestões, Autocomplete e Correção Ortográfica
O completion suggester implementa autocomplete eficiente baseado em prefixos:
PUT /artigos/_mapping
{
"properties": {
"titulo_suggest": {
"type": "completion"
}
}
}
GET /artigos/_search
{
"suggest": {
"titulo_sugestao": {
"prefix": "ma",
"completion": { "field": "titulo_suggest" }
}
}
}
Para correção ortográfica estilo "did you mean?", use o phrase suggester:
GET /artigos/_search
{
"suggest": {
"correcao": {
"text": "inteligencia artificial",
"phrase": {
"field": "conteudo",
"size": 3,
"gram_size": 3,
"direct_generator": [{
"field": "conteudo",
"suggest_mode": "always"
}]
}
}
}
}
7. Otimização de Performance e Escalabilidade
Um cluster Elasticsearch distribui dados em shards (partições) e cria réplicas para alta disponibilidade. A configuração ideal depende do volume de dados e padrão de consultas:
PUT /artigos
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2,
"refresh_interval": "30s"
}
}
O refresh_interval controla o quão rápido novos documentos ficam visíveis para busca. Valores maiores (30s-60s) melhoram a performance de indexação, enquanto valores menores (1s) reduzem latência de visibilidade.
Cache de consultas e filtros acelera buscas repetitivas. Ative o cache para filtros frequentes:
GET /artigos/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "categoria": "tecnologia" } }
]
}
},
"request_cache": true
}
8. Casos de Uso Avançados e Integração
Busca geoespacial combinada com full-text permite encontrar "restaurantes italianos próximos" ou "artigos sobre sustentabilidade em São Paulo":
GET /estabelecimentos/_search
{
"query": {
"bool": {
"must": [
{ "match": { "descricao": "comida japonesa" } }
],
"filter": [
{ "geo_distance": { "distance": "5km", "localizacao": { "lat": -23.55, "lon": -46.63 } } }
]
}
}
}
Para documentos com arrays de objetos complexos, use nested queries:
PUT /cursos
{
"mappings": {
"properties": {
"modulos": { "type": "nested" }
}
}
}
GET /cursos/_search
{
"query": {
"nested": {
"path": "modulos",
"query": {
"bool": {
"must": [
{ "match": { "modulos.titulo": "introdução" } },
{ "range": { "modulos.duracao": { "gte": 60 } } }
]
}
}
}
}
}
A integração com Logstash permite ingerir logs e dados estruturados, enquanto Kibana oferece dashboards visuais para explorar resultados de busca, agregações e monitorar performance do cluster.
Referências
-
Documentação Oficial do Elasticsearch: Full-text Queries — Guia completo sobre todas as queries full-text disponíveis no Elasticsearch, com exemplos práticos de cada tipo de consulta.
-
Elasticsearch: The Definitive Guide - Full-Text Search — Capítulo do livro oficial que explica os fundamentos da busca full-text, incluindo análise de texto, mapeamentos e scoring.
-
BM25 Algorithm Explained — Artigo técnico do blog oficial da Elastic explicando o algoritmo BM25, suas variáveis e como otimizar a relevância das buscas.
-
Elasticsearch Suggester: Autocomplete and Did You Mean — Documentação detalhada sobre todos os tipos de suggesters: completion, phrase e term, com exemplos de configuração.
-
Elasticsearch Aggregations: A Practical Guide — Referência completa sobre agregações no Elasticsearch, incluindo buckets, métricas e pipeline aggregations para criar facetes e dashboards.