Salta al contenuto principale

01 - pgvector: Trasforma PostgreSQL in Vector DB

Guida completa a pgvector: Trasforma PostgreSQL in Vector DB: architettura, implementazione pratica e best practices per developer e team tecnici.

Cosa ne pensi di questo articolo?

01 - pgvector: Trasforma PostgreSQL in Vector DB
PostgreSQL AIFoto:panumas nikhomkhaiPexels

In Breve

Guida completa a pgvector: Trasforma PostgreSQL in Vector DB: architettura, implementazione pratica e best practices per developer e team tecnici.

pgvector: Trasforma PostgreSQL in un Vector Database

Nel 2026, il mantra della community e diventato chiaro: "Just Use Postgres". Snowflake ha acquisito Crunchy Data per 250 milioni di dollari, Databricks ha investito 1 miliardo su Neon, e Supabase ha raggiunto una valutazione di 5 miliardi. PostgreSQL non e più solo un database relazionale: con pgvector, diventa un vector database completo, capace di gestire embeddings, similarity search e operazioni AI senza aggiungere un singolo servizio al tuo stack.

Se stai già usando PostgreSQL (e statisticamente lo stai facendo), non hai bisogno di Pinecone, Qdrant o Weaviate per iniziare con la ricerca vettoriale. In questo articolo costruiremo un sistema di vector search completo partendo da zero: dall'installazione di pgvector alla creazione di indici ottimizzati, passando per la generazione di embeddings e l'integrazione con LangChain.

Panoramica della Serie

# Articolo Focus
1Sei qui - pgvectorInstallazione, operatori, indexing
2Embeddings in ProfonditàModelli, chunking, dimensioni
3RAG con PostgreSQLPipeline RAG end-to-end
4Hybrid SearchVettori + full-text search
5Scaling pgvectorPartitioning, sharding, performance
6pgvector in ProduzioneMonitoring, backup, CI/CD

Cosa Imparerai

  • Cos'è pgvector e perchè e l'estensione PostgreSQL più importante del 2026
  • Come installare pgvector su Linux, Docker e servizi managed (Supabase, Neon, AWS RDS)
  • Come creare tabelle con colonne vettoriali e inserire embeddings
  • I tre operatori di distanza: Cosine, L2 ed Inner Product
  • Come eseguire query di similarity search efficaci
  • Come generare embeddings con Python (OpenAI e Sentence Transformers)
  • HNSW vs IVFFlat: quale indice scegliere e come configurarlo
  • Benchmark pgvector vs database vettoriali dedicati
  • Un esempio completo end-to-end con Python
  • Integrazione con LangChain per sistemi RAG

1. Il Trend "Just Use Postgres" nel 2026

Per anni, il mondo AI ha spinto verso database vettoriali dedicati: Pinecone, Weaviate, Qdrant, Milvus. Ogni progetto RAG sembrava richiedere un nuovo servizio da gestire, monitorare e pagare. Ma nel 2025-2026 qualcosa e cambiato radicalmente.

La community ha capito che i vettori non sono un tipo di database, ma un tipo di dato. Cosi come PostgreSQL gestisce JSON, array, geometrie e testi con ricerca full-text, può gestire anche vettori ad alta dimensionalità. L'estensione che rende tutto questo possibile si chiama pgvector.

perchè "Just Use Postgres" nel 2026

Fattore Database Vettoriale Dedicato PostgreSQL + pgvector
Infrastruttura Nuovo servizio da gestire Estensione nel DB esistente
Costo Piano separato (Pinecone da $70/mese) Incluso nel costo PostgreSQL
Consistenza dati Sincronizzazione tra DB necessaria Transazioni ACID native
Query API proprietaria SQL standard + JOIN, WHERE, GROUP BY
Backup Sistema separato pg_dump include tutto
Team Competenze specifiche richieste Chiunque conosca SQL

Il vantaggio chiave e la semplicità operativa. Con pgvector, i tuoi embeddings vivono nella stessa tabella dei tuoi dati relazionali. Puoi fare JOIN tra vettori e metadati, usare WHERE clause per filtrare, e avere consistenza transazionale. Nessun servizio esterno da sincronizzare, nessuna API diversa da imparare.

2. Cos'è pgvector

pgvector e un'estensione open-source per PostgreSQL che aggiunge il supporto nativo per vettori ad alta dimensionalità e ricerca per similarità. Creata da Andrew Kane nel 2021, e diventata rapidamente l'estensione PostgreSQL più scaricata per use case AI/ML.

Con pgvector puoi:

  • Salvare vettori (embeddings) come tipo di dato nativo nella colonna di una tabella
  • Eseguire ricerche di similarità usando tre metriche di distanza diverse
  • Creare indici specializzati (HNSW e IVFFlat) per ricerche veloci
  • Combinare la ricerca vettoriale con tutto ciò che PostgreSQL offre: JOIN, transazioni, trigger, viste materializzate

pgvector in Numeri (2026)

