Artigo

Como Funciona um Sistema de Buscas de Verdade (e por que LIKE não é busca)

18 min de leitura

Vai no Google e pesquisa "Java Script" — assim, com espaço, sem o C. Ele encontra. Não só encontra como identifica que você tava falando de JavaScript. Pesquisa "cafe" sem acento — ele entende que é "café". Pesquisa "como fazer deploy" — e te retorna resultados que não necessariamente têm esse texto exato escrito em lugar nenhum.

É assim que um sistema de buscas funciona. E é assim que ele deveria funcionar.

Agora olha o que a maioria dos devs faz quando precisa buscar alguma coisa no banco:

1SELECT * FROM products WHERE name LIKE '%termo%';

Isso não é um sistema de buscas. Isso é um filtro de string.

Funciona? Funciona — pra sistemas pequenos, que não precisam de muita precisão. Mas no momento que seu banco cresce, que o usuário erra uma letra, esquece um acento ou pesquisa algo que não é exatamente o que tá escrito no banco, tudo desmorona.

Nesse artigo eu vou te mostrar, passo a passo, tudo o que acontece por trás de um sistema de buscas de verdade. São 5 conceitos que, juntos, transformam uma busca amadora numa busca profissional. Vamos lá.


O problema do LIKE

Antes de entender a solução, vale entender direito o problema. O LIKE com %termo% parece inofensivo, mas ele tem dois problemas sérios que escalam rápido:

1. Performance: Full Table Scan

Quando você usa LIKE '%celular%', o banco de dados faz o que chamamos de Full Table Scan — ele literalmente varre todos os registros da tabela, um por um, comparando string por string pra ver se bate.

1-- Isso aqui varre TODA a tabela, registro por registro 2SELECT * FROM products WHERE name LIKE '%celular%';

Com 100 registros? De boa. Com 10 mil? Começa a ficar lento. Com 100 mil, um milhão de registros? Inviável. Cada busca do usuário vai ser uma operação pesada no banco, e isso não escala.

E não adianta colocar índice B-Tree na coluna — o LIKE com % no começo invalida qualquer índice tradicional. O banco não tem como otimizar isso. Ele precisa ler tudo.

2. Precisão: bytes, não linguagem

O LIKE não entende linguagem. Ele entende bytes. E isso significa que qualquer variação mínima entre o que o usuário digitou e o que tá no banco vai quebrar a busca:

  • O usuário pesquisa "celular" com C minúsculo, mas no banco tá "Celular" com C maiúsculo → não encontra
  • O usuário pesquisa "cafe" sem acento, mas no banco tá "Café" com acento → não encontra
  • O usuário erra uma letra: "celuar" em vez de "celular" → não encontra
1-- Nenhuma dessas queries retorna "Café com Leite" 2SELECT * FROM products WHERE name LIKE '%cafe%'; -- sem acento 3SELECT * FROM products WHERE name LIKE '%caffe%'; -- typo 4SELECT * FROM products WHERE name LIKE '%CAFÉ%'; -- case diferente

O LIKE compara texto exato. Mas busca não é sobre texto exato — busca é sobre intenção. Quando o usuário pesquisa alguma coisa, ele quer encontrar algo que às vezes ele nem sabe como se escreve. O sistema precisa ser inteligente o bastante pra lidar com isso, porque o sistema é pra ele, não pra você.


Normalização: preparando o texto antes da busca

Tudo começa antes do usuário pesquisar. Antes da busca acontecer, o texto precisa ser trabalhado, tratado. O nome disso é normalização — o processo de pegar um texto e transformar ele numa versão padronizada.

É como se o texto guardado no banco e o que o usuário vai usar pra buscar fossem coisas diferentes. Os dois passam pelo mesmo processo de normalização, e aí sim a comparação faz sentido.

O pipeline de normalização

Pega esse texto como exemplo: "JavaScript é INCRÍVEL, programação 100%!"

Ele tem J maiúsculo, S maiúsculo, "INCRÍVEL" todo em caps, acentuação, cedilha, til, número, porcentagem. Tudo isso precisa ser tratado.

Passo 1 — Lowercase (tudo pra minúsculo)

"javascript é incrível, programação 100%!"

Passo 2 — Remoção de acentos e diacríticos

"javascript e incrivel, programacao 100%!"

Sai o acento do "é", do "í", o cedilha do "ç", o til do "ã".

Passo 3 — Remoção de caracteres especiais

"javascript e incrivel programacao 100"

Tudo que for símbolo especial — exclamação, porcentagem, vírgula, ponto — some nessa fase.

