Busca que encontra: full-text search com PostgreSQL e Elasticsearch
Busca é uma daquelas funcionalidades que parecem triviais até você tentar implementar direito. O usuário digita "camisa azul" e espera encontrar "camiseta na cor azul", tolerando que ele tenha escrito "camissa", e quer os resultados mais relevantes no topo, não numa ordem aleatória. Um LIKE '%termo%' no banco entrega o oposto de tudo isso: é lento, ignora relevância, não entende variações de palavra e não perdoa um erro de digitação. Busca de verdade é um problema de linguagem, não de comparação de strings.
Este guia trata de full-text search a sério. Como o PostgreSQL resolve busca textual bem mais do que a maioria imagina, quando isso é suficiente, e em que ponto vale migrar para um motor dedicado como o Elasticsearch ou o OpenSearch. O foco aqui é busca e relevância, um assunto distinto de escalar o banco com índices e particionamento.
Por que LIKE não é busca
O LIKE '%palavra%' é a primeira coisa que todo mundo tenta, e a primeira que quebra em produção. Ele tem três defeitos fatais como mecanismo de busca:
- É lento. Um curinga no início do padrão impede o uso de índices comuns, então o banco varre a tabela inteira.
- É literal. "correndo" não casa com "corre", "ratos" não casa com "rato". Ele não entende que são a mesma palavra em formas diferentes.
- Não ordena por relevância. Um resultado que menciona o termo uma vez vem junto com outro que o menciona dez vezes, sem distinção.
Busca precisa entender palavras e suas variações, e precisa rankear. É aí que entra o full-text search.
Full-text search no PostgreSQL
O que muita gente não sabe é que o PostgreSQL tem um motor de busca textual embutido, capaz e maduro. Ele funciona convertendo texto em uma forma otimizada para busca, o tsvector, e consultas em uma forma estruturada, o tsquery.
tsvector e tsquery
O tsvector transforma um texto em uma lista de lexemas, as palavras reduzidas à sua raiz, sem ruído. O processo, chamado stemming, faz "correndo", "correu" e "corre" virarem o mesmo lexema, e descarta stop words (palavras vazias como "de", "a", "o"). O tsquery representa os termos buscados, também normalizados, com operadores lógicos.
-- Documento vira tsvector; busca vira tsquery
SELECT titulo
FROM artigos
WHERE to_tsvector('portuguese', titulo || ' ' || corpo)
@@ to_tsquery('portuguese', 'escalabilidade & banco');
O operador @@ verifica se o documento casa com a consulta. Como os dois lados são normalizados pelo mesmo dicionário de idioma, "escalabilidade" encontra "escalável", e a busca entende português de verdade, não apenas caracteres.
Ranking de relevância
Encontrar não basta; é preciso ordenar por relevância. O PostgreSQL oferece ts_rank, que pontua cada resultado conforme a frequência e a proximidade dos termos no documento. Você ordena por essa pontuação e entrega os melhores primeiro, exatamente o que o LIKE nunca fez.
Índice GIN: o que torna tudo rápido
Sem índice, o banco recalcularia o tsvector de cada linha a cada busca. O índice GIN (Generalized Inverted Index) resolve isso guardando um mapa invertido de lexema para documentos, a mesma ideia que motores de busca dedicados usam. Com um GIN sobre a coluna de busca, consultas full-text ficam rápidas mesmo em milhões de registros. O padrão é materializar o tsvector em uma coluna própria e indexá-la com GIN.
Quando o PostgreSQL basta
Para uma parcela enorme dos produtos, o full-text search do PostgreSQL é mais que suficiente, e tem uma vantagem decisiva: os dados já estão lá. Você não precisa manter um segundo sistema sincronizado. O Postgres resolve bem quando:
- O volume de documentos está na casa de milhares a poucos milhões.
- A busca é sobre dados que já vivem no banco.
- Você precisa de stemming, ranking e busca por idioma, mas não de recursos avançados de relevância.
- Simplicidade operacional importa mais que sofisticação de busca.
Adotar um motor de busca dedicado cedo demais é um erro clássico: você dobra a complexidade da infraestrutura para resolver um problema que o banco que você já opera resolveria.
Quando migrar para Elasticsearch ou OpenSearch
Há um ponto em que a busca vira um produto dentro do produto, e um motor dedicado como o Elasticsearch ou o OpenSearch passa a compensar. Esses sistemas são construídos do zero para busca e trazem recursos que o Postgres não entrega bem:
- Tolerância a erros de digitação (fuzzy search). "camissa" encontra "camisa" por distância de edição. Fundamental em busca voltada ao usuário final.
- Relevância avançada e ajustável. Controle fino de pesos por campo, sinônimos, boosting e modelos de pontuação sofisticados.
- Facetas e agregações. Aqueles filtros de "marca", "cor", "faixa de preço" que se atualizam com a busca, calculados em tempo real.
- Autocomplete e sugestões enquanto o usuário digita.
- Escala horizontal para dezenas de milhões de documentos e alto volume de consultas, distribuída entre nós.
O custo é real: você passa a operar um sistema distribuído à parte e precisa sincronizar os dados do banco para o índice de busca, lidando com o atraso e a consistência dessa replicação.
Postgres vs. motor dedicado, lado a lado
| Critério | PostgreSQL full-text | Elasticsearch / OpenSearch |
|---|---|---|
| Infraestrutura | Já existe (o próprio banco) | Sistema distribuído à parte |
| Sincronização de dados | Nenhuma | Necessária, do banco ao índice |
| Typo tolerance (fuzzy) | Limitada | Nativa e forte |
| Facetas e agregações | Básicas | Avançadas |
| Autocomplete e sugestões | Manual | Nativo |
| Escala de documentos | Milhares a milhões | Dezenas de milhões e além |
| Complexidade operacional | Baixa | Alta |
Um caminho de evolução saudável
A trajetória mais sensata não começa com Elasticsearch. Ela começa com o que você já tem e sobe de degrau quando a dor aparece:
- Comece no PostgreSQL com
tsvector,tsquery,ts_ranke índice GIN. Resolve a maioria dos casos com zero infraestrutura nova. - Meça a experiência de busca real. Os usuários reclamam de resultados irrelevantes? Erram a digitação e não encontram nada? Pedem filtros que se atualizam?
- Migre para um motor dedicado quando o produto exigir relevância fina, fuzzy, facetas ou escala que o Postgres não acompanha, e não antes disso.
Busca é uma funcionalidade que os usuários julgam em segundos: ou encontra o que eles querem, ou eles vão embora. A boa notícia é que você raramente precisa de um sistema complexo para começar bem. O PostgreSQL leva você surpreendentemente longe, e o motor dedicado entra quando a busca deixa de ser um detalhe e vira um diferencial competitivo.
Sua busca frustra os usuários com resultados fracos ou lentos? Na ConstructorTI, desenhamos busca do jeito certo para o seu estágio, do full-text nativo do PostgreSQL bem indexado a um motor dedicado com relevância, fuzzy e facetas quando o produto pede. Se o seu campo de busca hoje entrega menos do que deveria, fale com a nossa engenharia e receba um diagnóstico da sua camada de busca.