Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

05 - Search (Elasticsearch)

#busca#elasticsearch#indice-invertido#logs#opensearch
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Search (Elasticsearch)

Elasticsearch é um motor de busca distribuído construído sobre o Apache Lucene, projetado para indexar grandes volumes de dados semiestruturados e responder consultas textuais em dezenas de milissegundos. Ele ocupa três papéis centrais em arquiteturas de produção: busca de produto e conteúdo para o usuário final, agregação e exploração de logs (a base do stack ELK) e autocomplete com relevância ajustável. Diferentemente do banco relacional, que responde com precisão por igualdade e faixa, o motor de busca responde com ordenação por relevância, tolerância a erro de grafia, análise morfológica e facets, e é essa família de capacidades que justifica sua existência ao lado do banco principal. O cluster é distribuído por natureza, com shards particionando índices e réplicas sustentando leitura e disponibilidade. Em 2024 e 2025 a categoria também mudou de rumo comercial: após a adoção de licenças restritivas, o Elasticsearch 8.16 reintroduziu uma opção open source sob AGPLv3, enquanto o OpenSearch, fork da AWS sob Apache 2.0, consolidou sua própria linha de evolução.

Conceito

O coração de um motor de busca é o índice invertido: em vez de mapear documento para conteúdo, ele mapeia cada termo para a lista de documentos que o contêm, com posições e frequências. Assim como o índice remissivo de um livro aponta em que páginas cada palavra aparece, o índice invertido permite responder "quais documentos falam de 'postgresql'?" sem varrer a coleção. Antes de entrar no índice, o texto passa pelo pipeline de análise: tokenização quebra o texto em termos, filtros normalizam (minúsculas, remoção de stopwords) e stemmers reduzem variações à raiz ("correndo" e "correu" convergem para "corre"). Campos numéricos e de data recebem estruturas próprias para faixas, e o campo keyword preserva o termo exato para agregação e filtro. O documento indexado é, portanto, uma representação transformada do original, e a qualidade da busca depende primeiro da qualidade dessa análise.

Que problema resolve

O motor de busca resolve o conjunto de problemas que bancos generalistas atendem mal, e cada problema tem nome e sintoma em produção:

  • Busca textual com relevância: o usuário digita "fone bluetooth" e espera os resultados mais prováveis primeiro, ordenados por pontuação de relevância e não por data de inserção.
  • Tolerância a variação linguística: plural, acento, inflexão e erro de digitação são absorvidos por análise, fuzzy matching e correção sugerida.
  • Agregação e facets sobre texto: contagens por categoria, faixa de preço e marca em tempo subsegundo sobre milhões de documentos.
  • Exploração de logs: ingestão contínua, retenção por idade e consultas ad hoc combinadas a agregações por janela, com ciclo de vida gerido pelo próprio índice.
  • Autocomplete e busca-as-you-type: prefixos e completions servidos em milissegundos, algo inviável com LIKE '%texto%' no banco relacional.

Sem o search engine, esses requisitos terminam em varreduras caras no banco primário, latência crescente e acoplamento entre a saúde da aplicação e consultas exploratórias.

Funcionamento interno

O Elasticsearch organiza dados em índices, e cada índice é dividido em shards, que são instâncias Lucene completas distribuídas entre nós do cluster. Cada shard é replicado em cópias (primária e réplicas) que servem leitura e sobrevivem à perda do nó primário. A escrita passa primeiro por um translog para durabilidade e por um buffer em memória; periodicamente o buffer é descarregado em segmentos imutáveis, que são mesclados em segundo plano (merge) para controlar o número de segmentos e remover documentos deletados; deleção em Lucene é marcação, e o espaço só volta após o merge. Consultas distribuídas seguem o modelo scatter-gather: o nó coordenador distribui a consulta para um shard de cada grupo (primária ou réplica), cada um responde com seus melhores candidatos, e o coordenador mescla e ordena o resultado final. O índice por shard tem custo de memória e file handles, e shards muito grandes ou numerosos demais são a fonte mais comum de problemas operacionais da categoria.

// Mapeamento e settings de um índice de produtos
PUT produtos
{
  "settings": {
    "number_of_shards": 3,        // particionamento horizontal
    "number_of_replicas": 1,      // cópias para leitura e HA
    "refresh_interval": "1s"      // custo de visibilidade x throughput
  },
  "mappings": {
    "properties": {
      "nome":   { "type": "text", "analyzer": "portuguese" },
      "marca":  { "type": "keyword" },
      "preco":  { "type": "scaled_float", "scaling_factor": 100 },
      "tags":   { "type": "keyword" },
      "criado_em": { "type": "date" }
    }
  }
}
// Consulta com relevância combinada e facets
POST produtos/_search
{
  "query": {
    "bool": {
      "must":   [{ "match": { "nome": { "query": "fone bluetooth", "fuzziness": "AUTO" } } }],
      "filter": [{ "term": { "marca": "audiox" } },
                 { "range": { "preco": { "lte": 500 } } }]
    }
  },
  "aggs": {
    "por_marca": { "terms": { "field": "marca" } }
  },
  "size": 20
}

Relevância e scoring

A pontuação padrão usa BM25, evolução do TF-IDF que pondera frequência do termo no documento, raridade do termo na coleção e normalização pelo tamanho do documento. Em produção, a relevância percebida é mais engenharia do que sorteio: filtros baratos antes de matches textuais, boosts por campo (título pesa mais que descrição), boosts funcionais por popularidade, vendas ou margem, e regras de negócio para itens patrocinados. A avaliação de relevância é um processo contínuo com conjuntos de consultas de referência e métricas como nDCG, corridas A/B e análise de consultas sem clique, que indicam onde a análise de texto ou o ranking falham. Três armadilhas recorrentes merecem atenção: deep pagination com from alto é proibitiva (use search_after com cursor), o filtro e a query têm semânticas de scoring distintas, e boost por script executa por documento, custando caro em coleções grandes.

Autocomplete e busca por prefixo

Autocomplete é o caso de uso que expõe o custo de fazer busca mal feita no banco relacional, e o search engine o resolve com estruturas dedicadas. Para sugestão enquanto o usuário digita, o padrão é indexar um campo auxiliar com analisador de edge ngram, que gera prefixos incrementais de cada termo, e consultar com match sobre esse campo; a alternativa search_as_you_type embute essa estrutura pronta. Para completions de alta velocidade, o tipo completion usa uma estrutura de prefixo (FST) em memória, com respostas em microssegundos, porém sem scoring textual completo. Três detalhes de produção fazem diferença: cache no frontend para teclas digitadas em sequência, limite de resultados por requisição e ordenação por popularidade para as sugestões, já que o usuário espera o item mais provável primeiro, não o alfabético.

// Campo de autocomplete com edge ngram no mapeamento
PUT produtos
{
  "settings": {
    "analysis": {
      "filter": {
        "edge_2_15": { "type": "edge_ngram", "min_gram": 2, "max_gram": 15 }
      },
      "analyzer": {
        "auto_complete": {
          "tokenizer": "standard",
          "filter": ["lowercase", "asciifolding", "edge_2_15"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "nome_autocomplete": {
        "type": "text",
        "analyzer": "auto_complete",
        "search_analyzer": "standard"   // consulta sem ngram
      }
    }
  }
}

Cluster, shards e capacidade

O desenho de capacidade do cluster é decisão de arquitetura com consequências duradouras. Shards têm tamanho saudável na faixa das dezenas de gigabytes, e o número de shards por nó deve manter o heap sob controle; a regra prática antiga de shards de até 50 GB ainda orienta, mas o que importa é o custo de rebalanceamento e de merges. Réplicas aumentam throughput de leitura e protegem contra falha de nó, ao custo de armazenamento dobrado; em clusters pequenos, number_of_replicas: 1 é o piso usual, e escalar réplicas para 2 ou 3 só sob demanda de leitura. O gerenciamento de ciclo de vida (ILM no Elasticsearch, políticas equivalentes no OpenSearch) move índices entre fases: hot para ingestão e busca intensa, warm para leitura ocasional, cold para arquivo consultável e delete para expiração. Log management sem ILM é o caminho mais rápido para cluster saturado, e a regra é dimensionar ingestão diária, retenção por fase e tamanho de shard juntos, nunca em separado.

Elasticsearch versus OpenSearch

O histórico comercial da categoria importa para decisão de longo prazo. A Elastic adotou licenças restritivas em 2021 (SSPL e Elastic License), o que motivou a AWS a criar o OpenSearch como fork do Elasticsearch 7.10.2 sob Apache 2.0. Em 2024, com o Elasticsearch 8.16, a Elastic reintroduziu uma opção open source sob AGPLv3, somada às licenças anteriores, reabrindo o caminho de distribuições comunitárias; o OpenSearch seguiu evolução independente sob a Linux Foundation, com recursos próprios como o OpenSearch Flow Framework e melhorias de escala. Na prática, os dois produtos divergem em versões de API e recursos, e a compatibilidade de clientes deixou de ser total. A escolha hoje é de governança e plataforma: OpenSearch para quem prioriza licença Apache e ofertas gerenciadas AWS, Elasticsearch para quem quer o ecossistema Elastic (observabilidade, segurança unificada) e aceita AGPLv3 ou licença comercial.

Critério Elasticsearch OpenSearch
Licença atual AGPLv3 (opcional, desde 8.16), SSPL, Elastic License Apache 2.0
Governança Elastic NV Linux Foundation
Linha de origem Lucene, versões 7/8/9 Fork do 7.10.2, linha própria
Gestão de índices ILM ISM (Index State Management)
Segurança básica Incluída na distribuição básica Incluída (security plugin integrado)
Ofertas gerenciadas Elastic Cloud, Elastic Cloud on EKS Amazon OpenSearch Service
Ecossistema Stack unificado de observabilidade e segurança Comunidade AWS e integrações OpenSearch

Erros comuns

  • Shards pequenos e numerosos (milhares por nó), inflando o heap com metadados e degradando merges e recuperação; consolide índices por janela de tempo.
  • Consultas com wildcard no início do termo ou script_score por documento, custos lineares que derrubam o cluster no primeiro pico de tráfego.
  • Deep pagination com from: 10000, memória e CPU por documento pulado; pagine com search_after e limite o usuário a poucas páginas.
  • Campos text usados como filtro exato ou keyword indexado com texto longo, invertendo o propósito dos tipos e inflando o índice.
  • Falta de ILM/ISM em índices de logs, com retenção infinita e cluster de busca servindo de arquivo morto.
  • Refresh interval padrão assumido como real time, quando a visibilidade de novos documentos tem atraso de cerca de um segundo por desenho.
  • Mapeamento dinâmico sem controle, onde um campo novo de alta cardinalidade por documento explode o índice e os termos no dicionário.

Boas práticas

  • Modele o mapeamento explicitamente (templates e componentes reutilizáveis) e trate mudança de mapeamento como reindexação planejada.
  • Dimensione shards por janela de ingestão diária e retenção, revisitando o plano quando o volume diário dobrar.
  • Separe papéis no cluster (master dedicado, data hot, data warm) quando a escala justificar, e mantenha o heap abaixo de 75 por cento com folga para merges.
  • Versione consultas críticas como código, com testes de relevância sobre consultas de referência executadas em pipeline.
  • Use aliases para abstrair o índice da aplicação, permitindo reindexação e rotação sem mudança no código cliente.
  • Monitore latência de query por shard, taxa de rejeição da fila de busca, heap e merge pressure, alertando antes do degrau de saturação.
  • Para logs, defina fases de ciclo de vida no primeiro dia de ingestão, com base no volume diário e no custo de armazenamento aceito.