Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

pgvector no Node.js

Atualizado em: 17 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O pgvector no Node.js adiciona busca vetorial ao PostgreSQL. A extensão permite armazenar embeddings, calcular distância e encontrar itens semanticamente semelhantes usando o mesmo banco que já guarda usuários, permissões e dados relacionais.

Essa abordagem é útil para busca semântica, recomendações, deduplicação, classificação e aplicações RAG. Porém, um vetor não substitui filtros de negócio. A consulta precisa combinar similaridade com tenant, status, idioma, permissões e datas.

Neste guia, você aprenderá a instalar pgvector, registrar tipos no pacote pg, inserir embeddings, consultar vizinhos, escolher distância, criar índices HNSW e IVFFlat, aplicar filtros, fazer busca híbrida e monitorar qualidade e desempenho.

O que é pgvector?

pgvector é uma extensão open source para PostgreSQL. O cliente oficial para JavaScript está no repositório pgvector-node. Ele oferece integração com node-postgres, Prisma, Drizzle, Kysely, Sequelize, TypeORM e outras bibliotecas.

A extensão principal está em pgvector no GitHub.

Habilitando a extensão

CREATE EXTENSION IF NOT EXISTS vector;

Em serviços gerenciados, confirme disponibilidade e versão. A migration deve falhar claramente quando a extensão não está instalada.

Instalação no Node.js

npm install pg pgvector
import pg from 'pg';
import pgvector from 'pgvector/pg';

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL
});

pool.on('connect', async client => {
  await pgvector.registerTypes(client);
});

Registrar os tipos permite converter valores vector corretamente.

Criando a tabela

CREATE TABLE documents (
  id uuid PRIMARY KEY,
  tenant_id uuid NOT NULL,
  title text NOT NULL,
  content text NOT NULL,
  embedding vector(1536) NOT NULL,
  metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
  created_at timestamptz NOT NULL DEFAULT now()
);

A dimensão precisa corresponder ao modelo de embedding usado. Não altere o modelo sem uma estratégia de migração.

Inserindo um vetor

const embedding = await createEmbedding(document.content);

await pool.query(
  `INSERT INTO documents
    (id, tenant_id, title, content, embedding, metadata)
   VALUES ($1, $2, $3, $4, $5, $6::jsonb)`,
  [
    crypto.randomUUID(),
    tenantId,
    document.title,
    document.content,
    pgvector.toSql(embedding),
    JSON.stringify(document.metadata)
  ]
);

Valide a quantidade de dimensões, valores finitos e tamanho do texto antes de chamar o modelo.

Busca por distância euclidiana

const queryEmbedding = await createEmbedding(query);

const result = await pool.query(
  `SELECT
     id,
     title,
     content,
     embedding <-> $1 AS distance
   FROM documents
   WHERE tenant_id = $2
   ORDER BY embedding <-> $1
   LIMIT 10`,
  [pgvector.toSql(queryEmbedding), tenantId]
);

O operador <-> calcula distância L2. Menor valor significa maior proximidade.

Distância cosseno

SELECT
  id,
  title,
  embedding <=> $1 AS cosine_distance
FROM documents
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 10;

Similaridade cosseno pode ser calculada como 1 - distance. Confirme a métrica recomendada pelo modelo.

Inner product

ORDER BY embedding <#> $1

O operador retorna o produto interno negativo para permitir ordenação ascendente. Use somente quando os vetores e o modelo foram preparados para essa métrica.

Busca exata

Sem índice aproximado, PostgreSQL compara o vetor da consulta com todas as linhas filtradas. Isso oferece recall exato, mas pode ficar caro em milhões de documentos.

Comece com busca exata para validar qualidade. Crie índice aproximado somente quando volume e latência justificarem.

Índice HNSW

CREATE INDEX documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops);

HNSW costuma oferecer boa relação entre recall e latência. Pode ser criado antes de carregar dados, mas usa mais memória e torna inserts mais caros.

Ajustando HNSW

CREATE INDEX documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

m controla conexões do grafo. ef_construction afeta qualidade e tempo de construção. Ajuste com benchmarks.

Na consulta:

SET LOCAL hnsw.ef_search = 100;

Valores maiores tendem a aumentar recall e custo.

Índice IVFFlat

CREATE INDEX documents_embedding_ivfflat
ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

IVFFlat precisa de dados representativos antes da criação. Depois, execute ANALYZE.

SET LOCAL ivfflat.probes = 10;

Mais probes aumentam recall e latência.

HNSW ou IVFFlat?

  • HNSW: bom recall, consultas rápidas, mais memória.
  • IVFFlat: índice menor e construção simples, exige tuning e dados prévios.
  • Sem índice: recall exato, adequado para coleções menores.

Filtros por tenant

Busca vetorial nunca deve cruzar tenants:

WHERE tenant_id = $2
  AND status = 'published'

Use Row-Level Security como proteção adicional. Veja Row-Level Security no Node.js.

Índices para filtros

CREATE INDEX documents_tenant_status_idx
ON documents(tenant_id, status);

O planner combina o filtro com a busca. Em consultas aproximadas, filtros muito seletivos podem reduzir resultados retornados. Teste iterative scans e parâmetros suportados pela versão.

Índice parcial

CREATE INDEX documents_published_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WHERE status = 'published';