Depois disso, pode escrever "JavaScript" tudo maiúsculo, "jAvAsCrIpT" alternando case, "JAVASCRIPT" gritando — tudo vai ser normalizado pro mesmo resultado. A comparação passa a funcionar independente de como o usuário escreveu.

Stemming: reduzindo palavras à raiz

Alguns sistemas vão um passo além e aplicam stemming — o processo de pegar uma palavra e reduzir ela pra sua versão base, sua raiz.

programando  → program
programador  → program
programação  → program
programar    → program

Todas essas palavras diferentes viram "program". Então se o usuário pesquisa "programando" e o texto no banco tem "programar", os dois batem — porque os dois compartilham a mesma raiz.

Isso é extremamente poderoso. O usuário não precisa acertar a conjugação exata, o tempo verbal, o sufixo. O sistema entende que são variações da mesma ideia.

Stopwords: tirando o ruído

Também tem a remoção de stopwords — palavras que são interjeições, artigos, preposições e conectivos que não agregam valor semântico à busca:

Removidas: o, a, de, para, é, um, uma, em, com, que, do, da, no, na...

Se o usuário pesquisa "como fazer deploy de uma aplicação", depois da remoção de stopwords fica: "fazer deploy aplicação". As palavras "como", "de", "uma" não ajudam a busca — elas só geram volume. Remover elas melhora a performance sem perder o sentido.

Na prática, o texto original:

"JavaScript é uma linguagem de programação popular"

Depois de normalização completa (lowercase + acentos + especiais + stemming + stopwords) vira:

["javascript", "linguagem", "program", "popular"]

Limpo, padronizado e pronto pra ser indexado.


Índice Invertido: o coração do sistema de buscas

Se você for tirar uma coisa desse artigo, tira essa. Isso aqui é o conceito mais importante de qualquer sistema de busca.

O problema da busca linear

Imagina que você tem 3 documentos no banco:

DocTítuloConteúdo
1Intro a JS"JavaScript é uma linguagem de programação popular"
2Intro a Python"Python é uma linguagem de programação moderna"
3Comparativo"JavaScript e Python são linguagens de programação modernas"

Quando o usuário pesquisa "JavaScript", num sistema com LIKE ou busca linear, você teria que varrer documento por documento, abrir cada um, procurar a palavra "JavaScript" dentro do texto, e retornar os que batem.

Com 3 docs? De boa. Com 3 milhões? Absurdo.

A inversão da lógica

O índice invertido inverte essa lógica completamente. Em vez de ir no documento e procurar a palavra, você vai na palavra e descobre em quais documentos ela tá.

Funciona assim: quando um documento é inserido no sistema, ele passa por todo aquele processo de normalização que a gente viu, e cada palavra resultante é registrada num índice separado, apontando pros documentos que a contêm:

TermoDocumentos
javascript1, 3
python2, 3
linguagem1, 2, 3
program1, 2, 3
popular1
moderna2, 3

Agora, quando o usuário pesquisa "JavaScript", o sistema:

  1. Normaliza o termo de busca → "javascript"
  2. Vai direto no índice invertido → encontra que "javascript" está nos docs 1 e 3
  3. Retorna os docs 1 e 3

Sem ler nenhum documento. Sem varrer nada. Direto ao ponto.

E se o usuário pesquisa "JavaScript Python"? O sistema busca os dois termos no índice:

  • "javascript" → docs 1, 3
  • "python" → docs 2, 3

Intersecção: doc 3 tem os dois. Ele vem primeiro. Docs 1 e 2 têm só um dos termos — vêm depois, com score menor.

A analogia do glossário

É exatamente a mesma ideia dos glossários de livros. Você vai lá no final do livro, procura um termo, e ele te diz: "página 42, parágrafo 3". Você não precisa ler o livro inteiro pra achar onde aquela palavra foi mencionada.

O índice invertido é o glossário do seu banco de dados — só que digital, automático, e absurdamente rápido.

Quem usa isso?

É exatamente por isso que ferramentas como Elasticsearch, Algolia, MeiliSearch e Typesense implementam índice invertido por baixo dos panos. Quando você faz uma busca nessas ferramentas, o que tá acontecendo é isso: o termo é normalizado, o índice é consultado, e os documentos são retornados sem precisar ler nenhum deles na hora da query.

Até o Postgres Full Text Search usa uma variação dessa lógica internamente quando você cria um índice GIN em colunas tsvector.


Fuzzy Matching: tolerando erros de digitação

