rcRacoelho
HomeBlogDesafiosProjetosSetupVagasComunidade
rcRacoelho

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

Links rápidos
  • Blog
  • Desafios
  • Projetos
  • Setup
  • Vagas
  • Comunidade
  • Links
Conecte-se
    © 2024–2026 Racoelho. Todos os direitos reservados.v3.0.27 · build 2026-10-04
    rcRacoelho
    HomeBlogDesafiosProjetosSetupVagasComunidade
    blog / Arquitetura de Sistemas / como-funciona-um-sistema-de-buscas-de-verdade
    Arquitetura de SistemasDesenvolvimento de Software

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

    Rafael Coelho
    25 mar 2026 · 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:

    sql
    SELECT * 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.

    sql
    -- Isso aqui varre TODA a tabela, registro por registro SELECT * 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
    sql
    -- Nenhuma dessas queries retorna "Café com Leite" SELECT * FROM products WHERE name LIKE '%cafe%'; -- sem acento SELECT * FROM products WHERE name LIKE '%caffe%'; -- typo SELECT * 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.

    code
    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.

    code
    "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:

    json
    { "query": { "match": { "title": { "query": "javasript", "fuzziness": "AUTO" } } } }

    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.

    code
    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:

    code
    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
    sql
    -- Criando uma coluna de busca otimizada ALTER TABLE articles ADD COLUMN search_vector tsvector; UPDATE articles SET search_vector = to_tsvector('portuguese', title || ' ' || content); CREATE INDEX idx_search ON articles USING GIN(search_vector); -- Buscando com ranking SELECT title, ts_rank(search_vector, query) AS rank FROM articles, to_tsquery('portuguese', 'javascript & deploy') query WHERE search_vector @@ query ORDER 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.

    json
    { "query": { "bool": { "must": [ { "multi_match": { "query": "javascript tutorial", "fields": ["title^3", "content"], "fuzziness": "AUTO" } } ] } }, "highlight": { "fields": { "content": {} } } }

    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.

    Neste artigo
    • O problema do LIKE
    • Normalização: preparando o texto antes da busca
    • Índice Invertido: o coração do sistema de buscas
    • Fuzzy Matching: tolerando erros de digitação
    • Ranking com TF-IDF: quem aparece primeiro?
    • Autocomplete: a Trie
    • Na prática: qual ferramenta usar?
    • O fluxo completo: tudo junto
    • Conclusao

    Leia tambémContinue lendo

    14 min

    CQRS na Prática: Como Separar Leitura e Escrita Transforma Sua Arquitetura

    Arquitetura de Sistemas
    9 min

    Como Arquitetar um Sistema de Notificações que Aguenta Escala

    Arquitetura de Sistemas
    11 min

    Seu Sistema Está Lento? O Problema Não É o Banco de Dados — É Quando Você Processa os Dados

    Arquitetura de Sistemas
    rcRacoelho

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

    Links rápidos
    • Blog
    • Desafios
    • Projetos
    • Setup
    • Vagas
    • Comunidade
    • Links
    Conecte-se
      © 2024–2026 Racoelho. Todos os direitos reservados.v3.0.27 · build 2026-10-04