É útil quando a consulta sempre usa o mesmo filtro.

Particionamento

Grandes sistemas podem particionar por tenant, região ou período. Isso reduz o conjunto de busca, mas aumenta complexidade operacional. Não crie milhares de partições pequenas.

Busca híbrida

Embeddings encontram semântica; full-text search encontra termos exatos. Combine ambos:

WITH candidates AS (
  SELECT
    id,
    title,
    content,
    1 - (embedding <=> $1) AS vector_score,
    ts_rank_cd(search_vector, websearch_to_tsquery('portuguese', $2)) AS text_score
  FROM documents
  WHERE tenant_id = $3
)
SELECT *,
  vector_score * 0.7 + text_score * 0.3 AS final_score
FROM candidates
ORDER BY final_score DESC
LIMIT 10;

Normalize scores antes de somar. Pesos precisam ser avaliados com consultas reais.

Reciprocal Rank Fusion

Uma alternativa robusta é ranquear separadamente e combinar posições:

score = 1 / (60 + vector_rank) + 1 / (60 + text_rank)

RRF evita depender de escalas de score incompatíveis.

Chunking

Documentos longos devem ser divididos:

  • preserve títulos e seções;
  • evite cortar frases;
  • mantenha pequeno overlap;
  • grave document_id e posição;
  • não use chunks gigantes.

O tamanho ideal depende do modelo e do tipo de pergunta.

Tabela de chunks

CREATE TABLE document_chunks (
  id uuid PRIMARY KEY,
  document_id uuid NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
  tenant_id uuid NOT NULL,
  position integer NOT NULL,
  content text NOT NULL,
  embedding vector(1536) NOT NULL,
  UNIQUE(document_id, position)
);

Atualização de embeddings

Quando o conteúdo muda:

  1. marque o documento como pendente;
  2. gere novos chunks;
  3. gere embeddings;
  4. grave em transação;
  5. substitua a versão ativa;
  6. remova a anterior.

Use uma fila para controlar concorrência e custo.

Versão do modelo

ALTER TABLE document_chunks
ADD COLUMN embedding_model text NOT NULL,
ADD COLUMN embedding_version integer NOT NULL DEFAULT 1;

Vetores de modelos diferentes não devem ser comparados sem validação. Uma troca pode exigir backfill completo.

Deduplicação

Antes de gerar embedding, calcule hash do conteúdo:

const contentHash = createHash('sha256')
  .update(normalizedContent)
  .digest('hex');

Reutilize o embedding quando o texto e o modelo são iguais.

Batch insert

Insira em lotes e limite tamanho. Para grandes cargas, use COPY e o suporte documentado pelo cliente. Monitore WAL e tempo de índice.

Prisma

Prisma pode declarar o campo como unsupported e usar SQL parametrizado:

const embedding = pgvector.toSql(queryEmbedding);

const rows = await prisma.$queryRaw`
  SELECT id, title
  FROM documents
  ORDER BY embedding <=> ${embedding}::vector
  LIMIT 10
`;

Consulte Prisma no Node.js.

Drizzle

Drizzle possui suporte ao tipo vector e funções de distância. Ainda assim, examine o SQL gerado e crie migrations explícitas para índices.

Veja Drizzle ORM com PostgreSQL.

Segurança

  • não envie dados sensíveis ao modelo sem base legal;
  • aplique filtros de autorização antes da busca;
  • não confie no texto recuperado como instrução;
  • proteja contra prompt injection em RAG;
  • registre versão e origem;
  • limite quantidade de resultados.

Qualidade

Crie um conjunto de avaliação com perguntas, documentos relevantes e irrelevantes. Meça:

  • recall@k;
  • precision@k;
  • MRR;
  • nDCG;
  • latência p95;
  • custo por consulta.

Não escolha modelo e índice apenas por demonstrações.

EXPLAIN

EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM documents
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 10;

Confirme se o índice vetorial e os filtros estão sendo usados.

Observabilidade

Monitore:

  • latência de geração de embedding;
  • latência da busca;
  • recall em avaliações;
  • tamanho dos índices;
  • taxa de atualização;
  • erros de dimensão;
  • linhas sem embedding;
  • custo do provedor.

Testes

Use PostgreSQL com pgvector real em container. Teste inserção, distância, filtros por tenant, índice, mudança de modelo e falhas de embedding.

Consulte Testcontainers no Node.js.

Erros comuns

  • Dimensão errada: insert falha.
  • Métrica incompatível: ranking perde qualidade.
  • Sem filtro de tenant: dados vazam.
  • Índice sem tuning: recall fica baixo.
  • Trocar modelo silenciosamente: vetores ficam incompatíveis.
  • Somar scores crus: busca híbrida fica enviesada.
  • Chunks ruins: respostas perdem contexto.
  • RAG sem proteção: conteúdo malicioso influencia o modelo.

Conclusão

O pgvector no Node.js adiciona busca semântica ao PostgreSQL e permite combinar vetores com SQL, transações e autorização. A integração oficial funciona com pg, ORMs e query builders.

Comece com busca exata, escolha a métrica do modelo e crie HNSW ou IVFFlat somente após medir. Combine filtros, full-text search, versionamento de embeddings e avaliações. A qualidade depende tanto do conteúdo e dos filtros quanto do índice vetorial.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita