Banco de dados vetorial: qual usar e quando pgvector basta
Todo sistema de RAG vive ou morre pelo banco vetorial. É ele que decide se a busca devolve o parágrafo certo ou um trecho sem nexo — e, no fim, se o seu agente responde com base no documento ou alucina com cara de confiança.
O problema é que "banco de dados vetorial" virou palavra de ordem antes de virar entendimento. Muita gente já saiu escolhendo entre Pinecone, Qdrant, Weaviate e pgvector sem saber o que essas ferramentas fazem por dentro. Escolher a caixa antes de entender o que ela guarda é receita de retrabalho.
Então vamos do começo, de baixo pra cima: o que é um banco de dados vetorial, como a busca por similaridade funciona de verdade, e como você monta um em cima do Postgres que provavelmente já está rodando no seu projeto. Sem hype. Só engenharia.
TL;DR
- O que é: um banco especializado em guardar e indexar embeddings (vetores) e responder "quais itens são mais parecidos com este?" em milissegundos, em vez de "quais itens são exatamente iguais a este?".
- Como busca: compara a distância entre vetores e devolve os vizinhos mais próximos, usando busca aproximada (ANN) para não varrer a base inteira.
- Stack de exemplo: PostgreSQL + pgvector 0.8.x — a mesma similaridade dos serviços dedicados, dentro do banco que você já tem.
- Quando precisa: RAG, busca semântica, recomendação, deduplicação, memória de agente. Qualquer coisa que dependa de "parecido", não de "igual".
O que é um banco de dados vetorial?
Um banco relacional guarda dado escalar — strings, números, datas — em linhas e colunas, e responde perguntas de igualdade: WHERE email = 'lucas@...', WHERE status = 'pago'. Ou é igual, ou não é. Não existe "quase".
Um banco de dados vetorial resolve outro problema. Ele guarda embeddings: representações numéricas de texto, imagem ou áudio, geralmente com centenas ou milhares de dimensões, onde conteúdos com significado parecido ficam matematicamente próximos. Se você ainda não tem intimidade com essa parte, vale ler antes o que é um embedding — é a matéria-prima que o banco vetorial guarda.
A diferença está na pergunta que ele sabe responder. Como resume a Pinecone, um banco vetorial "aplica uma métrica de similaridade para encontrar o vetor mais parecido com a nossa consulta" — não o idêntico, o mais próximo. Você joga a pergunta "modelo de embedding barato pra português" e ele te devolve os chunks que falam sobre custo de embedding em pt-br, mesmo que nenhum use exatamente essas palavras.
Conceito técnico → aplicação prática → impacto no produto: é essa capacidade de buscar por sentido que sustenta busca semântica, RAG, recomendação, memória de longo prazo de agente e deduplicação. Tudo que depende de "parecido" em vez de "igual" passa por aqui.
Vale a distinção fina: existe índice vetorial (uma biblioteca como FAISS, que só faz a busca) e existe banco de dados vetorial (que junta o índice com o que um banco de verdade precisa: CRUD, filtro por metadado, escala, persistência). O pgvector, que a gente vai usar no exemplo, transforma o Postgres nessa segunda categoria.
Como a busca por similaridade funciona
Aqui é onde a mágica deixa de ser mágica e vira geometria.
Cada embedding é um ponto num espaço de muitas dimensões. Buscar por similaridade é medir distância entre pontos: quanto mais perto, mais parecido. Os três jeitos mais comuns de medir:
- Distância euclidiana (L2): a linha reta entre dois pontos. Menor = mais parecido.
- Similaridade de cosseno: o ângulo entre os vetores, ignorando o tamanho. É a métrica mais usada em texto.
- Produto interno (inner product): útil quando os vetores já vêm normalizados.
O fluxo de uma consulta é sempre o mesmo: você transforma a pergunta em embedding (com o mesmo modelo que gerou os vetores da base), o banco compara esse vetor com os que estão indexados e devolve os k vizinhos mais próximos (o famoso top-k).
O detalhe que separa protótipo de produto é o como essa comparação é feita. A forma ingênua — comparar a pergunta com cada vetor da base, um por um — chama-se brute force (ou busca exata). Funciona lindamente com 10 mil vetores. Com 10 milhões, cada busca vira uma varredura completa e a latência explode.
Por isso os bancos vetoriais usam ANN — Approximate Nearest Neighbor (vizinho mais próximo aproximado). Em vez de garantir o resultado perfeito, eles constroem uma estrutura de índice que acha quase sempre os vizinhos certos, muito mais rápido. O trade-off é explícito e você precisa entendê-lo: quanto mais preciso o resultado, mais lenta a busca. A métrica que mede esse acerto chama-se recall — a fração dos vizinhos verdadeiros que o índice conseguiu recuperar. 95% de recall a 5ms costuma valer muito mais, em produto, do que 100% a 800ms.
HNSW vs IVFFlat: os dois índices que você vai encontrar
Praticamente todo banco vetorial ANN usa um destes dois algoritmos de índice — vale saber a diferença antes de escolher.
HNSW (Hierarchical Navigable Small World) monta um grafo de várias camadas. A busca "cai" da camada mais esparsa para a mais densa, pulando de vizinho em vizinho até chegar perto do alvo. Segundo a documentação do pgvector, o HNSW tem melhor performance de consulta (na relação velocidade × recall), mas constrói o índice mais devagar e consome mais memória.
IVFFlat (Inverted File) divide os vetores em listas (clusters) e, na busca, só olha as listas mais próximas da pergunta. Constrói mais rápido e gasta menos memória, mas a qualidade da consulta é inferior à do HNSW.
A regra prática: comece com HNSW, a menos que tenha um motivo forte pra não fazer isso (base gigante que reindexe o tempo todo, ou pouca RAM). É o default de mercado por ter o melhor equilíbrio entre recall e latência.
Exemplo prático: um banco vetorial com pgvector
Chega de teoria. Você não precisa assinar um serviço dedicado para ter tudo isso. O pgvector — hoje na versão 0.8.x, para Postgres 13+ — transforma o Postgres num banco vetorial completo. Se seu projeto já roda em Postgres (o do 80% dos backends que eu vejo), você já tem 80% do caminho andado. Escrevi um guia dedicado sobre pgvector como memória do agente, mas aqui vai o mínimo pra você ver funcionando.
Primeiro, liga a extensão e cria uma tabela com uma coluna do tipo vector:
CREATE EXTENSION IF NOT EXISTS vector;
-- 3 dimensões só pra caber no exemplo.
-- Na vida real: 1536 (text-embedding-3-small), 1024, etc.
CREATE TABLE documentos (
id bigserial PRIMARY KEY,
conteudo text,
embedding vector(3)
);
Depois, insere os embeddings (na prática você gera esses vetores com um modelo de embedding e grava o array):
INSERT INTO documentos (conteudo, embedding) VALUES
('pgvector no Postgres', '[0.9, 0.1, 0.2]'),
('índice HNSW e recall', '[0.8, 0.2, 0.1]'),
('receita de pão de fermentação','[0.1, 0.9, 0.7]');
Agora a parte que importa: a busca por similaridade. O pgvector expõe a distância como operadores:
<->— distância euclidiana (L2)<=>— distância de cosseno<#>— produto interno negativo
Buscar os 2 documentos mais parecidos com um vetor de consulta é um ORDER BY pela distância:
SELECT conteudo, embedding <=> '[0.85, 0.15, 0.15]' AS distancia
FROM documentos
ORDER BY embedding <=> '[0.85, 0.15, 0.15]'
LIMIT 2;
Repare: não tem WHERE embedding = .... Tem ORDER BY <distância> + LIMIT k. Isso é a busca por similaridade em SQL puro — os dois vetores próximos do "assunto pgvector" voltam primeiro; o pão de fermentação fica pra trás.
Do jeito acima, o Postgres faz brute force (busca exata) — ótimo pra base pequena. Quando ela cresce, você cria o índice HNSW e a mesma query passa a rodar como ANN:
CREATE INDEX ON documentos
USING hnsw (embedding vector_cosine_ops);
Um detalhe de produção: o operador do índice tem que casar com o da query. Índice com vector_cosine_ops serve buscas com <=>; se você busca com <->, precisa de um índice vector_l2_ops. Errar isso faz o Postgres ignorar o índice e cair no brute force sem avisar.
Não só acompanhe as novidades — domine. Engenharia de IA na prática, ao vivo, toda semana, na maior comunidade do Brasil.
Entrar no Clãpgvector vs. Qdrant vs. Pinecone vs. Chroma: o critério de escolha
A pergunta que todo mundo faz é "qual é o melhor?". A pergunta certa é outra: a partir de que ponto o banco que eu já tenho deixa de aguentar? Porque os quatro resolvem o mesmo problema matemático — HNSW é HNSW em qualquer lugar, e nenhum deles inventou uma distância de cosseno melhor que a do outro. O que muda é operação, custo e o que acontece quando a base cresce.
| Opção | O que é | Custo | Operação | Latência |
|---|---|---|---|---|
| pgvector | extensão do Postgres que você já roda | zero de licença; você paga RAM e disco da instância que já existe | nenhuma peça nova: mesmo backup, mesma migration, mesma transação | ótima enquanto o índice cabe na memória; degrada quando não cabe |
| Qdrant | banco vetorial dedicado, open source (Rust), self-host ou cloud | grátis self-hosted (você paga a VM); cloud cobra por nó/recurso | mais um serviço pra subir, monitorar e fazer backup | forte em filtro + vetor junto e em quantização pra caber mais na RAM |
| Pinecone | serviço gerenciado, você não toca em índice | cobra por armazenamento e por operações de leitura/escrita; o modelo de preço muda com frequência, então cheque antes de orçar | quase nenhuma: é o ponto forte dele | previsível e gerenciada, ao custo de mais uma dependência externa e do dado saindo do seu banco |
| Chroma | store embutido, orientado a Python/prototipagem | grátis, roda local | trivial no notebook; vira problema quando precisa de multi-tenant, HA e backup sério | boa em base pequena; não é o alvo dele aguentar produção pesada |
O ponto que ninguém fala em voz alta: a diferença de qualidade de resultado entre eles é pequena; a diferença de custo operacional é enorme. Trocar Postgres por um banco dedicado não melhora seu RAG. Quem melhora RAG é chunking, escolha de modelo de embedding e reranking. O banco dedicado resolve escala e operação, não relevância.
Até quantos vetores o pgvector aguenta
Não existe número mágico, e quem crava um está chutando. Mas existe uma conta que você pode fazer hoje, no guardanapo, e que decide quase tudo: o índice HNSW precisa caber na RAM. Se ele cabe, a latência é de milissegundos. Se ele não cabe, o Postgres começa a ir no disco e a busca vira dezenas ou centenas de milissegundos, de forma irregular.
A conta: um vetor float32 de 1536 dimensões ocupa 1536 × 4 bytes ≈ 6 KB. Então 100 mil vetores ≈ 600 MB só de dados brutos; 1 milhão ≈ 6 GB — e o grafo HNSW ainda soma o seu overhead de links por cima disso. Numa instância comum de 16 GB de RAM, que também serve o seu tráfego OLTP normal, isso coloca a faixa confortável na ordem de centenas de milhares a poucos milhões de vetores de 1536 dimensões. Não é um limite duro: é a fronteira onde você para de ignorar o assunto.
E dá pra empurrar essa fronteira bastante antes de trocar de banco. Três alavancas, em ordem de custo-benefício: reduzir dimensões (modelos como o text-embedding-3-small aceitam pedir menos dimensões; sair de 1536 para 512 corta o consumo em ~3×, com perda de recall geralmente pequena — meça no seu corpus); usar halfvec, o tipo de meia precisão do pgvector, que corta a memória pela metade; e particionar a tabela por tenant ou por período, para que cada busca só toque o pedaço relevante. Quantos vetores você vai ter, aliás, é função direta do seu chunking — se ainda não fechou essa parte, RAG do zero: chunking, embeddings e busca tem o cálculo.
Como medir em vez de opinar
O gatilho pra migrar não é a contagem de vetores. São quatro sinais, e todos são mensuráveis:
- O índice não cabe mais na RAM disponível depois das alavancas acima.
- A latência p95 no recall que você precisa estourou o seu SLA.
- A reindexação compete com o tráfego transacional do banco (o
CREATE INDEXdo HNSW é caro; versões recentes do pgvector fazem build paralelo, o que ajuda, mas não é de graça). - Você precisa de filtro pesado por metadado junto com o vetor. Esse é o calcanhar clássico do ANN filtrado: o índice devolve k candidatos, o
WHEREderruba quase todos e você recebe menos resultados do que pediu. O pgvector 0.8 atacou isso com iterative index scans; se mesmo assim seu caso sofre, é um argumento legítimo pra um banco com filtro nativo no índice.
Medir o item 2 é simples e quase ninguém faz. Você monta uma verdade de referência com busca exata, roda a mesma query com o índice e compara os conjuntos:
-- 1) verdade de referência: força a busca exata, sem índice
SET LOCAL enable_indexscan = off;
SELECT id FROM documentos ORDER BY embedding <=> :q LIMIT 10;
-- 2) mesma consulta com HNSW, variando o esforço de busca
RESET enable_indexscan;
SET LOCAL hnsw.ef_search = 40; -- depois 100, 200, 400
SELECT id FROM documentos ORDER BY embedding <=> :q LIMIT 10;
recall@10 é o tamanho da interseção dividido por 10. Faça isso com 100 a 200 consultas reais do seu produto (não consultas inventadas), tire a média, e plote recall contra latência p95 medida com carga concorrente de verdade. Você vai descobrir o seu ef_search mínimo em vinte minutos, e vai ter um número pra defender na reunião em vez de uma preferência. Esse tipo de decisão de arquitetura — medir antes de trocar de infra — é o que a gente treina no AI Engineering Lab, nos dias 19 e 20/09.
E se você não precisar de nenhum deles
A maioria dos projetos que me chega com "preciso escolher um banco vetorial" não precisa escolher nada. Precisa de uma coluna vector na tabela que já existe.
Se a sua base tem alguns milhares ou algumas dezenas de milhares de chunks — o tamanho de uma base de conhecimento interna, da documentação de um produto, do FAQ de um SaaS — o Postgres resolve sem índice nenhum. Um ORDER BY embedding <=> :q LIMIT 5 sobre 20 mil vetores é um seq scan que roda na casa de dezenas de milissegundos no hardware que você já paga. Meça no seu: é uma query, dois minutos de trabalho. Enquanto esse número couber no seu orçamento de latência, índice ANN é complexidade sem retorno — e, de quebra, você tem recall de 100% por construção, porque a busca é exata.
Tem um bônus que quase compensa sozinho: o vetor mora ao lado do dado relacional. Você faz JOIN com a tabela de permissões, filtra por tenant_id na mesma query, envolve o insert do documento e o insert do embedding na mesma transação, e o backup do banco já leva os embeddings junto. Num banco dedicado, cada uma dessas coisas vira sincronização entre dois sistemas — e sincronização entre dois sistemas é onde nascem os bugs de "o documento foi deletado mas continua aparecendo na busca".
O caminho sensato é esse: comece com a coluna vector e busca exata; adicione HNSW quando a latência doer; aplique as alavancas de memória quando o índice apertar; e só então avalie um banco dedicado, com número na mão. Se você quer o passo a passo dessa montagem num backend real — ingestion assíncrono, busca híbrida e as métricas de qualidade —, tem o caminho inteiro no guia de RAG para devs backend com pgvector em Laravel.
Quando você precisa de um banco vetorial (e quando não)
Nem todo projeto precisa. Você precisa de busca vetorial quando a pergunta certa é "o que é parecido com isso?":
- RAG — recuperar os trechos certos de uma base pra alimentar o LLM. É o caso mais comum. Se RAG ainda é abstrato pra você, comece por o que é RAG.
- Busca semântica — achar por significado, não por palavra-chave exata.
- Recomendação — "produtos parecidos com o que o usuário viu".
- Deduplicação e memória de agente — reconhecer que dois conteúdos dizem a mesma coisa com palavras diferentes.
E quando não: se sua busca é por igualdade exata (SKU, CPF, e-mail, status), banco vetorial é overengineering. WHERE sku = 'RX-7000' é mais rápido, mais barato e não erra. Aliás, embedding puro confunde "RX-7000" com "RX-5000" — pra catálogo que mistura código e semântica, o certo é busca híbrida: BM25 + vetor + RRF, não vetor sozinho. Escolher a ferramenta certa pro tipo de pergunta é metade da engenharia.
FAQ rápido
Preciso de um banco vetorial dedicado (Pinecone, Qdrant) ou pgvector resolve? Pra maioria dos projetos de backend, pgvector resolve — e evita mais uma peça de infra pra manter. Serviços dedicados brilham em escala muito alta (centenas de milhões de vetores) ou quando você quer busca gerenciada sem tocar em índice. Comece no Postgres; migre quando a dor aparecer, não antes.
Qual métrica de distância eu uso?
Para texto, cosseno (<=>) é o default seguro na esmagadora maioria dos casos. Só use L2 ou produto interno se o modelo de embedding recomendar explicitamente.
Quantas dimensões meu vetor vai ter?
Depende do modelo. text-embedding-3-small da OpenAI gera 1536; outros modelos vão de 384 a 4096. O importante é que a coluna vector(n) bata com a saída do modelo, e que você use o mesmo modelo pra indexar e pra consultar.
HNSW ou IVFFlat? HNSW por padrão — melhor recall e latência. IVFFlat só se o tempo de construção do índice ou o consumo de memória forem gargalo real no seu caso.
Fechando
Banco de dados vetorial não é mágica nem buzzword: é um banco que troca a pergunta "o que é igual?" por "o que é parecido?", medindo distância entre embeddings e usando busca aproximada pra fazer isso em milissegundos. Entender essa mecânica — distância, ANN, recall, HNSW — é o que te deixa escolher (e debugar) a ferramenta com critério, em vez de por moda.
O próximo passo é sair do exemplo de 3 dimensões e montar um pipeline de verdade: chunking, embedding e busca com índice. Tem o caminho completo em RAG do zero: chunking, embeddings e busca. E se você quer ver esse tipo de arquitetura sendo construída ao vivo, com código rodando e decisão de produção na mesa, é o que a gente faz toda semana no Clã Beer and Code — a maior comunidade de engenharia de IA do Brasil, onde o pessoal constrói agente que aguenta produção, não slide.
No fim, o banco vetorial é a fundação silenciosa embaixo de todo sistema de IA que entende o que você pediu. Vale conhecer a fundação antes de subir a casa.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
Conteúdo é o que não falta. Falta quem desembaralhe: o que importa agora é como implementar do jeito certo. No Clã você tem isso ao vivo, toda semana, com quem já filtrou o ruído.
Entrar no Clã