pgvector ou banco vetorial dedicado: escolhendo pelo seu volume
A resposta honesta é que na maioria dos projetos o Postgres que você já tem resolve. Onde está a fronteira, o que muda ao cruzá-la, e como não descobrir isso tarde demais.
Equipe EasyOps Cloud · · 7 min de leitura
Todo projeto de busca semântica passa pela mesma decisão logo no começo: usar a
extensão pgvector no Postgres que já existe, ou subir um banco vetorial
dedicado.
A resposta que a maioria dos artigos dá é "depende". A resposta mais útil é que, para a esmagadora maioria dos projetos brasileiros que vejo, o Postgres resolve — e adicionar mais um banco à infraestrutura antes de precisar cobra um custo operacional que raramente é contabilizado.
O que vale é saber onde está a fronteira, para reconhecer quando ela for cruzada.
O que muda entre um e outro
Bancos dedicados são construídos em torno de uma única operação: encontrar os vizinhos mais próximos em um espaço de muitas dimensões. Eles otimizam layout de memória, compressão de vetor e paralelismo para isso.
O pgvector acrescenta essa capacidade a um banco relacional que já faz muitas
outras coisas. O ganho é enorme em outra dimensão: o vetor fica na mesma linha
que o resto do dado.
Essa diferença é mais importante do que parece. Com pgvector, uma busca
semântica filtrada por cliente, por data e por status é uma consulta SQL comum:
SELECT id, titulo, conteudo,
embedding <=> $1 AS distancia
FROM documentos
WHERE cliente_id = $2
AND publicado_em > now() - interval '1 year'
AND status = 'ativo'
ORDER BY embedding <=> $1
LIMIT 10;Uma transação, um lugar, consistência garantida. Com banco separado, o mesmo resultado exige manter os metadados sincronizados nos dois sistemas — e sincronia entre bancos é a fonte de bug mais chata que existe: o documento apagado no Postgres que continua aparecendo na busca.
Os números que definem a fronteira
Como referência aproximada, em hardware modesto e sem GPU:
| Volume de vetores | Recomendação |
|---|---|
| até ~1 milhão | pgvector, com folga |
| 1 a 10 milhões | pgvector com índice bem ajustado, ou dedicado |
| acima de 10 milhões | Banco dedicado tende a compensar |
O que empurra para o dedicado não é só a contagem. Três sinais valem mais:
- O índice não cabe na memória. É o critério decisivo, e dá para calcular.
- Busca concorrente alta. Dezenas de consultas por segundo simultâneas com outras cargas no mesmo Postgres começam a competir por recurso.
- Necessidade de recursos específicos — busca híbrida nativa, múltiplos vetores por registro, quantização agressiva.
Vale dizer o óbvio que costuma ser esquecido: a maioria das bases de conhecimento corporativas tem dezenas de milhares de trechos, não milhões. Uma documentação inteira de produto, com todos os manuais, raramente passa de 100 mil vetores.
A conta de memória, que é a que importa
O tamanho do índice é previsível e vale calcular antes de escolher.
Um vetor de 1536 dimensões em ponto flutuante de 4 bytes ocupa cerca de 6 KB. Um milhão deles são aproximadamente 6 GB só de vetores, antes do índice.
O índice HNSW, que é o padrão para busca aproximada, acrescenta as conexões entre vizinhos — de 30% a 50% a mais. Chegamos a algo em torno de 8 a 9 GB para um milhão de vetores.
A regra prática: o índice precisa caber na RAM. Se ele não couber, cada busca vira leitura de disco e o tempo de resposta sai de milissegundos para segundos.
Isso dá uma alavanca óbvia de custo: reduzir a dimensão do embedding. Modelos que produzem 384 ou 768 dimensões cortam o consumo pela metade ou por um quarto, com perda de qualidade que em muitas tarefas é imperceptível. Vale testar antes de dimensionar máquina maior — é a otimização de melhor retorno nesse tema.
CREATE INDEX ON documentos
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);E ajuste a memória de trabalho do Postgres, ou a criação do índice fica insuportavelmente lenta:
SET maintenance_work_mem = '2GB';O erro que faz o pgvector parecer ruim
Existe uma armadilha específica que leva muita gente a concluir, erradamente,
que pgvector não escala.
Quando você combina filtro restritivo com busca vetorial, o planejador pode decidir usar o índice do filtro e varrer os vetores um a um. Ou usar o índice vetorial e descartar quase tudo pelo filtro depois. Nos dois casos, a consulta que respondia em 20 ms passa a levar segundos.
EXPLAIN ANALYZE
SELECT id FROM documentos
WHERE cliente_id = 42
ORDER BY embedding <=> $1 LIMIT 10;Se aparecer Seq Scan no plano, é isso. As saídas:
- Aumentar o `ef_search` para que a busca aproximada recupere mais candidatos antes do filtro:
SET hnsw.ef_search = 100;- Índice parcial por cliente, quando o número de clientes é pequeno e estável.
- Particionar a tabela pelo campo mais usado no filtro.
Esse ajuste resolve a esmagadora maioria dos casos em que alguém concluiu que precisava trocar de banco.
Quando a busca vetorial não é a resposta
Uma constatação que economiza muito trabalho: parte relevante das buscas que motivaram o projeto seria melhor atendida por busca textual comum.
Busca vetorial encontra o que é semanticamente parecido. Ela é excelente para "documento sobre cancelamento de contrato" e ruim para "nota fiscal 84213", código de produto, nome próprio raro ou número de protocolo — casos em que o usuário quer correspondência exata e a similaridade semântica devolve dez documentos parecidos e nenhum certo.
O Postgres faz as duas coisas, o que é mais um argumento a favor de manter tudo no mesmo lugar:
-- Busca textual em português, com índice próprio
CREATE INDEX ON documentos
USING gin (to_tsvector('portuguese', conteudo));
SELECT id, titulo
FROM documentos
WHERE to_tsvector('portuguese', conteudo)
@@ plainto_tsquery('portuguese', $1)
LIMIT 10;A abordagem híbrida executa as duas buscas e combina os resultados, tipicamente intercalando por posição em cada lista. Ela costuma superar qualquer uma das duas isoladamente, e é o padrão em sistemas maduros de recuperação.
Vale começar por aí antes de investir em afinar embeddings: uma busca híbrida
simples com pgvector e tsvector entrega mais qualidade percebida que trocar de
modelo de embedding, e não adiciona nenhuma peça nova à infraestrutura.
O custo operacional de mais um banco
Se a decisão for pelo dedicado, conte o que ela traz junto: mais um serviço para manter atualizado, mais um backup para configurar e testar, mais um ponto de falha, mais uma credencial para gerenciar, e a sincronia entre os dois sistemas.
Nada disso é impeditivo. É trabalho, e trabalho recorrente. Para equipe pequena, esse custo frequentemente supera o ganho de desempenho que motivou a mudança.
Se for esse o caminho, vale isolar o serviço em máquina própria — a memória dedicada ao índice não deve disputar com o banco relacional — e incluir o novo volume na rotina de teste de restauração. Índice vetorial é reconstruível a partir dos textos originais, o que é uma vantagem: em caso de perda, o que precisa mesmo estar no backup são os documentos e os embeddings, não o índice.
Como decidir sem se arrepender
- Comece com `pgvector`, mesmo que o plano de longo prazo seja outro. O custo de começar é quase zero e você aprende com dado real.
- Meça o tamanho do índice e compare com a RAM disponível. Esse é o gatilho objetivo para reavaliar.
- Guarde os embeddings, não só os textos. Recalcular um milhão de vetores custa tempo e dinheiro; migrar de banco com os vetores em mãos é uma cópia.
- Isole a camada de busca no seu código atrás de uma interface. Se a troca vier, ela deve tocar um arquivo, não a aplicação inteira.
O resto da infraestrutura — pipeline de ingestão, estratégia de fragmentação, reindexação — pesa muito mais no resultado final que a escolha do banco, e é o que o artigo sobre infraestrutura para RAG cobre em detalhe. Trocar de banco vetorial não conserta um pipeline que fragmenta mal o texto.
Vale encerrar com a constatação que motivou este texto. A escolha do banco vetorial é, quase sempre, a decisão menos importante do projeto — e é a que consome mais tempo de discussão, porque é a única que se resolve comparando tabelas de características. As decisões que determinam se a busca vai funcionar são outras: como o texto é dividido, o que entra nos metadados, como o resultado é reordenado antes de chegar ao modelo, e o que acontece quando nada relevante é encontrado.
Se o tempo de análise for limitado, gaste-o ali. O banco pode ser trocado depois com relativa facilidade; um pipeline mal desenhado exige reindexar tudo e, pior, costuma só dar sinal de que está errado quando o usuário reclama do resultado.