Approfondimenti tecnici
Database vettoriali e ricerca per embedding
Un’analisi approfondita dei database vettoriali, degli spazi degli embedding e degli algoritmi di ricerca per similarità, e di come alimentano le moderne applicazioni di IA, dalla RAG ai sistemi di raccomandazione.

AI.info Team ·
Perché la ricerca dei vicini più prossimi ha bisogno di un database dedicato
Un embedding è un vettore prodotto da un modello in modo che la similarità semantica si traduca in prossimità geometrica. Due frasi dal significato simile finiscono vicine nello spazio vettoriale; due frasi non correlate finiscono lontane. Quando si hanno milioni di questi vettori, l’unica operazione utile è questa: dato un vettore di query, trovare i suoi vicini più prossimi in base a una funzione di distanza. Sembra un compito da indice e, per dataset piccoli, una scansione esaustiva funziona bene: si confronta la query con ogni vettore memorizzato e si ordinano i risultati.
Il problema è che la ricerca esatta dei vicini più prossimi cresce linearmente sia con il numero dei vettori sia con il numero delle dimensioni, e i moderni modelli di embedding producono vettori con centinaia o migliaia di dimensioni. text-embedding-3-small di OpenAI usa per impostazione predefinita 1536 dimensioni, mentre text-embedding-3-large ne usa 3072 (entrambi i modelli consentono di ridurle tramite un parametro dimensions, sacrificando un po’ di qualità del recupero in cambio di un minore spazio di archiviazione).
Con decine di milioni di vettori, confrontare la query con ogni candidato non è più fattibile se si vogliono tempi di risposta adatti a un uso interattivo. È il problema che i database vettoriali e le librerie per la ricerca approssimata dei vicini più prossimi (ANN) sono nati per risolvere: restituire in pochi millisecondi vicini quasi certamente corretti, senza esaminare ogni vettore.
Il termine «approssimata» in ANN indica il compromesso progettuale fondamentale dell’intero settore. Ogni struttura di indicizzazione descritta qui sotto guadagna velocità accettando una certa probabilità di non trovare il vero vicino più prossimo. La misura di quanto spesso ci riesce è la recall@k: la frazione dei veri k vicini più prossimi che l’indice restituisce effettivamente. Ogni sistema pratico offre un parametro per bilanciare la recall con la latenza e l’uso della memoria. Capire come funziona questo parametro, e dove interviene in ciascun algoritmo, è gran parte di ciò che serve per usare bene questi sistemi.
Metriche di distanza: similarità coseno, prodotto scalare e distanza euclidea
Prima di creare un indice, serve una funzione di similarità. Tre sono le più diffuse:
- La similarità coseno misura l’angolo tra due vettori, ignorandone la magnitudine. È la scelta predefinita per gli embedding testuali perché tiene conto della direzione (il contenuto semantico) anziché della lunghezza del vettore.
- Il prodotto scalare tiene conto sia della direzione sia della magnitudine. Richiede meno calcoli della similarità coseno perché salta il passaggio di normalizzazione.
- La distanza euclidea (L2) misura la distanza in linea retta nello spazio vettoriale.
Non si tratta di tre opzioni arbitrarie: quando i vettori sono normalizzati a lunghezza unitaria, producono lo stesso risultato. Gli embedding di OpenAI sono normalizzati in questo modo. Per questo la documentazione osserva che similarità coseno, prodotto scalare e distanza euclidea producono ordinamenti identici sui risultati dei suoi modelli; il prodotto scalare è semplicemente il più rapido da calcolare, quindi è la scelta predefinita più pratica. Se si usa un modello che non normalizza i propri risultati, le tre metriche possono dare ordinamenti diversi e scegliere quella sbagliata peggiora in modo misurabile la qualità del recupero.
È un dettaglio da verificare per ogni modello di embedding prima di scegliere la metrica dell’indice, perché sbagliare produce un malfunzionamento silenzioso: la ricerca continua a restituire risultati, ma di qualità inferiore.
HNSW: orientarsi in un grafo small-world
L’algoritmo ANN dominante negli attuali database vettoriali in produzione è HNSW — Hierarchical Navigable Small World graphs — introdotto da Yury Malkov e Dmitry Yashunin. L’articolo di riferimento è stato pubblicato su IEEE Transactions on Pattern Analysis and Machine Intelligence (vol. 42, fascicolo 4, 2018), dopo una precedente versione preliminare su arXiv nel 2016. HNSW è alla base di IndexHNSWFlat di FAISS e del tipo di indice hnsw di pgvector; è inoltre l’indice predefinito o principale in Weaviate, Qdrant e Milvus.
L’idea di fondo è un grafo di prossimità a più livelli. Ogni vettore è un nodo, collegato da archi ai nodi vicini. Ma anziché costruire un unico grafo piatto, HNSW crea una gerarchia di grafi sovrapposti. Il livello 0, alla base, contiene tutti i vettori.
Ogni livello superiore contiene un sottoinsieme dei vettori del livello sottostante, selezionato casualmente e via via più piccolo secondo un andamento esponenziale. Il livello massimo raggiunto da un vettore è scelto casualmente secondo una distribuzione di probabilità a decadimento esponenziale: la maggior parte dei vettori esiste solo nel livello 0, alcuni arrivano al livello 1, ancora meno al livello 2 e così via. La struttura ricorda una skip list: scorciatoie rade a lungo raggio in alto e connessioni locali fitte alla base.
La ricerca parte da un punto d’ingresso nel livello più alto e procede in modo greedy verso il vettore della query, passando di volta in volta al vicino più prossimo, finché non è più possibile avvicinarsi all’interno di quel livello. Scende quindi di un livello e ripete il percorso greedy, usando come nuovo punto di partenza l’ottimo locale trovato nel livello precedente. Il processo continua fino al livello 0, dove una ricerca best-first più ampia su un insieme di candidati produce i top-k finali.
Poiché i livelli superiori funzionano da tabelle di instradamento approssimative, che portano grosso modo nella zona giusta in pochi passaggi, mentre quelli inferiori affinano la ricerca localmente, la complessità della ricerca cresce in modo logaritmico rispetto al numero di vettori memorizzati. Questa proprietà consente a HNSW di restare veloce anche con dataset ben più grandi di quelli su cui una ricerca ingenua nel grafo perde efficienza.
Due parametri impostati durante la costruzione regolano il compromesso tra recall e risorse: M, il numero di archi mantenuti per nodo (un valore più alto comporta connessioni più fitte, una recall migliore e un maggiore uso di memoria), ed efConstruction, che determina quanto sia approfondita la ricerca durante l’inserimento di nuovi nodi e, di conseguenza, quanto sia buona la struttura del grafo risultante. Al momento della query, efSearch determina l’ampiezza della lista di candidati mantenuta durante la ricerca nel livello 0: aumentandolo, crescono sia la recall sia la latenza.
È la leva di regolazione più importante in tutti i sistemi basati su HNSW: ef/efSearch è la manopola con cui si regola il compromesso tra recall e latenza per ogni query, mentre M ed efConstruction regolano il compromesso tra recall, memoria e tempo di costruzione e vengono impostati una volta sola.
I punti deboli di HNSW sono il rovescio dei suoi punti di forza: la struttura del grafo, con le liste di archi associate a ciascun nodo, richiede molta memoria (senza compressione, un consumo nell’ordine di alcune volte la dimensione dei vettori grezzi). Inoltre, poiché il grafo viene costruito incrementalmente, le eliminazioni sono difficili da gestire: la maggior parte delle implementazioni contrassegna i nodi come eliminati anziché rimuoverli davvero, e quando i dati cambiano spesso il grafo trae beneficio da ricostruzioni periodiche.
IVF e quantizzazione di prodotto: l’approccio di FAISS
FAISS (Facebook AI Similarity Search), la libreria sviluppata dal gruppo di ricerca sull’IA di Meta, ha reso popolare una diversa famiglia di indici: gli indici a file invertito (IVF). IVF suddivide innanzitutto lo spazio dei vettori in partizioni usando k-means: concettualmente, sono simili a celle di Voronoi, ciascuna con un centroide. In fase di indicizzazione, ogni vettore viene assegnato alla lista del centroide più vicino («file invertito» richiama la stessa struttura usata nella ricerca testuale, dove un termine rimanda a una lista di documenti; qui un centroide rimanda a una lista di vettori).
Al momento della query, invece di esaminare tutto, IVF calcola la distanza da ciascun centroide, seleziona le nprobe celle più vicine ed esamina soltanto i vettori al loro interno. Qui nprobe è il parametro che regola il compromesso tra recall e latenza: esaminare una sola cella è veloce, ma rischia di non trovare vicini finiti in una cella adiacente, vicino al confine; esaminarne molte riporta il recall verso quello della ricerca esaustiva, a scapito della velocità.
IVF, da solo, controlla quanta parte del dataset viene esaminata. Non dice nulla sullo spazio occupato da ogni vettore né sulla velocità del singolo calcolo della distanza: è qui che entra in gioco la quantizzazione di prodotto (PQ). PQ divide ogni vettore ad alta dimensionalità in sottovettori e quantizza separatamente ciascuno di essi, associandolo al più vicino tra un piccolo insieme di centroidi appresi (in genere 256 per sottospazio, così ogni sottovettore può essere rappresentato con un solo byte).
Un vettore float32 di 1536 dimensioni, che occupa 6 KB nella forma originale, può essere compresso in un codice PQ di ben meno di 100 byte: una compressione con perdita che sostituisce il calcolo esatto della distanza con codici compatti e una rapida ricerca della distanza approssimata tramite tabelle precalcolate. Combinando i due metodi, IVFPQ è lo strumento principale di FAISS per la ricerca su scala di miliardi di vettori: il clustering restringe l’insieme dei candidati, poi la quantizzazione rende poco costoso esaminarli.
Su una sola GPU, questa pipeline può restituire risultati top-k in microsecondi per query. È per questo che FAISS resta l’implementazione di riferimento citata in quasi tutti gli articoli scientifici sull’infrastruttura per il recupero di dati su larga scala, anche quando i sistemi in produzione la incorporano in un indice diverso o distribuiscono una propria reimplementazione.
Da allora, l’idea della quantizzazione è stata estesa ben oltre la PQ originale di FAISS. Qdrant implementa la quantizzazione scalare, che converte i componenti float32 in int8, riducendo la memoria di circa il 75% e mantenendo l’errore di ricerca in genere sotto l’1%. Come vantaggio aggiuntivo, consente al ciclo di ricerca di usare istruzioni SIMD progettate per interi a 8 bit: oltre a occupare meno spazio, la ricerca diventa più veloce. Qdrant implementa anche la quantizzazione binaria, che comprime ogni componente del vettore in un solo bit: una riduzione della memoria di 32 volte che, nei benchmark, può rendere la ricerca circa 40 volte più veloce, a scapito dell’accuratezza.
La quantizzazione binaria funziona bene solo con embedding ad alta dimensionalità (all’incirca 1024 dimensioni o più); con embedding più piccoli le prestazioni peggiorano nettamente, perché il vettore non contiene abbastanza informazioni ridondanti da resistere alla riduzione dei suoi componenti a singoli bit. Gli schemi di quantizzazione possono seguire lo stesso schema operativo: i vettori quantizzati vengono usati per restringere a basso costo l’insieme dei candidati, poi i vettori originali a precisione piena possono essere usati per ricalcolare i punteggi dei candidati rimasti. Si ottiene così gran parte del risparmio di memoria e del guadagno di velocità senza subire per intero la perdita di accuratezza.
| Tecnica | Compressione | Effetto |
|---|---|---|
| Quantizzazione di prodotto (PQ) | 6 KB → ben meno di 100 byte (vettore di 1536 dimensioni) | con perdita, rapido calcolo della distanza approssimata |
| Quantizzazione scalare (int8) | riduzione della memoria di ~75% | errore di ricerca in genere sotto l’1% |
| Quantizzazione binaria | riduzione della memoria di 32 volte | ricerca fino a 40 volte più veloce, a scapito dell’accuratezza; richiede ~1024 dimensioni o più |
Scegliere un indice e un sistema di archiviazione
La decisione pratica non è «HNSW o IVF» in astratto: dipende dai compromessi richiesti dal proprio carico di lavoro.
- Recall, latenza e memoria formano un triangolo, non una linea. HNSW tende a prevalere nel rapporto tra recall e latenza (offre il maggior numero di query al secondo a parità di obiettivo di recall, come si vede in particolare nelle classifiche pubbliche come ann-benchmarks.com, su dataset quali GloVe e SIFT), a costo di un maggiore uso di memoria e di una costruzione degli indici più lenta e più difficile da parallelizzare. Gli indici della famiglia IVF si costruiscono più rapidamente e usano meno memoria, con una certa perdita di recall che di solito si può recuperare aumentando
nprobe. - Molte scritture o molte letture. Se i vettori vengono inseriti continuamente e solo di rado gli indici vengono ricostruiti in blocco, la rapidità degli inserimenti incrementali e una minore necessità di ricostruire l’indice contano più della capacità di elaborare query. La costruzione più rapida e meno costosa degli indici IVFFlat li rende più adatti a questo scenario rispetto alla costruzione dei grafi HNSW, più lenta e più esigente in termini di memoria; pgvector documenta esplicitamente questo compromesso tra i suoi due tipi di indice.
- Filtraggio. Le applicazioni reali raramente cercano «i vicini più prossimi nell’intero dataset»: cercano i vicini più prossimi tra i documenti di proprietà di questo utente o all’interno di questo intervallo di date. Gli indici puramente vettoriali gestiscono male la ricerca filtrata se il filtro viene applicato dopo che la ricerca ANN ha restituito un insieme di candidati (un filtro molto selettivo può lasciare pochissimi risultati, anche se altrove nell’indice esistono corrispondenze migliori). I sistemi affrontano il problema in modi diversi: alcuni applicano il filtro durante la traversata del grafo, altri mantengono sottoindici filtrati e altri ancora ricorrono a una ricerca non filtrata più ampia, seguita dal filtraggio. È una domanda da porre esplicitamente quando si valuta un sistema di archiviazione, perché la risposta incide sensibilmente sia sul recall sia sulla latenza con i filtri usati nelle applicazioni reali.
- La ricerca ibrida — che combina la similarità tra vettori densi con segnali lessicali sparsi come BM25 — è importante perché gli embedding funzionano male nei casi che richiedono una corrispondenza esatta (codici SKU dei prodotti, acronimi, nomi propri rari), per i quali un indice per parole chiave dà facilmente la risposta corretta. Weaviate offre nativamente la fusione di BM25 e ricerca vettoriale come funzionalità fondamentale, anziché come aggiunta successiva: un motivo significativo per sceglierlo quando il recupero ibrido è un requisito irrinunciabile e non una semplice funzionalità desiderabile.
- Modello operativo. Pinecone è un database vettoriale serverless interamente gestito, progettato per arrivare a miliardi di vettori con una latenza inferiore a 100 ms senza che il team debba gestire alcuna infrastruttura: si rinuncia al controllo e alla possibilità di ospitarlo sui propri server in cambio di una maggiore semplicità operativa. Milvus è progettato per ricerche su miliardi di vettori, con diversi tipi di indice intercambiabili e supporto multimodale, ma è di conseguenza più impegnativo da gestire in proprio. Qdrant, scritto in Rust, ha registrato alcune delle latenze p50 più basse tra i database vettoriali specializzati nei benchmark indipendenti (nell’ordine di pochi millisecondi). Chroma è volutamente leggero e facile da usare per gli sviluppatori: dispone di un motore per un singolo nodo e, dall’agosto 2025, di Chroma Cloud, una soluzione ospitata e adatta all’uso in produzione, riprogettata come sistema distribuito per poter scalare. pgvector è l’opzione per i team che usano già PostgreSQL e vogliono aggiungere la ricerca vettoriale come un ulteriore tipo di indice nel database che gestiscono comunque, evitando del tutto un secondo sistema: i suoi indici IVFFlat e HNSW rispecchiano direttamente i compromessi descritti sopra, e la versione 0.8.0 ha introdotto per entrambi la scansione iterativa, che consente a una query di continuare ad ampliare la ricerca quando un filtro restrittivo la lascerebbe altrimenti senza risultati.
Non esiste una scelta giusta in assoluto. Un team che usa la generazione aumentata dal recupero su alcune centinaia di migliaia di documenti interni, con un volume moderato di scritture e un database Postgres già in produzione, trae più vantaggio da pgvector che dall’attivazione di un nuovo servizio gestito. Un team che sviluppa un sistema di ricerca per i consumatori su centinaia di milioni di embedding di prodotti, con picchi di traffico imprevedibili, ha ragioni molto più solide per scegliere un sistema gestito e serverless progettato per quella scala. Un team per cui la ricerca ibrida, per parole chiave e vettori, è un requisito fondamentale e non un’aggiunta secondaria dovrebbe attribuire a questa capacità tanto peso quanto ai risultati grezzi dei benchmark ANN.
Scalare oltre un singolo nodo
A una scala sufficientemente grande, l’indice stesso smette di essere l’unico problema: lo diventa anche la distribuzione. Suddividere un indice vettoriale in shard significa ripartire i vettori tra più nodi (di solito tramite hashing o una fase preliminare di clustering simile a quella dei centroidi di IVF) e poi interrogare tutti gli shard e unire i risultati (scatter-gather), oppure indirizzare le query allo shard in cui è più probabile che si trovi la risposta.
Lo scatter-gather preserva meglio il recall, ma moltiplica il costo della query in funzione del numero di shard interrogati; l’instradamento costa meno, ma reintroduce il rischio di perdere un vero vicino finito nello shard «sbagliato». È lo stesso compromesso tra recall e costo che si presenta all’interno di un singolo indice IVF, spostato soltanto a un livello di astrazione superiore.
La replica per aumentare la capacità di gestire le letture è relativamente semplice: una volta costruiti, gli indici vettoriali servono soprattutto a rispondere a query e si prestano facilmente alla parallelizzazione. Il vero lavoro ingegneristico nei database vettoriali distribuiti, come Milvus e la modalità cluster di Weaviate, sta però nel mantenere coerenti le repliche in presenza di scritture continue senza bloccare le query. E poiché ricostruire da zero un grafo HNSW è costoso su larga scala, i sistemi di produzione privilegiano sempre più spesso strutture di indice incrementali o un’architettura a due livelli: un buffer veloce e modificabile per le scritture recenti, su cui si effettua una ricerca esaustiva, i cui dati vengono periodicamente compattati nell’indice principale, immutabile e quantizzato. È lo stesso schema log-structured merge comune nei database ottimizzati per le scritture, applicato ai vettori anziché alle righe.
Il compromesso ricorrente
Ogni livello di questa architettura — la scelta della metrica, il parametro ef di HNSW, il parametro nprobe di IVF, la dimensione del codice di PQ, il numero di bit per dimensione della quantizzazione binaria — è la stessa leva sotto un nome diverso: a quanta esaustività si è disposti a rinunciare per guadagnare velocità e risparmiare memoria, e quanto errore può tollerare l’applicazione a valle. Un sistema di raccomandazione che riordina una lista ristretta può tollerare più approssimazione di un sistema di ricerca di documenti legali, in cui non trovare una corrispondenza è un vero errore.
Fare bene la ricerca vettoriale significa meno scegliere l’algoritmo «migliore» e più capire dove si colloca il proprio carico di lavoro rispetto a questo compromesso, scegliendo un indice e un sistema di archiviazione che permettano di regolarlo esplicitamente, anziché scoprirlo per caso in produzione.