Metrica Valore
Versione attuale0.8.0+ (con iterative index scans)
Dimensioni vettore max16.000
Tipi indiceHNSW, IVFFlat
Tipi vettorevector, halfvec, sparsevec, bit
Metriche distanzaCosine, L2 (Euclidea), Inner Product
Supportato daAWS RDS, Supabase, Neon, Google Cloud SQL, Azure
LicenzaPostgreSQL License (open-source)

La versione 0.8.0, rilasciata nel 2025, ha introdotto una funzionalità chiave: gli iterative index scans. Questa feature risolve il problema dell'overfiltering, ovvero quando un indice vettoriale non restituisce abbastanza risultati perchè la maggior parte viene scartata dai filtri WHERE. Con gli iterative scans, pgvector continua a esplorare l'indice fino a trovare abbastanza risultati che soddisfano i filtri, migliorando drasticamente la qualità delle query filtrate.

3. Installazione di pgvector

pgvector si installa in diversi modi a seconda del tuo ambiente. Vediamo i tre approcci principali: installazione nativa su Linux, Docker e servizi managed.

3.1 Installazione su Ubuntu/Debian

Installazione pgvector su Linux

# Prerequisiti: PostgreSQL 14+ già installato
sudo apt-get update
sudo apt-get install -y postgresql-server-dev-16

# Clona e compila pgvector
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install

# Abilita l'estensione nel tuo database
sudo -u postgres psql -d il_tuo_database -c "CREATE EXTENSION vector;"
    

Per PostgreSQL 17, sostituisci postgresql-server-dev-16 con postgresql-server-dev-17. pgvector supporta PostgreSQL dalla versione 13 alla 17.

3.2 Installazione con Docker

Il modo più veloce per iniziare. L'immagine ufficiale pgvector/pgvector include PostgreSQL con pgvector pre-installato.

Docker Compose per pgvector

# docker-compose.yml
version: '3.8'
services:
  postgres:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_DB: vectordb
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret_password
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql

volumes:
  pgdata:
    
init.sql - Abilitare pgvector all'avvio

-- init.sql: eseguito automaticamente al primo avvio
CREATE EXTENSION IF NOT EXISTS vector;
    
Avvio rapido con un singolo comando Docker

# Avvio rapido senza docker-compose
docker run -d \
  --name pgvector-db \
  -e POSTGRES_DB=vectordb \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  pgvector/pgvector:pg17

# Connetti e abilita pgvector
docker exec -it pgvector-db psql -U postgres -d vectordb \
  -c "CREATE EXTENSION vector;"
    

3.3 Servizi Managed (Zero Setup)

Se non vuoi gestire l'infrastruttura, diversi servizi cloud offrono PostgreSQL con pgvector già abilitato. Questa e la scelta ideale per la produzione.

Servizi Managed con pgvector

Servizio pgvector Incluso Piano Gratuito Note
Supabase Pre-installato Si (500 MB) Dashboard UI, API REST automatiche
Neon Pre-installato Si (0.5 GB) Serverless, branch database
AWS RDS Da abilitare No pgvector 0.8.0 su Aurora PostgreSQL
Google Cloud SQL Da abilitare No Supporto pgvector 0.8.0
Azure Database Da abilitare No Flexible Server con pgvector
Abilitare pgvector su Supabase

-- Su Supabase, pgvector e già disponibile.
-- Basta abilitare l'estensione:
CREATE EXTENSION IF NOT EXISTS vector;

-- Verifica l'installazione
SELECT extversion FROM pg_extension WHERE extname = 'vector';
-- Risultato: 0.8.0
    

4. Creare una Tabella con Vettori

Con pgvector installato, puoi aggiungere colonne di tipo vector(n) alle tue tabelle, dove n e la dimensionalità del vettore. Il valore di n dipende dal modello di embedding che usi: OpenAI text-embedding-3-small produce vettori a 1536 dimensioni, mentre text-embedding-3-large arriva fino a 3072.

Creare la tabella documenti con embedding

-- Abilita l'estensione (se non già fatto)
CREATE EXTENSION IF NOT EXISTS vector;

-- Tabella documenti per un sistema RAG
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    title VARCHAR(500) NOT NULL,
    content TEXT NOT NULL,
    source VARCHAR(255),
    category VARCHAR(100),
    embedding vector(1536),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Aggiungi un commento alla colonna per documentazione
COMMENT ON COLUMN documents.embedding IS
    'Embedding generato con OpenAI text-embedding-3-small (1536 dim)';
    

Inserire Documenti con Embeddings

L'inserimento funziona come un normale INSERT, passando il vettore come stringa di valori separati da virgola tra parentesi quadre.

Inserire embeddings nella tabella

-- Inserimento singolo
INSERT INTO documents (title, content, source, embedding)
VALUES (
    'Introduzione a PostgreSQL',
    'PostgreSQL e un database relazionale open-source potente e affidabile...',
    'docs/postgres-intro.md',
    '[0.023, -0.045, 0.067, 0.089, -0.012, ...]'  -- vettore a 1536 dimensioni
);

-- Inserimento multiplo (batch)
INSERT INTO documents (title, content, source, category, embedding)
VALUES
    ('Capitolo 1', 'Contenuto cap 1...', 'libro.pdf', 'tutorial',
     '[0.11, -0.22, 0.33, ...]'),
    ('Capitolo 2', 'Contenuto cap 2...', 'libro.pdf', 'tutorial',
     '[0.44, -0.55, 0.66, ...]'),
    ('FAQ', 'Domande frequenti...', 'faq.md', 'supporto',
     '[-0.12, 0.34, -0.56, ...]');
    

Dimensione del Vettore

La dimensionalità specificata nella definizione della colonna (vector(1536)) deve corrispondere esattamente alla dimensione degli embeddings che inserisci. Un tentativo di inserire un vettore a 768 dimensioni in una colonna vector(1536) causera un errore. Scegli la dimensione in base al modello di embedding che intendi usare.

Dimensioni dei Modelli di Embedding Popolari

Modello Provider Dimensioni Costo / 1M token
text-embedding-3-small OpenAI 1536 $0.02
text-embedding-3-large OpenAI 3072 $0.13
all-MiniLM-L6-v2 HuggingFace (gratuito) 384 Gratuito (locale)
all-mpnet-base-v2 HuggingFace (gratuito) 768 Gratuito (locale)
voyage-3 Voyage AI 1024 $0.06

5. Operatori di Distanza

pgvector offre tre operatori per calcolare la distanza tra vettori. Ogni operatore corrisponde a una metrica matematica diversa e ha casi d'uso specifici.

I Tre Operatori pgvector

Operatore Metrica Formula Ordinamento Caso d'Uso
<=> Cosine Distance 1 - cosine_similarity ASC (0 = identici) Testo, embeddings normalizzati
<-> L2 (Euclidea) sqrt(sum((a-b)^2)) ASC (0 = identici) Immagini, dati spaziali
<#> Inner Product (negato) -1 * dot_product ASC (più negativo = più simile) Vettori normalizzati, max performance
Confronto degli operatori in pratica

-- COSINE DISTANCE <=> (il più usato per testo)
-- Risultato: 0 = identici, 2 = opposti
SELECT title, content,
       embedding <=> '[0.1, 0.2, ...]' AS cosine_distance
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]' ASC
LIMIT 5;

-- L2 (EUCLIDEA) <->
-- Risultato: 0 = identici, valori crescenti = più lontani
SELECT title, content,
       embedding <-> '[0.1, 0.2, ...]' AS l2_distance
FROM documents
ORDER BY embedding <-> '[0.1, 0.2, ...]' ASC
LIMIT 5;

-- INNER PRODUCT (NEGATO) <#>
-- Risultato: valori più negativi = più simili
SELECT title, content,
       (embedding <#> '[0.1, 0.2, ...]') * -1 AS similarity
FROM documents
ORDER BY embedding <#> '[0.1, 0.2, ...]' ASC
LIMIT 5;
    

Quale Operatore Scegliere

Per il 90% degli use case con testo e AI, usa <=> (cosine distance). I modelli di embedding come quelli di OpenAI producono vettori normalizzati, il che rende cosine distance e inner product equivalenti in termini di risultato. Usa <-> (L2) solo quando lavori con dati spaziali o immagini dove la magnitudine del vettore conta. L'inner product (<#>) e leggermente più veloce del cosine perchè evita la normalizzazione, ma solo su vettori già normalizzati.

6. Query di Similarity Search

La similarity search e il cuore di pgvector. La query base e semplice: ordina i risultati per distanza dal vettore query e prendi i primi K. Ma in produzione, le query diventano più sofisticate combinando ricerca vettoriale con filtri SQL.

6.1 Query Base

Similarity search fondamentale

-- Trova i 5 documenti più simili a un vettore query
SELECT
    id,
    title,
    content,
    1 - (embedding <=> $1) AS similarity  -- $1 = vettore query
FROM documents
ORDER BY embedding <=> $1
LIMIT 5;
    

6.2 Query con Filtri (il vero potere di pgvector)

Qui pgvector brilla rispetto ai vector database dedicati. Puoi combinare la ricerca vettoriale con qualsiasi clausola SQL: WHERE, JOIN, GROUP BY, subquery.

Similarity search con filtri avanzati

-- Filtra per categoria e soglia di similarità
SELECT
    id, title, content,
    1 - (embedding <=> $1) AS similarity
FROM documents
WHERE category = 'tutorial'
  AND created_at > NOW() - INTERVAL '30 days'
ORDER BY embedding <=> $1
LIMIT 5;

-- JOIN con altre tabelle: trova documenti simili con info autore
SELECT
    d.title,
    d.content,
    a.name AS author_name,
    1 - (d.embedding <=> $1) AS similarity
FROM documents d
JOIN authors a ON d.author_id = a.id
WHERE a.verified = true
ORDER BY d.embedding <=> $1
LIMIT 10;

-- Esclusione: trova simili ma NON il documento stesso
SELECT
    id, title,
    1 - (embedding <=> (SELECT embedding FROM documents WHERE id = 42)) AS similarity
FROM documents
WHERE id != 42
ORDER BY embedding <=> (SELECT embedding FROM documents WHERE id = 42)
LIMIT 5;
    

6.3 Iterative Index Scans (pgvector 0.8.0)

Una delle novità più importanti della versione 0.8.0 e il supporto agli iterative index scans. Quando combini ricerca vettoriale con filtri WHERE restrittivi, l'indice potrebbe non trovare abbastanza risultati nel primo passaggio. Con gli iterative scans, pgvector continua a esplorare l'indice fino a soddisfare il LIMIT richiesto.

Abilitare iterative index scans

-- Abilita iterative scan per HNSW
SET hnsw.iterative_scan = relaxed_order;  -- o strict_order

-- Imposta il limite massimo di tuple da scansionare
SET hnsw.max_scan_tuples = 20000;

-- Ora le query con filtri restrittivi funzionano correttamente
SELECT id, title,
       1 - (embedding <=> $1) AS similarity
FROM documents
WHERE category = 'raro'  -- categoria con pochi documenti
ORDER BY embedding <=> $1
LIMIT 10;
-- Senza iterative scan: potrebbe restituire 0-3 risultati
-- Con iterative scan: restituisce sempre fino a 10 risultati

-- Modalità disponibili:
-- off           = comportamento tradizionale (default)
-- strict_order  = mantiene l'ordine esatto delle distanze
-- relaxed_order = ordine approssimato (migliori performance)
    

Quando Usare Iterative Scans

  • relaxed_order: nella maggior parte dei casi, offre il miglior bilanciamento tra performance e qualità
  • strict_order: quando l'ordine esatto delle distanze e critico (es. ranking di risultati per l'utente)
  • off: quando non hai filtri WHERE o quando i filtri non sono restrittivi

7. Generare Embeddings con Python

Per inserire dati in pgvector, devi prima trasformare il testo in vettori numerici usando un modello di embedding. Vediamo due approcci: l'API OpenAI (a pagamento, alta qualità) e Sentence Transformers (gratuito, locale).

7.1 Con l'API OpenAI

Generare embeddings con OpenAI e inserirli in PostgreSQL

import openai
import psycopg2
from psycopg2.extras import execute_values

# Configura il client OpenAI
client = openai.OpenAI(api_key="sk-...")

def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
    """Genera un embedding per un testo usando OpenAI."""
    response = client.embeddings.create(
        input=text,
        model=model
    )
    return response.data[0].embedding  # lista di 1536 float

# Connetti a PostgreSQL
conn = psycopg2.connect(
    host="localhost",
    database="vectordb",
    user="admin",
    password="secret_password"
)
cur = conn.cursor()

# Genera e inserisci embeddings
documents = [
    ("Introduzione a Python", "Python e un linguaggio di programmazione versatile..."),
    ("PostgreSQL Avanzato", "Le CTE ricorsive permettono di eseguire query gerarchiche..."),
    ("Docker per Sviluppatori", "I container Docker isolano le applicazioni..."),
]

for title, content in documents:
    embedding = get_embedding(content)
    cur.execute(
        "INSERT INTO documents (title, content, embedding) VALUES (%s, %s, %s)",
        (title, content, str(embedding))
    )

conn.commit()
cur.close()
conn.close()
    

7.2 Con Sentence Transformers (Gratuito e Locale)

Embeddings gratuiti con Sentence Transformers

from sentence_transformers import SentenceTransformer
import psycopg2
import numpy as np

# Carica il modello (scaricato automaticamente la prima volta)
model = SentenceTransformer('all-MiniLM-L6-v2')  # 384 dimensioni

# Genera embeddings per un batch di testi
texts = [
    "Python e un linguaggio di programmazione versatile",
    "Le CTE ricorsive permettono query gerarchiche in SQL",
    "I container Docker isolano le applicazioni",
]
embeddings = model.encode(texts)  # shape: (3, 384)

# Connetti e inserisci
conn = psycopg2.connect(
    host="localhost", database="vectordb",
    user="admin", password="secret_password"
)
cur = conn.cursor()

# NOTA: la colonna deve essere vector(384), non vector(1536)
for text, embedding in zip(texts, embeddings):
    cur.execute(
        "INSERT INTO documents (title, content, embedding) VALUES (%s, %s, %s)",
        (text[:100], text, str(embedding.tolist()))
    )

conn.commit()
cur.close()
conn.close()
    

OpenAI vs Sentence Transformers

