Como usar o pgvector no PostgreSQL para busca vetorial com embeddings
1. Fundamentos da busca vetorial e embeddings
Embeddings vetoriais são representações numéricas densas de dados não estruturados — textos, imagens, áudios — em um espaço multidimensional contínuo. Diferentemente da busca textual tradicional baseada em correspondência exata de palavras (LIKE, full-text search), a busca vetorial captura relações semânticas: documentos com significados similares ficam próximos nesse espaço, mesmo usando vocabulários distintos.
A extensão pgvector transforma o PostgreSQL em um banco de dados vetorial, permitindo armazenar embeddings lado a lado com dados relacionais e executar consultas híbridas. Casos de uso incluem:
- Sistemas de recomendação que combinam perfil do usuário com similaridade de produtos
- Busca semântica em bases de conhecimento (FAQ, documentação técnica)
- Detecção de duplicatas em grandes volumes de texto
- Matching de currículos e vagas por competências
2. Instalação e configuração do pgvector
Pré-requisitos: PostgreSQL 12 ou superior. A instalação varia por sistema operacional:
# Debian/Ubuntu
sudo apt install postgresql-16-pgvector
# macOS (Homebrew)
brew install pgvector
# Compilação manual
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install
Ative a extensão no banco de dados:
CREATE EXTENSION vector;
Verifique a instalação:
SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';
Não há parâmetros de configuração obrigatórios, mas para datasets grandes, ajuste work_mem e shared_buffers no postgresql.conf.
3. Criação de tabelas e tipos de dados vetoriais
O tipo vector(n) armazena vetores de n dimensões. Exemplo prático para armazenar embeddings de documentos:
CREATE TABLE documentos (
id SERIAL PRIMARY KEY,
titulo TEXT NOT NULL,
conteudo TEXT,
autor VARCHAR(100),
data_publicacao DATE,
embedding vector(768) -- 768 dimensões (modelo all-MiniLM-L6-v2)
);
Inserção de dados:
INSERT INTO documentos (titulo, conteudo, embedding)
VALUES (
'Introdução ao Machine Learning',
'Machine learning é um subcampo da inteligência artificial...',
'[0.0123, -0.0456, 0.0789, ..., 0.0012]'::vector
);
4. Geração de embeddings com modelos externos
Com OpenAI API (Python):
import openai
import psycopg2
openai.api_key = "sua-chave"
def gerar_embedding(texto):
resp = openai.Embedding.create(
model="text-embedding-ada-002",
input=texto
)
return resp['data'][0]['embedding']
texto = "Redes neurais convolucionais para visão computacional"
vetor = gerar_embedding(texto)
conn = psycopg2.connect("dbname=meubd")
cur = conn.cursor()
cur.execute(
"INSERT INTO documentos (titulo, conteudo, embedding) VALUES (%s, %s, %s)",
("Visão Computacional", texto, vetor)
)
conn.commit()
Com sentence-transformers (local):
from sentence_transformers import SentenceTransformer
modelo = SentenceTransformer('all-MiniLM-L6-v2')
textos = ["Primeiro documento", "Segundo documento"]
embeddings = modelo.encode(textos).tolist()
5. Operações de busca por similaridade
O pgvector oferece três operadores de distância:
| Operador | Métrica | Uso típico |
|---|---|---|
<-> |
Distância L2 (Euclidiana) | Embeddings densos de alta dimensão |
<#> |
Produto interno negativo | Embeddings normalizados (similaridade cosseno) |
<=> |
Distância cosseno | Textos, quando embeddings não são normalizados |
Busca por similaridade semântica:
-- Encontrar os 5 documentos mais similares a uma consulta
SELECT id, titulo,
1 - (embedding <=> '[0.0234, -0.0567, ...]'::vector) AS similaridade
FROM documentos
ORDER BY embedding <=> '[0.0234, -0.0567, ...]'::vector
LIMIT 5;
Combinando filtros tradicionais:
SELECT id, titulo, autor,
embedding <=> '[consulta_embedding]'::vector AS distancia
FROM documentos
WHERE autor = 'Maria Silva'
AND data_publicacao >= '2024-01-01'
ORDER BY distancia
LIMIT 10;
6. Índices para busca vetorial eficiente
IVFFlat (Inverted File with Flat Compression): ideal para datasets de médio porte (até 1M vetores). Cria clusters de vetores similares.
CREATE INDEX idx_documentos_ivfflat
ON documentos
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
Parâmetros:
- lists: número de clusters (100 para até 1M registros, 1000 para maiores)
- Ajuste probes na consulta: SET ivfflat.probes = 10;
HNSW (Hierarchical Navigable Small World): melhor desempenho para grandes datasets e alta precisão.
CREATE INDEX idx_documentos_hnsw
ON documentos
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
Parâmetros:
- m: número de conexões por nó (16-64, maior = mais preciso mas mais lento)
- ef_construction: tamanho da lista dinâmica durante construção (200-800)
Comparação de desempenho (dataset 500k vetores, 768 dimensões):
| Índice | Tempo de busca (ms) | Precisão@10 | Tamanho do índice |
|---|---|---|---|
| Sem índice | 4500 | 100% | - |
| IVFFlat (lists=100) | 45 | 92% | 180 MB |
| HNSW (m=32) | 8 | 98% | 420 MB |
7. Otimização e boas práticas em produção
Particionamento por data ou categoria:
CREATE TABLE documentos_2024 PARTITION OF documentos
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
Monitoramento com EXPLAIN ANALYZE:
EXPLAIN (ANALYZE, BUFFERS)
SELECT titulo FROM documentos
ORDER BY embedding <=> '[vetor]'::vector
LIMIT 10;
Manutenção periódica:
- Reindexe após grandes inserções: REINDEX INDEX idx_documentos_hnsw;
- Atualize embeddings quando o modelo de embedding mudar (versione seus modelos)
- Use VACUUM ANALYZE regularmente para estatísticas atualizadas
8. Integração com aplicações e ferramentas
Exemplo com SQLAlchemy:
from sqlalchemy import Column, Integer, Text, Date
from sqlalchemy.dialects.postgresql import VECTOR
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Documento(Base):
__tablename__ = 'documentos'
id = Column(Integer, primary_key=True)
titulo = Column(Text)
conteudo = Column(Text)
embedding = Column(VECTOR(768))
API REST para busca semântica (FastAPI):
from fastapi import FastAPI
import psycopg2
from sentence_transformers import SentenceTransformer
app = FastAPI()
modelo = SentenceTransformer('all-MiniLM-L6-v2')
@app.get("/buscar")
def buscar_semantico(q: str, limite: int = 5):
vetor = modelo.encode(q).tolist()
conn = psycopg2.connect("dbname=meubd")
cur = conn.cursor()
cur.execute("""
SELECT id, titulo,
1 - (embedding <=> %s::vector) AS score
FROM documentos
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (vetor, vetor, limite))
resultados = cur.fetchall()
return [{"id": r[0], "titulo": r[1], "score": r[2]} for r in resultados]
Limitações conhecidas:
- O pgvector não suporta sharding nativo — para escalar horizontalmente, considere alternativas como Milvus, Qdrant ou pgvector em clusters com Citus
- Índices HNSW consomem mais memória RAM que IVFFlat
- A geração de embeddings é o gargalo mais comum — pré-calcule e armazene em cache
Referências
- Documentação oficial do pgvector — Repositório oficial com guia de instalação, tipos de dados, operadores e índices disponíveis
- PostgreSQL Vector Similarity Search com pgvector (Timescale) — Tutorial prático cobrindo desde instalação até otimização de consultas em produção
- How to Build a Semantic Search Engine with pgvector (Supabase) — Passo a passo completo integrando OpenAI embeddings com PostgreSQL e pgvector
- Sentence-Transformers Documentation — Biblioteca Python para geração de embeddings de texto, com modelos pré-treinados e exemplos de uso
- IVFFlat vs HNSW: Choosing the Right Index for pgvector (Crunchy Data) — Análise comparativa detalhada dos índices vetoriais com benchmarks reais
- pgvector Performance Benchmarks (Pgvector GitHub Wiki) — Benchmarks oficiais comparando diferentes configurações de índices e dimensionalidades