O índice invertido resolve velocidade e precisão — quando o usuário digita a palavra certa. Mas e quando ele erra? "Javasript" sem o C. "Pythn" sem o O. "Rect" em vez de "React".

O índice não vai encontrar "Javasript" porque essa palavra simplesmente não existe nele. E aí?

Distância de Levenshtein

Aí entra o Fuzzy Matching — busca aproximada. O conceito por trás é a distância de Levenshtein (ou distância de edição): um cálculo que mede quantas operações mínimas são necessárias pra transformar uma palavra em outra.

As operações são três: inserir, alterar e remover uma letra.

"Javasript" → "Javascript"
  Inserir o 'c' entre 's' e 'r'
  Distância: 1

"Pythn" → "Python"
  Inserir o 'o' entre 'h' e 'n'
  Distância: 1

"Rect" → "React"
  Trocar 'c' por 'a' + inserir 'c' na posição correta
  Distância: 2

Tolerância configurável

Na prática, o sistema define uma tolerância. Tipo: se a distância entre o que o usuário digitou e uma palavra do índice for até 2, a gente aceita como match.

O Elasticsearch, por exemplo, tem isso nativo. Quando você faz uma query, pode definir o nível de fuzziness:

1{ 2 "query": { 3 "match": { 4 "title": { 5 "query": "javasript", 6 "fuzziness": "AUTO" 7 } 8 } 9 } 10}

O "AUTO" define a tolerância automaticamente baseado no tamanho da palavra:

  • Palavras com 1-2 caracteres: distância 0 (match exato)
  • Palavras com 3-5 caracteres: distância 1
  • Palavras com 6+ caracteres: distância 2

O Google faz isso numa escala muito maior — ele não só tolera o erro como ainda sugere: "Você quis dizer JavaScript?"

O custo computacional

Um detalhe importante: fuzzy matching é caro. Você tá essencialmente comparando a palavra do usuário com potenciais milhares (ou milhões) de palavras no índice, calculando a distância de cada uma.

Pra não explodir em performance, os sistemas usam otimizações:

  • N-grams: quebrar palavras em pedaços de N caracteres e comparar os pedaços. "javascript" com bigrams vira ["ja", "av", "va", "as", "sc", "cr", "ri", "ip", "pt"]. Palavras similares compartilham muitos n-grams, então dá pra filtrar candidatos antes de calcular a distância completa.
  • Autômatos de Levenshtein: uma estrutura que permite verificar todas as palavras dentro de uma distância X de forma muito mais eficiente do que calcular par a par.

Você não vai implementar isso do zero (e nem deveria). Mas saber que existe te ajuda a entender por que ferramentas como Elasticsearch conseguem fazer fuzzy search em milissegundos mesmo com índices gigantes.


Ranking com TF-IDF: quem aparece primeiro?

Beleza, o sistema encontrou resultados. Mas e quando a busca retorna 500, 1.000 registros? Qual vem primeiro? Qual é mais relevante?

Busca sem ranking é só uma lista aleatória. E o que separa um sistema de busca bom de um ruim é exatamente isso: a capacidade de ranquear os resultados por relevância.

O algoritmo TF-IDF

O algoritmo clássico pra ranking de busca se chama TF-IDF — Term Frequency, Inverse Document Frequency. São duas métricas combinadas:

TF (Term Frequency) — quantas vezes o termo aparece no documento.

TF("kubernetes", doc_A) = 15   → aparece 15 vezes
TF("kubernetes", doc_B) = 1    → aparece 1 vez

Quanto mais vezes a palavra aparece no documento, mais relevante esse documento é pra aquele termo.

IDF (Inverse Document Frequency) — o quão rara aquela palavra é no conjunto total de documentos.

IDF = log(total de documentos / documentos que contêm o termo)

Se a palavra aparece em poucos documentos, ela é rara — o IDF é alto, ela é mais relevante. Se aparece em quase todos os documentos, ela é comum — o IDF é baixo, ela é irrelevante pra diferenciar resultados.

O score final é TF × IDF.

Exemplo concreto

Imagina que o usuário busca "Kubernetes deploy" e você tem 1.000 artigos no banco.

Artigo A: "Kubernetes" aparece 15 vezes, "deploy" aparece 8 vezes.

  • TF alto pros dois termos
  • IDF de "Kubernetes" é alto (aparece em poucos artigos)
  • Score final: alto → esse artigo é super relevante

Artigo B: "Kubernetes" aparece 1 vez, lá no rodapé, num link lateral.

  • TF baixo
  • Score final: baixo → esse artigo mal fala do assunto