Aspetto OpenAI API Sentence Transformers
Costo $0.02 / 1M token Gratuito
qualità Eccellente (stato dell'arte) Buona (sufficiente per molti casi)
Privacy Dati inviati a OpenAI Tutto locale, nessun dato esce
Velocita ~100ms per richiesta (rete) ~10ms per testo (con GPU)
Offline No (richiede internet) Si (dopo il primo download)
Dimensioni 1536 o 3072 384 o 768 (modello-dipendente)

Regola d'Oro degli Embeddings

Usa lo stesso modello di embedding sia per indicizzare i documenti sia per generare il vettore della query. Embeddings generati con modelli diversi vivono in spazi vettoriali diversi e non sono confrontabili. Se indicizzi con text-embedding-3-small, devi cercare con lo stesso modello.

8. Indexing: HNSW vs IVFFlat

Senza un indice, pgvector esegue una scansione sequenziale (brute force) confrontando il vettore query con ogni riga della tabella. Questo e preciso al 100% ma diventa lento con dataset grandi. Gli indici ANN (Approximate Nearest Neighbor) sacrificano una minima percentuale di precisione per ottenere ricerche ordini di grandezza più veloci.

8.1 Indice HNSW

HNSW (Hierarchical Navigable Small World) costruisce un grafo multi-livello di connessioni tra vettori. E l'indice consigliato per la maggior parte dei casi: veloce in lettura, buona precisione, e supporta inserimenti incrementali senza ricostruzione.

Creare e configurare un indice HNSW

-- Indice HNSW con parametri di default
CREATE INDEX idx_documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops);

-- Indice HNSW con parametri personalizzati
CREATE INDEX idx_documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (
    m = 24,                -- connessioni per nodo (default: 16, range: 2-100)
    ef_construction = 128  -- candidati durante costruzione (default: 64, range: 4-1000)
);

-- Regola ef_search per le query (default: 40)
SET hnsw.ef_search = 100;  -- più alto = più preciso ma più lento
    

Parametri HNSW: Guida alla Configurazione

Parametro Default Range Effetto Consiglio
m 16 2 - 100 Più connessioni = più preciso, più memoria 16-32 per la maggior parte dei casi
ef_construction 64 4 - 1000 Più alto = indice migliore, build più lento 64-256 (una tantum, vale la pena alzare)
ef_search 40 1 - 1000 Più alto = ricerca più precisa, più lenta 40-200 in base al trade-off voluto

8.2 Indice IVFFlat

IVFFlat (Inverted File with Flat compression) divide i vettori in cluster e cerca solo nei cluster più vicini alla query. E più veloce da costruire di HNSW ma richiede la ricostruzione quando aggiungi molti dati nuovi. Ideale per dataset che cambiano poco frequentemente.

Creare e configurare un indice IVFFlat

-- IMPORTANTE: IVFFlat richiede dati nella tabella PRIMA di creare l'indice
-- (necessità di fare clustering k-means sui dati esistenti)

-- Indice IVFFlat
CREATE INDEX idx_documents_embedding_ivf
ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (
    lists = 100   -- numero di cluster (default: 100)
);

-- Regola il numero di cluster da esplorare durante la ricerca
SET ivfflat.probes = 10;  -- default: 1, consigliato: sqrt(lists)

-- Regola pratica per "lists":
-- rows < 1M:    lists = rows / 1000
-- rows >= 1M:   lists = sqrt(rows)
    

8.3 Operatori per Tipo di Distanza

L'operatore specificato nella creazione dell'indice deve corrispondere all'operatore usato nelle query. Ecco la mappa completa.

Mappa Operatori e Classi di Indice

Distanza Operatore SQL Classe Indice (ops)
Cosine <=> vector_cosine_ops
L2 (Euclidea) <-> vector_l2_ops
Inner Product <#> vector_ip_ops

8.4 HNSW vs IVFFlat: Quale Scegliere

Confronto HNSW vs IVFFlat

Aspetto HNSW IVFFlat
Velocita ricerca Più veloce Veloce
Precisione (recall) ~95-99% ~90-98%
Tempo di build Lento Veloce
Memoria Alta Media
Inserimenti incrementali Supportati Richiedono REINDEX
Iterative scans Si (0.8.0) Si (0.8.0)
Caso d'uso ideale Uso generale, dati in crescita Dataset statici, build veloce

Consiglio Pratico

Per la maggior parte dei progetti, scegli HNSW. E più versatile, supporta inserimenti incrementali senza ricostruzione, e la versione 0.8.0 di pgvector ha migliorato significativamente le sue performance. Usa IVFFlat solo se il tempo di costruzione dell'indice e critico (dataset molto grandi caricati una volta sola) o se hai vincoli di memoria stringenti.

9. Performance: pgvector vs Database Vettoriali Dedicati

La domanda più frequente e: "pgvector e abbastanza veloce per la produzione?". La risposta nel 2026 e un chiaro si, per la stragrande maggioranza dei casi d'uso. Vediamo i numeri.

9.1 Benchmark su Larga Scala

I benchmark di pgvectorscale (estensione complementare a pgvector sviluppata da Timescale) su 50 milioni di vettori mostrano risultati impressionanti:

Benchmark: 50M Vettori a 99% Recall

Sistema QPS (Query/sec) Recall Note
pgvectorscale 471 99% 11.4x più veloce di Qdrant
Qdrant 41 99% Ottimizzato per vector search puro

9.2 Benchmark a Scala Media (1M Vettori)

Benchmark: 1M Vettori - Throughput

Sistema Query/sec Insert/sec Costo Managed
Pinecone ~5.000 ~50.000 Da $70/mese
Qdrant ~4.500 ~45.000 Self-hosted o cloud
pgvector ~3.000 ~30.000 Incluso nel costo PG

A 1M di vettori, pgvector e leggermente più lento in throughput puro. Ma considerando che il costo e praticamente zero (e già incluso nel tuo PostgreSQL), il rapporto qualità-prezzo e imbattibile. Per la maggior parte delle applicazioni web, 3.000 query al secondo sono più che sufficienti.

Quando pgvector NON Basta

Considera un database vettoriale dedicato (Qdrant, Pinecone) quando:

  • Hai oltre 50 milioni di vettori
  • Hai bisogno di latenze p99 sotto i 5ms
  • Il tuo workload e esclusivamente vector search (nessun dato relazionale)
  • Hai bisogno di sharding distribuito nativo

Per tutto il resto, pgvector e la scelta giusta. Un sistema sotto 10 milioni di vettori con PostgreSQL già nello stack non ha bisogno di complessità aggiuntiva.

10. Esempio Completo: Knowledge Base con Python

Mettiamo tutto insieme in un esempio pratico. Costruiremo una knowledge base che: indicizza documenti con embeddings, permette ricerche per similarità e supporta filtri per categoria.

knowledge_base.py - Esempio completo

"""
Knowledge Base con PostgreSQL + pgvector
Requisiti: pip install openai psycopg2-binary
"""
import openai
import psycopg2
from dataclasses import dataclass

# === Configurazione ===

OPENAI_API_KEY = "sk-..."
DB_CONFIG = {
    "host": "localhost",
    "database": "vectordb",
    "user": "admin",
    "password": "secret_password",
}
EMBEDDING_MODEL = "text-embedding-3-small"
EMBEDDING_DIM = 1536


@dataclass(frozen=True)
class SearchResult:
    id: int
    title: str
    content: str
    similarity: float
    category: str


# === Funzioni di embedding ===

client = openai.OpenAI(api_key=OPENAI_API_KEY)

def get_embedding(text: str) -> list[float]:
    """Genera un embedding per un testo."""
    response = client.embeddings.create(
        input=text,
        model=EMBEDDING_MODEL,
    )
    return response.data[0].embedding


def get_embeddings_batch(texts: list[str]) -> list[list[float]]:
    """Genera embeddings per un batch di testi (più efficiente)."""
    response = client.embeddings.create(
        input=texts,
        model=EMBEDDING_MODEL,
    )
    return [item.embedding for item in response.data]


# === Setup del database ===

def setup_database() -> None:
    """Crea la tabella e gli indici."""
    conn = psycopg2.connect(**DB_CONFIG)
    cur = conn.cursor()

    cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")

    cur.execute(f"""
        CREATE TABLE IF NOT EXISTS kb_documents (
            id BIGSERIAL PRIMARY KEY,
            title VARCHAR(500) NOT NULL,
            content TEXT NOT NULL,
            category VARCHAR(100) DEFAULT 'general',
            embedding vector({EMBEDDING_DIM}),
            created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
        );
    """)

    cur.execute("""
        CREATE INDEX IF NOT EXISTS idx_kb_embedding_hnsw
        ON kb_documents
        USING hnsw (embedding vector_cosine_ops)
        WITH (m = 24, ef_construction = 128);
    """)

    cur.execute("""
        CREATE INDEX IF NOT EXISTS idx_kb_category
        ON kb_documents (category);
    """)

    conn.commit()
    cur.close()
    conn.close()
    print("Database setup completato.")


# === Inserimento documenti ===

def insert_documents(
    documents: list[dict],
    batch_size: int = 50,
) -> None:
    """Inserisce documenti con embeddings generati automaticamente."""
    conn = psycopg2.connect(**DB_CONFIG)
    cur = conn.cursor()

    for i in range(0, len(documents), batch_size):
        batch = documents[i : i + batch_size]
        texts = [doc["content"] for doc in batch]
        embeddings = get_embeddings_batch(texts)

        for doc, emb in zip(batch, embeddings):
            cur.execute(
                """INSERT INTO kb_documents (title, content, category, embedding)
                   VALUES (%s, %s, %s, %s)""",
                (doc["title"], doc["content"],
                 doc.get("category", "general"), str(emb)),
            )

        print(f"Inseriti {min(i + batch_size, len(documents))}/{len(documents)} documenti")

    conn.commit()
    cur.close()
    conn.close()


# === Ricerca per similarità ===

