Implementar pesquisa vetorial e RAG com PostgreSQL e pgvector
Armazene embeddings no Banco de Dados do Azure para PostgreSQL, escolha métricas de distância e índices ANN, mantenha vetores em evolução, crie recuperação semântica e híbrida e fundamente respostas RAG com citações rastreáveis.
Tempo de estudo sugerido: 130 minutos • Nível intermediário • Reescrita autoral completa com versão resumida de cada tópico, avaliação comentada e laboratório guiado de pesquisa vetorial
Por João Ricardo Dutra••Conteúdo autoral completo
1. Transformar um PostgreSQL existente em recuperador de IA
Imagine um sistema jurídico que já mantém de documentos e clientes no . Advogados precisam localizar casos, cláusulas e precedentes pelo significado, mesmo quando a pergunta e a fonte usam palavras diferentes. Manter embeddings junto aos dados relacionais elimina outro banco vetorial e a sincronização entre sistemas, sem impedir ingestões diárias e consultas em escala com baixa latência.
Armazenar e consultar embeddings com pgvector.
Escolher a métrica e executar pesquisa por similaridade.
Selecionar e ajustar índices de vizinhos mais próximos aproximados.
Atualizar vetores e migrar modelos de embedding com segurança.
Criar recuperação semântica, híbrida e RAG com citações e qualidade mensurável.
Resumo do tópico
O pgvector leva recuperação semântica aos dados PostgreSQL existentes e reduz componentes e sincronizações adicionais.
2. Habilitar pgvector e projetar esquemas vetoriais
No servidor flexível do , inclua primeiro a extensão na lista de permissões do servidor, confirme com SHOW .extensions e crie-a em cada banco que a utilizará. A comunidade diz pgvector, mas o binário e o nome SQL são vector. Normalmente a operação exige o administrador do servidor ou participação em azure_pg_admin.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE knowledge_chunks (
id BIGSERIAL PRIMARY KEY,
source_id BIGINT NOT NULL,
chunk_no INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT,
token_count INTEGER,
embedding vector(1536),
embedding_stale BOOLEAN NOT NULL DEFAULT false,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (source_id, chunk_no)
);
A dimensão de vector(n) deve coincidir exatamente com a saída do modelo. Vetores de 384, 1.536 e 3.072 dimensões não podem compartilhar uma coluna restrita sem transformação. Guarde com o vetor os usados para filtrar e exibir; separe textos muito grandes somente quando a redução de tráfego compensar o JOIN.
Colunas distintas atendem embeddings de título e corpo ou modelos diferentes. INSERT, INSERT de várias linhas e COPY carregam vetores; ao mudar o conteúdo, o embedding correspondente precisa ser regenerado.
Dimensão do esquema, saída do modelo e operador de consulta devem representar o mesmo espaço vetorial.
Resumo do tópico
Autorize e crie vector por banco e associe cada coluna à dimensão e à finalidade de um único espaço de embeddings.
3. Escolher vector, halfvec ou sparsevec
Tipos de armazenamento do pgvector.
Tipo
Representação
Quando usar
vector
Pontos flutuantes de 32 bits; cerca de 6 KB em 1.536 dimensões.
Padrão para embeddings densos e bom equilíbrio entre precisão e espaço.
halfvec
Pontos flutuantes de 16 bits; aproximadamente metade do armazenamento.
Depois de testes confirmarem que a precisão menor preserva a relevância.
sparsevec
Somente valores diferentes de zero e suas posições.
Saídas de alta dimensão realmente esparsas.
Comece com vector para embeddings densos. Valide halfvec com métricas de qualidade antes da economia de espaço. O HNSW sobre sparsevec aceita no máximo 1.000 elementos não nulos; acima disso, reduza dimensões ou selecione outra estratégia.
Resumo do tópico
Use vector como padrão, halfvec após validação de qualidade e sparsevec somente para representações genuinamente esparsas.
4. Alinhar operadores de distância ao modelo
-- Euclidean/L2: smaller means nearer
SELECT id, title, embedding <-> $1::vector AS distance
FROM knowledge_chunks ORDER BY embedding <-> $1::vector LIMIT 8;
-- Cosine distance: common for text embeddings
SELECT id, title, embedding <=> $1::vector AS distance
FROM knowledge_chunks ORDER BY embedding <=> $1::vector LIMIT 8;
-- Negative inner product: smaller means a larger dot product
SELECT id, title, embedding <#> $1::vector AS distance
FROM knowledge_chunks ORDER BY embedding <#> $1::vector LIMIT 8;
Semântica das distâncias.
Operador
Métrica
Interpretação
<->
L2 / euclidiana
Distância em linha reta; a magnitude pode importar.
<=>
Distância de cosseno
Ângulo entre vetores; comum em pesquisa semântica de texto.
<#>
Produto interno negativo
Produto escalar negado para manter os menores valores primeiro.
Nos três operadores, menor significa mais próximo. Siga a recomendação do modelo. Vetores normalizados podem aproveitar produto interno com eficiência, enquanto cosseno é um padrão frequente para texto. Envie o vetor como parâmetro, sem concatená-lo ao SQL.
Resumo do tópico
A métrica precisa refletir a geometria do modelo, e todas as distâncias do pgvector são ordenadas do menor para o mais semelhante.
5. Entender pesquisa exata, ANN, recall e DiskANN
Sem índice, o PostgreSQL compara o vetor de consulta com todas as linhas e encontra os vizinhos exatos. O recall é perfeito, mas o custo cresce linearmente; pode ser aceitável abaixo de aproximadamente dez mil linhas e lento em milhões.
A pesquisa ANN examina um subconjunto estruturado. Recall mede a parcela dos vizinhos exatos presente no resultado aproximado; muitos sistemas aceitam 95–99% para obter milissegundos. O também oferece DiskANN por pg_diskann, com alto recall e throughput, escala orientada a disco, compilações rápidas, quantização de produto e, em versões recentes, dimensões maiores que HNSW/IVFFlat.
Resumo do tópico
Pesquisa exata maximiza recall; ANN troca uma pequena parcela dele por grande redução de latência, e DiskANN acrescenta uma opção para grande escala.
6. Criar e ajustar IVFFlat
Na criação, IVFFlat usa k-means e separa vetores em lists ao redor de centroides. A consulta encontra os centroides próximos e pesquisa o número de listas indicado por probes; o trabalho aproximado é (linhas / lists) × probes.
Carregue dados representativos antes do índice; uma tabela vazia não treina úteis.
Até cerca de um milhão de linhas, comece com linhas / 1.000 para lists; acima disso, use aproximadamente a raiz quadrada.
Comece probes próximo de sqrt(lists); elevar melhora recall e aumenta latência.
Reconstrua se uma carga grande ou de outro domínio tornar os antigos pouco representativos.
Resumo do tópico
IVFFlat economiza memória e compila rapidamente, mas depende de dados de treinamento e do equilíbrio entre lists e probes.
7. Criar e ajustar HNSW
HNSW constrói um grafo de proximidade em camadas. A busca parte de uma camada superior esparsa, segue conexões promissoras e desce até candidatos próximos. Diferente do IVFFlat, pode nascer em uma tabela vazia e evoluir com inserções.
CREATE INDEX chunks_embedding_hnsw_idx
ON knowledge_chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 96);
SET LOCAL hnsw.ef_search = 100;
-- Alternative after representative data exists
CREATE INDEX chunks_embedding_ivfflat_idx
ON knowledge_chunks USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
SET LOCAL ivfflat.probes = 10;
m limita conexões por nó: valores maiores podem elevar recall, memória e tempo de compilação. ef_construction amplia candidatos durante a criação e melhora o grafo às custas de tempo. ef_search amplia a exploração na consulta; aumente-o somente quando o recall medido exigir.
Resumo do tópico
HNSW costuma entregar melhor relação velocidade/recall, pagando com mais memória, criação mais lenta e inserções moderadamente custosas.
8. Escolher o índice e provar seu uso
Seleção de índice ANN.
Fator
IVFFlat
HNSW
Velocidade/recall
Bom
Geralmente melhor
Compilação
Mais rápida
Mais lenta
Memória
Menor
Maior
Tabela vazia
Não é útil
Compatível
Inserções
Rápidas
Moderadas
Prefira HNSW quando baixa latência e alto recall dominarem e houver memória. Prefira IVFFlat quando compilação, memória ou recargas em lote forem prioritárias. A classe deve casar com o operador: vector_l2_ops com <->, vector_cosine_ops com <=> e vector_ip_ops com <#>. Uma divergência impede o índice de atender à ordenação.
EXPLAIN (ANALYZE, VERBOSE, BUFFERS)
SELECT id, title
FROM knowledge_chunks
ORDER BY embedding <=> $1::vector
LIMIT 10;
Em tabela grande com ORDER BY de distância e LIMIT, procure Index Scan. Seq Scan pode ser correto em tabela pequena. Confirme ainda validade do índice, estatísticas atualizadas e presença de LIMIT.
A escolha de índice é uma decisão medida e revisável.
Resumo do tópico
Escolha com dados da carga, alinhe classe e operador e confira o plano real em vez de presumir uso do índice.
9. Monitorar, reconstruir e recuperar espaço
pg_stat_user_indexes mostra varreduras e tamanho; zero usos pode revelar operador incompatível ou decisão do planejador. Acompanhe EXPLAIN ANALYZE e recall de consultas representativas. pg_stat_progress_create_index exibe fases e progresso da criação.
Latência crescente sem volume equivalente, qualidade menor ou aproximadamente 20–30% de dados novos de outro domínio sinalizam reconstrução. Sem indisponibilidade, crie substituto com CREATE INDEX CONCURRENTLY, valide, remova o antigo e renomeie. REINDEX é mais simples se uma interrupção de gravação for aceitável.
Estime vector como dimensões × 4 bytes × linhas, mais cerca de 1,5–2 vezes esse volume para HNSW ou 1–1,5 vez para IVFFlat. MVCC deixa tuplas mortas após atualizações: VACUUM reaproveita espaço; VACUUM FULL devolve mais ao disco, mas bloqueia. Tabelas muito atualizadas podem precisar de limites menores de autovacuum.
O embedding fica obsoleto quando o significado da fonte muda. Em alterações raras, conteúdo e novo vetor podem ser confirmados juntos, mas isso acopla a gravação à de embeddings. Em alta frequência, marque linhas obsoletas, processe lotes assíncronos, gere vetores em grupo e limpe o sinalizador apenas após gravar.
-- Content writes stay fast and mark the vector as stale
UPDATE knowledge_chunks
SET content = $1, embedding_stale = true, updated_at = now()
WHERE id = $2;
-- A worker claims a bounded batch without colliding with peers
SELECT id, content
FROM knowledge_chunks
WHERE embedding_stale
ORDER BY updated_at
FOR UPDATE SKIP LOCKED
LIMIT 250;
-- The worker writes the regenerated vector
UPDATE knowledge_chunks
SET embedding = $1::vector, embedding_stale = false
WHERE id = $2;
Uma atualização integral agendada corrige falhas e normaliza mudanças de configuração. Priorize linhas antigas, respeite limites da , use transações controladas—em grandes backfills, muitas vezes 1.000–5.000 linhas—e monitore replicação, bloqueios, WAL e crescimento de índices.
Resumo do tópico
Use atualização atômica para poucos eventos e sinalizadores com workers em lote para corpora dinâmicos.
11. Migrar modelos com colunas paralelas
Vetores de modelos ou dimensões diferentes não pertencem ao mesmo espaço. Sobrescrever a coluna ativa mistura semânticas incompatíveis. Adicione uma segunda coluna, crie o índice correspondente, preencha em lotes, compare versões em avaliação rotulada, altere as leituras de uma vez e mantenha o caminho antigo até não precisar de .
ALTER TABLE knowledge_chunks ADD COLUMN embedding_v2 vector(3072);
CREATE INDEX CONCURRENTLY chunks_embedding_v2_idx
ON knowledge_chunks USING hnsw (embedding_v2 vector_cosine_ops);
-- Backfill in bounded batches, compare recall and latency, then switch reads.
-- Keep the original column until the new model passes acceptance tests.
Calcule o prazo por quantidade de linhas, throughput do lote, cotas da e tentativas. Durante a migração, duas colunas e dois índices exigem capacidade temporária adicional.
SELECT id, title, content, category,
embedding <=> $1::vector AS distance
FROM knowledge_chunks
WHERE category = $2
AND embedding <=> $1::vector < $3
ORDER BY embedding <=> $1::vector
LIMIT $4;
Índices B-tree em filtros frequentes, inclusive compostos, reduzem candidatos. Filtros muito seletivos podem levar o planejador aos índices relacionais; filtros amplos favorecem o índice vetorial. ANALYZE e EXPLAIN ANALYZE revelam a escolha.
LIMIT sempre devolve até N linhas, mesmo ruins. Um limiar de distância define qualidade mínima e pode retornar vazio corretamente. Derive-o de pares relevantes e irrelevantes rotulados. Traga título, conteúdo, citação e distância em uma ida ao banco, sem transferir o texto inteiro quando um resumo basta.
Resumo do tópico
limitam o escopo, limiares impõem relevância mínima e planos mostram como o PostgreSQL combina acessos.
13. Tratar consultas multivetoriais e híbridas
Faça a média de vetores de exemplos quando todos expressarem um único conceito. Mantenha vetores separados quando houver requisitos independentes e combine a evidência de candidatos recuperados para cada aspecto.
Semântica pode subestimar nomes exatos, códigos ou jargão. A pesquisa de texto completo do PostgreSQL fornece classificação lexical, acelerada por GIN sobre tsvector armazenado ou gerado. A recuperação híbrida une candidatos. A fusão recíproca de classificação (RRF) combina posições sem fingir que distância vetorial e relevância textual usam a mesma escala.
WITH semantic AS (
SELECT id, row_number() OVER (ORDER BY embedding <=> $1::vector) AS rank
FROM knowledge_chunks ORDER BY embedding <=> $1::vector LIMIT 40
), lexical AS (
SELECT id, row_number() OVER (
ORDER BY ts_rank_cd(to_tsvector('simple', content), websearch_to_tsquery('simple', $2)) DESC
) AS rank
FROM knowledge_chunks
WHERE to_tsvector('simple', content) @@ websearch_to_tsquery('simple', $2)
LIMIT 40
)
SELECT k.id, k.title,
coalesce(1.0 / (60 + s.rank), 0) + coalesce(1.0 / (60 + l.rank), 0) AS rrf_score
FROM knowledge_chunks k
LEFT JOIN semantic s ON s.id = k.id
LEFT JOIN lexical l ON l.id = k.id
WHERE s.id IS NOT NULL OR l.id IS NOT NULL
ORDER BY rrf_score DESC LIMIT 10;
Resumo do tópico
Use média para um conceito, recuperação separada para conceitos distintos e RRF quando significado e termos exatos forem importantes.
14. Projetar o modelo de documentos e para RAG
RAG transforma a pergunta em vetor, recupera evidências e orienta o modelo de linguagem com esse contexto. Documentos inteiros são amplos demais; separe da fonte de pesquisáveis.
CREATE TABLE source_documents (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
source_url TEXT,
document_type TEXT,
version INTEGER NOT NULL DEFAULT 1,
ingested_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE document_chunks (
id BIGSERIAL PRIMARY KEY,
document_id BIGINT NOT NULL REFERENCES source_documents(id) ON DELETE CASCADE,
chunk_no INTEGER NOT NULL,
section_title TEXT,
page_number INTEGER,
content TEXT NOT NULL,
token_count INTEGER NOT NULL,
embedding vector(1536),
UNIQUE (document_id, chunk_no)
);
CREATE INDEX chunks_source_order_idx ON document_chunks(document_id, chunk_no);
Estratégias de segmentação.
Estratégia
Força
Risco / uso
Tamanho fixo
previsíveis e simples.
Pode cortar ideias; útil sem estrutura clara.
Limites semânticos
Preserva parágrafos, seções, cláusulas ou respostas.
Tamanhos variáveis; ideal para material estruturado.
Sobreposição
Preserva conceitos na fronteira.
Aumenta espaço e evidências duplicadas.
Guarde seção, página, offsets e para reconstruir contexto e citações. Envie embeddings e inserts em lote. Uma substituição pode excluir antigos por cascata; quando histórico importa, mantenha versões e filtre a ativa.
Resumo do tópico
Separe fontes de , preserve rastreabilidade e defina limites conforme a estrutura e as perguntas do corpus.
15. Montar janela de contexto, orçamento e citações
Top-k basta para autônomos. Conteúdo narrativo pode exigir vizinhos do mesmo documento. Expanda apenas ali, elimine duplicatas e some para reservar espaço às instruções, pergunta e resposta.
WITH seeds AS (
SELECT id, document_id, chunk_no, embedding <=> $1::vector AS distance
FROM document_chunks
ORDER BY embedding <=> $1::vector
LIMIT 4
), expanded AS (
SELECT DISTINCT c.*, s.distance
FROM seeds s
JOIN document_chunks c ON c.document_id = s.document_id
AND c.chunk_no BETWEEN s.chunk_no - 1 AND s.chunk_no + 1
), budgeted AS (
SELECT e.*, d.title, d.source_url,
sum(e.token_count) OVER (ORDER BY e.distance, e.chunk_no) AS tokens_used
FROM expanded e JOIN source_documents d ON d.id = e.document_id
)
SELECT content, title, source_url, section_title, page_number, distance
FROM budgeted WHERE tokens_used <= 3200
ORDER BY distance, chunk_no;
Retorne título, ou ID estável, seção, página e distância. Agrupe vários da mesma fonte em uma citação com vários trechos. Em áreas de alto risco, cada afirmação deve ser verificável e correspondências fracas precisam de revisão humana.
Uma resposta RAG confiável começa por recuperação rastreável, limitada e relevante.
Resumo do tópico
Expanda contexto apenas quando necessário, imponha orçamento de e devolva verificáveis.
16. Avaliar recuperação separadamente da geração
Métricas centrais.
Métrica
Pergunta
Ajuste típico
Precisão
Quantos retornados são relevantes?
Limiar mais rígido, menos , segmentação focada.
Recall
Quantos relevantes foram encontrados?
Mais candidatos, limiar flexível, ef_search ou probes maior.
MRR
Quão alto aparece o primeiro relevante?
Embeddings, pré-processamento, reclassificação ou fusão melhores.
Comece com 20–50 perguntas representativas, recupere 10–20 candidatos e peça a especialistas que rotulem relevância. Automatize precision@k, recall@k e MRR. menores podem elevar precisão e fragmentar fatos; sobreposição preserva continuidade e duplica dados; busca ANN mais ampla eleva recall e latência.
Resumo do tópico
Perguntas rotuladas e métricas tornam o ajuste de modelo, , limiar e índice baseado em evidências.
17. Laboratório guiado: similaridade de produtos com Flask
O exercício de origem reserva cerca de 30 minutos para criar um aplicativo web de produtos, padrão reutilizável em recomendações, pesquisa semântica e RAG.
Prepare assinatura com permissão, , atual, Python 3.12 ou posterior e psql.
Baixe o projeto inicial e configure o script de implantação.
Implante servidor flexível do com .
Complete o aplicativo Flask enquanto o servidor é criado.
Autorize e habilite vector e crie a tabela de produtos com embedding.
Carregue exemplos, pesquise similaridade e examine ordem e distâncias.
Inclua produtos e observe a mudança dos vizinhos.
Exclua os recursos temporários ao concluir.
Resumo do tópico
O laboratório une provisionamento seguro, esquema pgvector, Flask e resultados reais de similaridade.
18. Revisão da avaliação e checklist final
Para similaridade semântica com embeddings unitários, a resposta esperada é distância de cosseno (<=>); a documentação do modelo pode justificar produto interno em implementações otimizadas.
Para cinco milhões de embeddings, cargas ocasionais e sem inserções em tempo real, a avaliação escolhe IVFFlat com lists adequadas.
No HNSW, m controla o máximo de conexões por nó; ef_construction controla a exploração na criação.
Para 50 mil vetores regenerados, transações de aproximadamente 1.000–5.000 linhas perturbam menos que uma única transação gigante.
RRF equilibra classificações híbridas sem multiplicar escalas incompatíveis.
Alinhe dimensão, modelo, operador e classe.
Meça recall e latência antes de escolher exata, HNSW, IVFFlat ou DiskANN.
Monitore planos, usos, criação, bloat e mudanças de distribuição.
Atualize de forma assíncrona em alta frequência e migre modelos com colunas paralelas.
Filtre e aplique limiar; adicione sinal lexical quando termos exatos importarem.
Projete RAG para íntegros, limite de , citações e qualidade mensurável.
Recuperação vetorial de produção combina espaço de embeddings coerente, índices verificados, atualização sustentável, filtros relacionais e evidências RAG rastreáveis.