E a palavra "como"? Ela aparece em 95% dos artigos do banco. O IDF dela é praticamente zero. Se o usuário pesquisa "Kubernetes deploy como fazer", o sistema basicamente ignora o "como" — ele não ajuda em nada a diferenciar resultados.

Combinando os scores de "Kubernetes" e "deploy", o sistema consegue varrer todos os artigos e trazer exatamente o mais relevante no topo. Sem heurísticas manuais, sem gambiarras — pura matemática.

Além do TF-IDF

O TF-IDF é o algoritmo básico, mas sistemas modernos usam variações mais sofisticadas:

  • BM25 (usado pelo Elasticsearch por padrão): uma evolução do TF-IDF que normaliza pelo tamanho do documento (evita que documentos longos tenham vantagem injusta)
  • Boosting por campo: dar mais peso pro título do que pro corpo do texto — se "Kubernetes" tá no título, é mais relevante do que se tiver só no rodapé
  • Recency: documentos mais recentes podem ter boost no score
  • Popularidade: documentos mais acessados podem receber um boost

Mas todos partem da mesma ideia base: TF-IDF.


Autocomplete: a Trie (árvore de prefixo)

Sabe quando você vai pesquisar no Google, digita "pro", e antes de apertar Enter ele já sugere "programação", "professor auxiliar", "prouni", "procon"?

Isso não faz parte do sistema de buscas em si — é o autocomplete, praticamente um sistema à parte. Mas é igualmente necessário pra uma experiência de busca completa.

Como funciona

Enquanto o sistema de buscas usa o índice invertido, o autocomplete usa uma estrutura de dados chamada Trie (pronuncia-se "trai"), também conhecida como árvore de prefixo.

A ideia é simples: cada letra é um nó da árvore. Quando o usuário digita "pro", o sistema percorre os nós P → R → O e, a partir daí, explora todos os caminhos possíveis:

         P
         |
         R
         |
         O
       / | \
      G  D  J
      |  |  |
      R  U  E
      |  |  |
      A  T  T
      |  |  |
      M  O  O
      |
      A
      |
      Ç
      |
      Ã
      |
      O

→ programação
→ produto
→ projeto

A Trie permite encontrar todas as palavras que começam com um prefixo de forma extremamente rápida — a complexidade é O(m), onde m é o tamanho do prefixo, independente de quantas palavras existem no dicionário.

Relevância no autocomplete

Mas não adianta retornar 500 palavras que começam com "pro". O sistema precisa saber quais são as mais relevantes — e relevância aqui pode significar:

  • Palavras mais buscadas (popularidade global)
  • Palavras mais buscadas por aquele usuário (personalização)
  • Palavras mais recentes (trending)
  • Palavras que fazem sentido no contexto da aplicação

Por isso cada nó da Trie geralmente armazena não só a letra, mas também um score de popularidade. Assim, ao percorrer a árvore, o sistema já retorna as sugestões ordenadas por relevância.

Não é LIKE

O ponto é: o autocomplete não é um LIKE no banco de dados. Não é um SELECT * FROM words WHERE word LIKE 'pro%'. É uma estrutura de dados inteira, pensada e otimizada especificamente pra esse caso de uso, e que faz diferença real na experiência do usuário.


Na prática: qual ferramenta usar?

Agora que você entende os conceitos, a pergunta prática: qual banco, qual ferramenta usar pro seu cenário?

Postgres Full Text Search — pra maioria dos casos

Se você tem uma aplicação pequena ou média, o Postgres com Full Text Search já é suficiente. Sério. Ele já faz:

  • Normalização e tokenização
  • Stemming (com dicionários por idioma)
  • Remoção de stopwords
  • Ranking com ts_rank
  • Índice GIN pra performance
1-- Criando uma coluna de busca otimizada 2ALTER TABLE articles ADD COLUMN search_vector tsvector; 3 4UPDATE articles SET search_vector = 5 to_tsvector('portuguese', title || ' ' || content); 6 7CREATE INDEX idx_search ON articles USING GIN(search_vector); 8 9-- Buscando com ranking 10SELECT title, ts_rank(search_vector, query) AS rank 11FROM articles, to_tsquery('portuguese', 'javascript & deploy') query 12WHERE search_vector @@ query 13ORDER BY rank DESC;

Pra muitos cenários, isso é mais do que suficiente. Você não precisa de nenhuma ferramenta externa, nenhum serviço adicional, nenhum custo extra. Tá tudo ali no banco que você já usa.

Elasticsearch — quando busca é core do produto

