Dados e bancos de dados

Apache Solr: índices invertidos, cadeias de análise e ajuste de relevância

Intermediário8 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

Um mecanismo de busca responde a uma pergunta diferente da de um banco de dados: não quais linhas correspondem, mas quais documentos são os mais relevantes. Este guia explica o índice invertido e a cadeia de análise que o alimenta, e depois aborda o schema do Solr, a indexação e os query parsers, o scoring BM25, facetas e destaque, as coleções do SolrCloud e o trabalho prático de ajustar a relevância.

O que você vai aprender

  • Explicar por que um índice invertido responde a consultas de texto que um índice relacional não consegue responder
  • Projetar uma cadeia de análise com tokenizers e filtros, e entender por que a análise na indexação e na consulta precisa ser compatível
  • Definir tipos de campo no Solr, indexar documentos e consultá-los com edismax
  • Usar facetas, destaque e sugestões para construir uma experiência de busca de verdade
  • Ajustar a relevância com boosts de campo, boosts de frase e function queries, e rodar o Solr em cluster

Na primeira vez que um time troca uma consulta LIKE '%term%' por um mecanismo de busca, o que surpreende não é a velocidade. É que os resultados voltam em uma ordem útil. Um banco de dados responde a uma pergunta booleana — esta linha corresponde? — e o ranking fica por sua conta. Um mecanismo de busca responde a uma pergunta graduada: entre os documentos que correspondem, quais atendem melhor à consulta, considerando o quão raro é cada termo e quanto do documento ele ocupa?

O Apache Solr é um servidor de busca construído sobre o Apache Lucene e há duas décadas sustenta buscas em catálogos, sites e documentos. Este guia trata do Solr 9: o que o índice realmente contém, como o texto vira tokens, como definir um schema e consultá-lo, e como funciona o ajuste de relevância depois que o básico está rodando.

Por que um mecanismo de busca não é um índice de banco de dados

Um índice B-tree mapeia o valor inteiro de uma coluna para as linhas que o contêm. Um mecanismo de busca constrói um índice invertido: um dicionário de termos, cada um apontando para uma lista de postings com os documentos que contêm aquele termo, junto com posições e frequências.

Doc 1: "Estuary birds of northern Portugal"
Doc 3: "Bird migration over the estuary"

term        postings (doc: positions, freq)
----------------------------------------------
bird        1:[2]/1   3:[1]/1
estuari     1:[1]/1   3:[5]/1
migrat      3:[2]/1
portug      1:[5]/1

Daí decorrem duas coisas. Buscar uma palavra é uma consulta a um dicionário mais uma fusão de listas, então continua rápido quer o corpus tenha dez mil documentos, quer dez milhões. E, como o índice registra quantas vezes e onde cada termo aparece, ele consegue pontuar as correspondências em vez de apenas encontrá-las.

O modelo de scoring padrão é o BM25. Em termos simples, um documento pontua mais quando o termo da consulta aparece mais vezes nele (com retornos fortemente decrescentes), quando o termo é raro no corpus inteiro e quando o campo que o contém é curto — um termo em um título de cinco palavras vale mais do que o mesmo termo perdido em um corpo de mil palavras. O Lucene usa o BM25 como similaridade padrão desde o Solr 6; o antigo modelo TF-IDF continua disponível por compatibilidade.

O trade-off é real. O Solr não tem transações, nem joins dignos do nome, nem atualização in-place; mudar um único campo reescreve o documento inteiro. Trate-o como uma projeção derivada de um sistema de registro, nunca como o registro em si — esse papel continua com o banco relacional, com os hábitos de modelagem e de indexação abordados no guia de planos de execução.

A cadeia de análise

O texto bruto vira termos de índice passando por um analisador, composto por uma cadeia de filtros de caracteres, um tokenizer e uma cadeia de filtros de tokens. Acertar essa parte é a maior parte do trabalho de uma boa experiência de busca.

  • Os filtros de caracteres atuam sobre a string bruta: removem HTML, mapeiam œ para oe, normalizam a pontuação.
  • O tokenizer divide o fluxo em tokens. O StandardTokenizer segue os limites de palavra do Unicode; o WhitespaceTokenizer quebra apenas nos espaços; o ClassicTokenizer preserva coisas como códigos de produto e hostnames.
  • Os filtros de tokens transformam o fluxo: passam para minúsculas, removem stop words, aplicam sinônimos, removem acentos (ASCIIFoldingFilter), quebram WiFi-2.4GHz em pedaços (WordDelimiterGraphFilter) e reduzem as palavras ao radical (SnowballPorterFilter para vários idiomas, ou o mais conservador KStemFilter para inglês).

A regra que mais causa confusão: a análise na indexação e a análise na consulta precisam ser compatíveis. Se o índice reduz birds a bird, mas a consulta não, nada corresponde. O Solr permite declarar as duas cadeias separadamente, e por bons motivos: a expansão de sinônimos e a geração de EdgeNGram costumam ser aplicadas só na indexação, para que a consulta continue sendo um único termo. A tela Analysis da interface de administração mostra os dois fluxos de tokens lado a lado e responde a "por que isso não bate?" mais rápido do que qualquer raciocínio.

Schema e tipos de campo

Um schema do Solr declara tipos de campo (uma cadeia de análise mais opções de armazenamento) e os campos que os usam. O Solr 9 usa um managed schema editável pela Schema API, embora a forma XML seja a que deixa a estrutura mais clara:

<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.ASCIIFoldingFilterFactory"/>
    <filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" expand="true"/>
    <filter class="solr.KStemFilterFactory"/>
  </analyzer>
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.ASCIIFoldingFilterFactory"/>
    <filter class="solr.KStemFilterFactory"/>
  </analyzer>
</fieldType>

<field name="id"        type="string"  indexed="true"  stored="true" required="true"/>
<field name="title"     type="text_en" indexed="true"  stored="true"/>
<field name="body"      type="text_en" indexed="true"  stored="false"/>
<field name="category"  type="string"  indexed="true"  stored="true" docValues="true"/>
<field name="price"     type="pdouble" indexed="true"  stored="true" docValues="true"/>
<field name="published" type="pdate"   indexed="true"  stored="true" docValues="true"/>

Quatro atributos carregam o significado. indexed torna um campo pesquisável. stored permite devolver o valor original. docValues constrói uma estrutura orientada a colunas usada para ordenação, facetas e function queries — ative-o em tudo o que você faceta ou ordena, e aí pode deixar stored desligado nos textos grandes que nunca exibe. E o tipo string não é analisado: a correspondência é exata — justamente o que você quer para identificadores, códigos de categoria e valores de faceta, e justamente o que você não quer para texto corrido.

Campos pelos quais você filtra de forma estrutural devem ser string; campos em que pessoas digitam palavras devem ser um TextField. Errar essa distinção é o erro de schema mais comum.

Indexação e consulta

Os documentos são enviados como JSON via HTTP, um a um ou em lotes. Use lotes, sempre — commits por documento são a causa clássica de um indexador lento.

curl -X POST 'http://localhost:8983/solr/catalogue/update?commitWithin=10000' \
  -H 'Content-Type: application/json' --data-binary '[
  { "id": "BK-1042", "title": "Field Guide to Estuaries",
    "body": "A practical guide to birds, tides and salt marshes.",
    "category": "reference", "price": 24.90, "published": "2026-02-11T00:00:00Z" }
]'

Use commitWithin (ou as configurações de autoCommit no solrconfig.xml) em vez de fazer commits explícitos: um hard commit grava os segmentos em disco, um soft commit torna os documentos visíveis para os searchers, e disparar qualquer um dos dois a cada documento destrói a vazão. Busca near real-time significa um soft commit a cada um ou dois segundos, não a cada escrita.

As consultas vão para o handler select. O parser edismax é o padrão certo para texto digitado pelo usuário: ele nunca lança erro de sintaxe por causa de uma pontuação perdida e distribui a consulta por vários campos com pesos independentes.

GET /solr/catalogue/select?defType=edismax
  &q=estuary birds
  &qf=title^4 body^1 category^2
  &pf=title^8
  &mm=2<-1
  &fq=category:reference
  &fq=price:[* TO 30]
  &sort=score desc, published desc
  &rows=20&fl=id,title,price,score

Cada parâmetro tem sua razão de estar ali. qf lista os campos a pesquisar e seus boosts. pf favorece documentos em que os termos da consulta aparecem como frase, e é assim que "estuary birds" vence um documento que cita as duas palavras em parágrafos sem relação. mm (minimum should match) controla quantos termos precisam estar presentes — 2<-1 significa "com dois termos, exija os dois; acima disso, permita que falte um". As filter queries fq são booleanas, não afetam o score e são cacheadas separadamente, então as restrições estruturais ficam ali, e não em q. Adicionar debugQuery=true retorna a consulta interpretada e uma explicação completa do score de cada documento; é, de longe, o diagnóstico mais útil que o Solr oferece.

Facetas, destaque e sugestões

Uma caixa de busca sozinha não é uma experiência de busca. Três recursos transformam resultados em navegação:

Facetas contam os documentos correspondentes por valor e alimentam a barra lateral de filtros. A JSON Facet API lida com aninhamento e estatísticas que os parâmetros antigos não conseguem:

{
  "query": "estuary birds",
  "filter": ["category:reference"],
  "facet": {
    "by_category": { "type": "terms", "field": "category", "limit": 10,
                     "facet": { "avg_price": "avg(price)" } },
    "price_bands": { "type": "range", "field": "price", "start": 0, "end": 100, "gap": 20 }
  }
}

Destaque (highlighting) devolve trechos com os termos encontrados marcados, para que o usuário veja por que um resultado correspondeu. O highlighter unified é o padrão atual e funciona melhor com storeOffsetsWithPositions ativado no campo.

Sugestões e ortografia. O componente suggester oferece autocompletar a partir de um dicionário construído sobre um campo; o componente spellcheck sugere correções do tipo "você quis dizer" a partir dos próprios termos do índice. Os dois precisam ser configurados no solrconfig.xml e reconstruídos conforme o conteúdo muda.

Rodando o Solr em cluster

O SolrCloud é o modo distribuído do Solr. Uma coleção é um índice lógico dividido em shards; cada shard tem uma ou mais réplicas espalhadas por nós diferentes, e uma réplica por shard é eleita líder e recebe o tráfego de indexação. O estado do cluster e a configuração ficam no Apache ZooKeeper, que também cuida da eleição de líder. A configuração é enviada como um configset, de modo que todos os nós rodam o mesmo schema e o mesmo solrconfig.xml.

# Upload a configset, then create a sharded, replicated collection
solr zk upconfig -n catalogue -d /opt/configsets/catalogue -z zk1:2181
curl 'http://localhost:8983/solr/admin/collections?action=CREATE&name=catalogue&numShards=2&replicationFactor=2&collection.configName=catalogue'

O dimensionamento segue algumas regras confiáveis. Faça sharding por tamanho de índice e vazão de indexação, e replique por vazão de consultas e disponibilidade; uma consulta é distribuída para todos os shards e os resultados são mesclados, então shards além do necessário acrescentam latência em vez de reduzi-la. Mantenha o heap da JVM moderado e deixe bastante RAM para o page cache do sistema operacional, porque o Lucene depende de arquivos mapeados em memória. Rodar um Solr de nó único em um contêiner serve bem para desenvolvimento — veja os fundamentos de Docker —, mas produção exige pelo menos três nós de ZooKeeper.

Ajustando a relevância

O trabalho de relevância é empírico. Comece com um conjunto de consultas reais cujos bons resultados já foram combinados, mude uma única coisa e meça de novo.

  • Boosts de campo (qf) expressam que uma correspondência no título vale mais do que uma no corpo. Mantenha as proporções modestas; boosts extremos tornam todo o resto irrelevante.
  • Boosts de frase (pf, pf2, pf3) recompensam a proximidade e costumam ser a mudança isolada de maior retorno.
  • Function queries incorporam ao score sinais não textuais: boost=recip(ms(NOW,published),3.16e-11,1,1) favorece documentos recentes, bf=sqrt(popularity) favorece os populares. Prefira o boost multiplicativo ao bf aditivo, para que um sinal forte não consiga soterrar o score textual.
  • Sinônimos e stop words corrigem o descompasso de vocabulário entre como você escreve e como os usuários buscam. Mantenha o arquivo de sinônimos como um ativo do produto — é ali que mora o conhecimento do domínio.
  • Learning to rank reordena os N primeiros resultados com um modelo treinado. Só vale a pena depois de esgotar o básico e quando você tem dados de cliques.

O Solr não é a ferramenta certa para todo problema com cara de busca. Grandes volumes de dados gerados por máquinas, com retenção baseada em tempo, pertencem a uma plataforma de logs — veja gerenciamento centralizado de logs com o Elastic Stack — e uma simples busca por palavra-chave em documentos que você já armazena pode ser resolvida pelos índices de texto do MongoDB, abordados no guia de MongoDB.

Principais pontos

  • Um índice invertido mapeia termos para listas de postings com frequências e posições, e é isso que torna possível a recuperação ranqueada; o BM25 recompensa a frequência do termo, a raridade do termo e campos curtos.
  • A cadeia de análise (filtros de caracteres, tokenizer, filtros de tokens) define o que é pesquisável, e as cadeias de indexação e de consulta precisam produzir tokens compatíveis.
  • Use string para valores estruturais exatos e um TextField para texto corrido; ative docValues em tudo o que você ordena, faceta ou usa em uma função.
  • Indexe em lotes com commitWithin e soft commits; hard commits por documento são o erro de desempenho clássico.
  • Consulte a entrada do usuário com edismax, coloque as restrições estruturais em filtros fq cacheados e diagnostique o scoring com debugQuery=true.
  • Ajuste a relevância com boosts de campo e de frase e function queries multiplicativas, medindo contra um conjunto fixo de consultas avaliadas, e não pela intuição.

Fontes e leituras complementares

Perguntas frequentes

Posso usar a busca full text do meu banco de dados em vez do Solr?
Para uma caixa de busca sobre algumas centenas de milhares de linhas, com necessidades modestas de relevância, a busca full text do PostgreSQL ou os índices de texto do MongoDB dão conta perfeitamente e poupam você de operar mais um serviço. Parta para o Solr quando precisar de análise configurável por idioma, navegação por facetas, destaque, sugestões, ajuste de relevância com boosts de campo e de frase, ou de um volume de consultas que atrapalharia seu banco transacional.
Devo usar o Solr como meu armazenamento de dados principal?
Não. O Solr não tem transações, nem chaves estrangeiras, nem atualização in-place — uma atualização reescreve o documento inteiro — e reindexar depois de uma mudança de schema é uma operação rotineira que pressupõe que os dados existem em outro lugar. Mantenha o sistema de registro em um banco de dados e trate o índice do Solr como uma projeção derivada dele, que pode ser reconstruída.
Por que minha frase exata não encontra nada?
Quase sempre é um descompasso de análise. As cadeias de análise da indexação e da consulta precisam produzir tokens comparáveis, então um stemmer, um filtro de minúsculas ou um filtro de sinônimos aplicado em só um dos lados quebra a correspondência sem nenhum aviso. Abra a tela Analysis na interface de administração do Solr, cole o valor indexado e a consulta, e compare os fluxos de tokens etapa por etapa.
Qual a diferença entre Solr e Elasticsearch?
Os dois são construídos sobre o Apache Lucene e compartilham a mesma estrutura de índice, os mesmos conceitos de análise e o scoring BM25, então o comportamento de busca é, de modo geral, comparável. Eles diferem nas APIs, no clustering (SolrCloud baseado em ZooKeeper versus uma camada de coordenação embutida), no licenciamento e no ecossistema. O Solr continua sob licença Apache e é forte em busca clássica de sites e catálogos; o Elasticsearch domina as cargas de logs e observabilidade.