DevGuideTK
← Trilha Backend

Busca textual

Índice invertido, relevância e os motores de busca como Elasticsearch e OpenSearch.

As decisões de busca são quatro: onde buscar, como analisar o texto, como lidar com erros de digitação e como manter o índice igual aos dados.

Onde buscar

OpçãoBom paraCustos
PostgreSQL com tsvector e GINBusca por palavras com acentos, radicais, pesos e frases, sem sistema a maisRelevância simples (ts_rank); sem busca aproximada embutida
PostgreSQL com pg_trgmBusca por trechos e ILIKE rápidos; nomes curtos com um erro de digitaçãoFraco com vários erros (0,40 para o tênis e para a mochila, no teste)
OpenSearch ou ElasticsearchBusca como produto: relevância BM25, busca aproximada, sugestões, filtros com contagens, sinônimosOutro sistema para operar, com índice que precisa ser sincronizado
Meilisearch, TypesenseBusca para o usuário final, com tolerância a erros já ajustada, mais simples de operarMenos flexíveis em consultas complexas
Busca gerenciada (Algolia, OpenSearch Service, Elastic Cloud)Sem operar servidoresCusto por uso; os dados saem do seu ambiente
NecessidadeEscolha
Achar registros por palavras numa tela administrativaPostgreSQL com tsvector
Autocompletar nomes de cidades ou clientesPostgreSQL com pg_trgm, ou índice de prefixo
Busca da loja, com erros de digitação, filtros e relevância ajustadaOpenSearch, Elasticsearch ou Meilisearch
Busca em logsFerramentas de logs (ver o tópico de logs)
Busca por significado ("algo para correr na chuva")Busca vetorial (ver o tópico de embeddings e RAG), muitas vezes combinada com a textual

Como analisar o texto

DecisãoRecomendação
AcentosRemover na análise (unaccent no PostgreSQL, asciifolding no OpenSearch)
RadicalizaçãoUsar, mas testar com as palavras do domínio: o radicalizador leve transformou "tênis" em tenil
Palavras vaziasRemover as da língua; cuidado com as que importam no domínio ("sem fio", "não tóxico")
SinônimosUma lista do domínio: "celular" e "smartphone", "tênis" e "sapatilha"
Campos e pesosNome com peso maior que a descrição
Vários campos para o mesmo textoUm com radicalização, para relevância; outro sem, para busca aproximada e frases exatas

Erros de digitação

TécnicaComo funcionaCuidados
Busca aproximada (fuzziness)Aceita 1 ou 2 letras diferentes por palavraRodar num campo sem radicalização; no teste, sobre os radicais, não achou nada
TrigramasSemelhança por trechos de três letrasBom para nomes curtos e um erro; ruim com vários erros e palavras comuns
"Você quis dizer" (suggest)Sugere a palavra mais próxima que existe no índiceMostrar a sugestão em vez de trocar a busca em silêncio
FonéticaAgrupa palavras que soam parecidoÚtil para nomes de pessoas

Uma combinação comum: buscar primeiro sem tolerância a erros, no campo com radicalização; se vier pouco ou nada, buscar com tolerância no campo sem radicalização. No teste, essa combinação devolveu os tênis de corrida para "tenys corida".

Manter o índice igual aos dados

Com o PostgreSQL, a coluna tsvector gerada pelo próprio banco nunca diverge da linha. Com um motor separado, o índice é uma cópia:

FormaAtrasoRiscos
A aplicação grava no banco e no índiceQuase nenhumUma das duas gravações falha, e os dois divergem
Fila de saída (outbox) e um consumidorSegundosNenhuma perda, se o consumidor for idempotente
Captura de mudanças (CDC) do bancoSegundosInfraestrutura a mais (Debezium, Kafka)
Reindexação periódicaHorasSimples; serve para catálogos que mudam pouco

Mesmo com a cópia em dia, o motor demora para mostrar o que foi gravado: no teste, um produto novo só apareceu na busca 1,5 segundo depois de gravado. Telas que gravam e logo em seguida buscam o próprio registro precisam ler do banco principal, ou pedir que o índice seja atualizado (refresh) nessa gravação específica.

E tenha sempre um caminho para reconstruir o índice do zero a partir do banco: mudanças de analisador exigem reindexar tudo. No teste, a reindexação dos 500 mil produtos com um campo novo levou 9 segundos.

Resumo da decisão

  1. PostgreSQL com tsvector, unaccent e GIN para a maioria das buscas; motor dedicado quando a busca é o centro do produto.
  2. Análise testada com as palavras do domínio: acentos, radicais, palavras vazias e sinônimos.
  3. Pesos por campo e ordenação por relevância.
  4. Tolerância a erros num campo sem radicalização, como segunda tentativa.
  5. Índice separado como cópia, sincronizado por fila de saída ou CDC, e reconstruível do zero.

O próximo nível mostra a busca no PostgreSQL nas cinco linguagens e a configuração do OpenSearch testada.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Escolher a busca do meu sistema

    Quero decidir como implementar a busca no meu sistema.
    
    Pergunte o que é buscado, o volume, quem busca (equipe interna ou clientes), se há erros de digitação, filtros e sugestões, e qual banco eu uso.
    
    Compare para o meu caso: PostgreSQL com tsvector, PostgreSQL com pg_trgm, OpenSearch ou Elasticsearch, Meilisearch e um serviço gerenciado. Recomende uma opção e explique o que eu ganharia e perderia com as outras.
  • Melhorar a qualidade da busca

    A busca do meu sistema funciona, mas os usuários reclamam dos resultados.
    
    Pergunte qual tecnologia eu uso, peça exemplos de buscas com resultados ruins e como os campos estão configurados.
    
    Me guie:
    1. Montar um conjunto de buscas de teste com os resultados esperados.
    2. Conferir a análise do texto com as palavras do domínio.
    3. Ajustar pesos, sinônimos e a tolerância a erros.
    4. Registrar as buscas sem resultado e as buscas abandonadas.
    5. Medir antes e depois de cada mudança.
  • Sincronizar um motor de busca

    Vou usar um motor de busca separado do banco principal.
    
    Pergunte qual banco e qual motor eu uso, quantos registros mudam por dia e com que atraso a busca pode ficar.
    
    Compare gravação dupla, fila de saída, captura de mudanças (CDC) e reindexação periódica. Monte comigo a opção escolhida, com o tratamento de falhas, a reconstrução do índice do zero e a troca de analisador sem parar a busca (índice novo e um apelido).