Se a busca é uma ferramenta central no seu produto — tipo um e-commerce, um marketplace, uma plataforma de conteúdo — aí recomendo ir pro Elasticsearch.

Ele tem tudo nativo: fuzzy matching, autocomplete, highlighting, agregações, filtros complexos. É poderoso, flexível, extremamente popular, e tem uma comunidade enorme.

1{ 2 "query": { 3 "bool": { 4 "must": [ 5 { 6 "multi_match": { 7 "query": "javascript tutorial", 8 "fields": ["title^3", "content"], 9 "fuzziness": "AUTO" 10 } 11 } 12 ] 13 } 14 }, 15 "highlight": { 16 "fields": { 17 "content": {} 18 } 19 } 20}

O title^3 ali dá 3x mais peso pro título do que pro conteúdo. O fuzziness: "AUTO" tolera erros de digitação. O highlight retorna os trechos com a palavra encontrada marcada. Tudo num request.

Mas: ele é mais pesado, precisa de atenção, manutenção, monitoramento. Você precisa cuidar dele com um pouco mais de carinho.

Alternativas mais leves

Se você quer algo mais simples que o Elasticsearch mas mais poderoso que o Postgres:

  • MeiliSearch — open source, setup ridiculamente simples, ótimo pra aplicações onde você precisa de busca rápida sem muita configuração. Fuzzy search nativo, typo tolerance, e uma API bem intuitiva.
  • Typesense — também open source, focado em facilidade de uso e performance. Muito bom pra search-as-you-type.
  • Algolia — SaaS (pago), mas extremamente rápido e com SDKs pra tudo. Se não quiser gerenciar infraestrutura, é uma opção sólida.

A complexidade deles é bem mais baixa do que a do Elasticsearch. O trade-off é que você perde flexibilidade e poder de customização.


O fluxo completo: tudo junto

Pra fechar, vamos ver como todas essas peças se conectam num fluxo real.

O usuário vai lá no seu sistema e digita: "javasript tutorial"

1. Normalização A busca é quebrada em tokens normalizados:

"javasript tutorial" → ["javasript", "tutorial"]

2. Fuzzy Matching O sistema compara cada token com o índice:

  • "tutorial" → match 100%, distância 0 ✓
  • "javasript" → match com "javascript", distância 1 (falta o 'c') ✓

3. Índice Invertido Com os termos corrigidos, o sistema consulta o índice:

  • "javascript" → docs [1, 3, 7, 12, 45, ...]
  • "tutorial" → docs [3, 8, 12, 23, 45, ...]
  • Intersecção: docs [3, 12, 45, ...] têm os dois termos

4. Scoring / Ranking Pra cada documento encontrado, calcula o TF-IDF (ou BM25):

  • Doc 3: "javascript" aparece 12x, "tutorial" aparece 8x → score alto
  • Doc 45: "javascript" aparece 2x, "tutorial" aparece 1x → score médio
  • Doc 12: "javascript" aparece 1x no rodapé → score baixo

5. Retorno Os resultados são ordenados por score e retornados pro usuário.

Tudo isso rodando em milissegundos. E essa divisão em etapas — normalização, fuzzy, índice, ranking — junto com a velocidade, é o que separa um sistema de buscas amador de um profissional.


Conclusao

A gente tá tão acostumado com sistemas de busca bons — Google, YouTube, Amazon — que acha que é simples. Que é só um LIKE no banco. Mas quando você precisa implementar algo assim, e percebe que o LIKE não aguenta, aí bate aquela realidade.

Agora você sabe o que tá por trás:

  • Normalização pra padronizar texto
  • Índice invertido pra buscar sem ler documentos
  • Fuzzy matching pra tolerar erros
  • TF-IDF pra ranquear por relevância
  • Trie pra autocomplete inteligente

Você não precisa implementar nada disso do zero. Elasticsearch, MeiliSearch, Typesense, e até o próprio Postgres já fazem o trabalho pesado. Mas entender o que tá acontecendo por trás te dá poder de decisão — saber quando usar o quê, entender os trade-offs, e construir sistemas que escalam de verdade.

Esse tipo de conhecimento de arquitetura vale dinheiro. Não à toa o Google, a Algolia e várias outras empresas construíram negócios bilionários em cima disso.

Racoelho

Conteúdo sobre desenvolvimento, tecnologia e desafios de programação para impulsionar sua carreira em tech.

Conecte-se

© 2024- 2026 Racoelho. Todos os direitos reservados.

v3.0.15 • Build: 2026-03-17