def search(
    query: str,
    limit: int = 5,
    category: str | None = None,
    min_similarity: float = 0.3,
) -> list[SearchResult]:
    """Cerca documenti simili alla query."""
    query_embedding = get_embedding(query)

    conn = psycopg2.connect(**DB_CONFIG)
    cur = conn.cursor()

    # Abilita iterative scan per filtri restrittivi
    cur.execute("SET hnsw.iterative_scan = relaxed_order;")

    if category:
        cur.execute(
            """SELECT id, title, content, category,
                      1 - (embedding <=> %s::vector) AS similarity
               FROM kb_documents
               WHERE category = %s
               ORDER BY embedding <=> %s::vector
               LIMIT %s""",
            (str(query_embedding), category,
             str(query_embedding), limit),
        )
    else:
        cur.execute(
            """SELECT id, title, content, category,
                      1 - (embedding <=> %s::vector) AS similarity
               FROM kb_documents
               ORDER BY embedding <=> %s::vector
               LIMIT %s""",
            (str(query_embedding), str(query_embedding), limit),
        )

    results = [
        SearchResult(
            id=row[0], title=row[1], content=row[2],
            category=row[3], similarity=row[4],
        )
        for row in cur.fetchall()
        if row[4] >= min_similarity
    ]

    cur.close()
    conn.close()
    return results


# === Main ===

if __name__ == "__main__":
    # 1. Setup
    setup_database()

    # 2. Inserisci documenti di esempio
    sample_docs = [
        {
            "title": "Introduzione a PostgreSQL",
            "content": "PostgreSQL e un RDBMS open-source con supporto ACID completo.",
            "category": "database",
        },
        {
            "title": "Indici in PostgreSQL",
            "content": "I B-tree sono il tipo di indice default. HNSW e usato per vettori.",
            "category": "database",
        },
        {
            "title": "Cos'è il RAG",
            "content": "RAG combina retrieval e generation per risposte basate su documenti.",
            "category": "ai",
        },
        {
            "title": "Docker per Sviluppatori",
            "content": "Docker isola le applicazioni in container leggeri e portabili.",
            "category": "devops",
        },
        {
            "title": "Embeddings e Vector Search",
            "content": "Gli embeddings trasformano testo in vettori per la similarity search.",
            "category": "ai",
        },
    ]
    insert_documents(sample_docs)

    # 3. Cerca documenti
    print("\n--- Ricerca: 'come funzionano i database vettoriali' ---")
    results = search("come funzionano i database vettoriali", limit=3)
    for r in results:
        print(f"  [{r.similarity:.3f}] {r.title} ({r.category})")

    print("\n--- Ricerca filtrata per categoria 'ai' ---")
    results = search("tutorial su PostgreSQL", limit=3, category="ai")
    for r in results:
        print(f"  [{r.similarity:.3f}] {r.title} ({r.category})")
    

11. Integrazione con LangChain

Se stai costruendo un sistema RAG, LangChain offre un'integrazione diretta con pgvector attraverso il pacchetto langchain-postgres. Questo ti permette di usare PostgreSQL come vector store senza scrivere SQL manualmente.

Installazione delle dipendenze

pip install langchain-postgres langchain-openai psycopg
    
pgvector come Vector Store in LangChain

from langchain_postgres import PGVector
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document

# Configura il modello di embedding
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small",
    openai_api_key="sk-...",
)

# NOTA: langchain-postgres usa psycopg3 (non psycopg2)
CONNECTION_STRING = "postgresql+psycopg://admin:secret@localhost:5432/vectordb"

# Crea il vector store
vector_store = PGVector(
    embeddings=embeddings,
    collection_name="langchain_docs",
    connection=CONNECTION_STRING,
    use_jsonb=True,  # metadati JSONB per filtri efficienti
)

# Aggiungi documenti
docs = [
    Document(
        page_content="pgvector trasforma PostgreSQL in un vector database",
        metadata={"source": "tutorial.md", "chapter": 1},
    ),
    Document(
        page_content="HNSW e l'indice consigliato per la maggior parte dei casi",
        metadata={"source": "tutorial.md", "chapter": 2},
    ),
    Document(
        page_content="LangChain semplifica la costruzione di sistemi RAG",
        metadata={"source": "guida-rag.md", "chapter": 1},
    ),
]

vector_store.add_documents(docs)

# Ricerca per similarità
results = vector_store.similarity_search(
    "come creare un indice vettoriale",
    k=3,
)
for doc in results:
    print(f"[{doc.metadata['source']}] {doc.page_content[:80]}...")

# Ricerca con score
results_with_scores = vector_store.similarity_search_with_score(
    "cos'è pgvector",
    k=3,
)
for doc, score in results_with_scores:
    print(f"[Score: {score:.4f}] {doc.page_content[:80]}...")

# Usa come retriever in una catena RAG
retriever = vector_store.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5},
)

# Il retriever può essere usato direttamente nelle chain LangChain
relevant_docs = retriever.invoke("installazione pgvector docker")
    

Nota su psycopg3

Il pacchetto langchain-postgres richiede psycopg3 (non psycopg2). La stringa di connessione deve usare il formato postgresql+psycopg:// invece di postgresql+psycopg2://. Se stai migrando da langchain-community, aggiorna la connection string.

12. Best Practices per pgvector in Produzione

12.1 Dimensione dei Vettori

Vettori più piccoli occupano meno spazio, rendono gli indici più veloci e riducono i costi. OpenAI text-embedding-3-small (1536 dim) offre il miglior rapporto qualità-dimensione per la maggior parte dei casi. Se puoi tollerare una leggera perdita di qualità, all-MiniLM-L6-v2 (384 dim) e un'ottima scelta gratuita.

12.2 Batch Insert

Inserimento batch ottimizzato

from psycopg2.extras import execute_values

# SBAGLIATO: un INSERT alla volta (lento)
for doc in documents:
    cur.execute("INSERT INTO docs (content, embedding) VALUES (%s, %s)",
                (doc.content, str(doc.embedding)))

# CORRETTO: batch insert con execute_values (10-50x più veloce)
data = [
    (doc.content, str(doc.embedding))
    for doc in documents
]
execute_values(
    cur,
    "INSERT INTO docs (content, embedding) VALUES %s",
    data,
    page_size=500,  # righe per batch SQL
)
conn.commit()
    

12.3 Connection Pooling

In produzione, non aprire una nuova connessione PostgreSQL per ogni richiesta. Usa un connection pool come PgBouncer o il pooling integrato di psycopg2.

Connection pooling con psycopg2

from psycopg2 import pool

# Crea un pool di connessioni (all'avvio dell'applicazione)
connection_pool = pool.ThreadedConnectionPool(
    minconn=5,    # connessioni minime
    maxconn=20,   # connessioni massime
    host="localhost",
    database="vectordb",
    user="admin",
    password="secret_password",
)

# Usa una connessione dal pool
conn = connection_pool.getconn()
try:
    cur = conn.cursor()
    cur.execute("SELECT ... ORDER BY embedding <=> %s LIMIT 5", ...)
    results = cur.fetchall()
    cur.close()
finally:
    connection_pool.putconn(conn)  # restituisci al pool
    

12.4 Maintenance

Operazioni di manutenzione periodica

-- VACUUM per recuperare spazio dopo DELETE/UPDATE
VACUUM ANALYZE documents;

-- Ricostruisci l'indice IVFFlat dopo inserimenti massicci
REINDEX INDEX CONCURRENTLY idx_documents_embedding_ivf;
-- NOTA: HNSW non richiede REINDEX per nuovi inserimenti

-- Monitora la dimensione dell'indice
SELECT
    indexname,
    pg_size_pretty(pg_relation_size(indexname::regclass)) AS size
FROM pg_indexes
WHERE tablename = 'documents';

-- Verifica che l'indice venga usato
EXPLAIN ANALYZE
SELECT id, 1 - (embedding <=> '[0.1, 0.2, ...]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 5;
-- Cerca "Index Scan using idx_..._hnsw" nell'output
    

Checklist per la Produzione

  • Crea un indice HNSW con m = 24 e ef_construction = 128
  • Imposta ef_search tra 40 e 200 in base al trade-off latenza/precisione
  • Abilita hnsw.iterative_scan = relaxed_order se usi filtri WHERE
  • Usa batch insert con execute_values per caricamenti massicci
  • Configura un connection pool (PgBouncer o psycopg2.pool)
  • Schedula VACUUM ANALYZE periodico
  • Monitora le query con EXPLAIN ANALYZE per verificare l'uso dell'indice
  • Usa halfvec invece di vector per risparmiare il 50% di spazio (se la precisione float16 e sufficiente)

Conclusioni

pgvector ha trasformato PostgreSQL da semplice database relazionale a piattaforma AI-ready. Con la versione 0.8.0 e le sue iterative index scans, il gap con i database vettoriali dedicati si e ridotto drasticamente, e per la maggior parte dei progetti sotto 10 milioni di vettori, non c'è ragione di aggiungere un servizio separato al tuo stack.

In questo articolo hai imparato a installare pgvector, creare tabelle con colonne vettoriali, eseguire similarity search con tre operatori di distanza, generare embeddings con Python, configurare indici HNSW e IVFFlat, e integrare tutto con LangChain. Hai anche visto i benchmark che confermano le performance di pgvector in produzione.

Nel prossimo articolo approfondiremo gli embeddings: come funzionano internamente, come scegliere il modello giusto, le strategie di chunking per documenti lunghi, e come ottimizzare la qualità della ricerca. Nel terzo articolo costruiremo una pipeline RAG completa con PostgreSQL come unico backend.

Risorse Aggiuntive

  • pgvector GitHub: github.com/pgvector/pgvector - Documentazione ufficiale e changelog
  • pgvectorscale: github.com/timescale/pgvectorscale - Estensione per performance avanzate
  • LangChain PGVector: python.langchain.com - Integrazione Python ufficiale
  • Supabase Vector: supabase.com/docs/guides/ai - Guida pgvector su Supabase
  • Neon AI: neon.tech/docs/ai - pgvector su Neon serverless

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti