Ottimizzare PostgreSQL per pgvector

Completato

I carichi di lavoro di ricerca vettoriale pongono richieste diverse in PostgreSQL rispetto alle query transazionali o analitiche tradizionali. Comprendere queste differenze consente di ottimizzare i parametri di configurazione per ottimizzare la latenza delle query, l'utilizzo della memoria e l'efficienza di calcolo per le applicazioni di intelligenza artificiale.

Annotazioni

Esempi di codice in questa unità illustrano i modelli di configurazione per PostgreSQL e pgvector. I valori dei parametri visualizzati sono punti di partenza per l'ottimizzazione. Le impostazioni ottimali dipendono dal carico di lavoro specifico, dalle dimensioni del set di dati e dall'hardware. Eseguire sempre il benchmark delle modifiche in un ambiente di test prima di applicarle all'ambiente di produzione.

Requisiti di calcolo e memoria pgvector

La ricerca di somiglianza vettoriale comporta distanze di calcolo tra un vettore di query e potenzialmente milioni di vettori archiviati. Questo modello di calcolo differisce fondamentalmente dalle operazioni di database tradizionali che filtrano le righe in base alle colonne indicizzate o alle tabelle di join sui valori chiave.

Quando si esegue una query di somiglianza vettoriale, pgvector deve calcolare la distanza tra il vettore di query e i vettori candidati. Per un incorporamento a 1536 dimensioni (comune con i modelli OpenAI), ogni calcolo della distanza comporta 1.536 operazioni a virgola mobile. La ricerca di un milione di vettori senza un indice richiede oltre 1,5 miliardi di operazioni a virgola mobile per ogni query. Le tre funzioni di distanza hanno costi di calcolo diversi che influiscono sulla scelta in base alle caratteristiche dei dati e ai requisiti di prestazioni.

  • Distanza L2 (euclidea): Usa l'operatore <-> e calcola la radice quadrata della somma delle differenze quadratiche. Questa è l'opzione più costosa in termini di risorse computazionali.
  • Distanza coseno: Usa l'operatore <=> e misura l'angolo tra vettori. Normalizza i vettori internamente, aggiungendo calcoli, ma fornendo una similitudine invariante alla scala.
  • Prodotto interno: usa l'operatore <#> e calcola il prodotto scalare. Si tratta dell'operazione più veloce, ma richiede vettori pre normalizzati per confronti significativi di somiglianza.

Per i motori di raccomandazione e la ricerca semantica, la distanza del coseno è spesso preferita perché gestisce in modo coerente i vettori di grandezza variabile. Se gli incorporamenti sono già normalizzati (molte API di incorporamento restituiscono vettori normalizzati), il prodotto interno fornisce risultati equivalenti con meno calcoli.

Le colonne vettoriali utilizzano un'archiviazione sostanziale rispetto ai tipi di dati tradizionali. Un singolo vettore 1536-dimensionale archiviato come float4 (precisione singola) richiede 6.144 byte, più overhead. Una tabella con un milione di incorporamenti di prodotti richiede circa 6 GB solo per la colonna vettoriale. Quando PostgreSQL elabora query vettoriali, carica i dati vettoriali in memoria. La relazione tra le dimensioni dei dati di memoria e vettore disponibili influisce direttamente sul fatto che le query possano essere eseguite in modo efficiente in memoria o che debbano essere lette ripetutamente dal disco.

Gli incorporamenti dimensionali superiori offrono una risoluzione semantica maggiore, ma aumentano i costi di archiviazione e calcolo in modo quadratico. Un vettore 3072-dimensionale (usato da alcuni modelli di incorporamento più recenti) richiede quattro volte il lavoro di calcolo della distanza e due volte l'archiviazione di un vettore dimensionale 1536. Quando si scelgono le dimensioni di incorporamento, prendere in considerazione i requisiti di accuratezza. Per molte applicazioni di raccomandazione e ricerca, 768 o 1.024 dimensioni forniscono una qualità sufficiente con un consumo significativamente inferiore delle risorse.

Configurazione della memoria per carichi di lavoro vettoriali

I parametri di memoria di PostgreSQL influiscono significativamente sulle prestazioni delle query vettoriali. L'ottimizzazione corretta garantisce che gli indici vettoriali e i dati a cui si accede di frequente rimangano in memoria, riducendo le operazioni su disco costose.

Il shared_buffers parametro controlla la cache di memoria condivisa di PostgreSQL, in cui si trovano le pagine di dati a cui si accede di frequente. Per i carichi di lavoro vettoriali, questa cache deve essere sufficientemente grande per contenere gli indici vettoriali e i dati ad accesso frequente. Una percentuale di riscontri nella cache inferiore al 99% per i carichi di lavoro pesanti sui vettori indica che il valore di shared_buffers potrebbe essere troppo piccolo. In Database di Azure per PostgreSQL questo parametro viene ottimizzato automaticamente in base al livello di calcolo, ma è possibile modificarlo all'interno dell'intervallo consentito per il livello. Per i carichi di lavoro di ricerca vettoriale dedicati, puntare a un valore di shared_buffers sufficientemente grande da contenere gli indici vettoriali più un margine per altri dati memorizzati nella cache. Un punto di partenza è 25% di memoria disponibile, con aumenti basati sul monitoraggio. Le query seguenti consentono di controllare le impostazioni correnti e le prestazioni della cache.

-- Check current setting
SHOW shared_buffers;

-- View buffer cache hit ratio
SELECT
    sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;

Il work_mem parametro controlla la memoria disponibile per singole operazioni di query, ad esempio ordinamenti e hash join. Le query di somiglianza vettoriale, in particolare quelle che combinano la ricerca vettoriale con il filtro e l'ordinamento, traggono vantaggio da un'adeguata work_mem. Il valore predefinito work_mem (in genere 4 MB) è spesso troppo piccolo per le operazioni vettoriali che devono ordinare i risultati in base alla somiglianza. È possibile aumentare questo valore per sessioni o query che eseguono ricerche vettoriali con set di risultati di grandi dimensioni usando SET work_mem = '256MB';. Prestare attenzione all'aumento globale di work_mem perché l'impostazione si applica a ogni operazione per connessione, quindi un server che gestisce 100 connessioni simultanee con query complesse potrebbe utilizzare 100 × work_mem × operazioni per query in memoria. Per i carichi di lavoro vettoriali, prendere in considerazione l'impostazione work_mem a livello di sessione per query specifiche anziché a livello globale.

Il effective_cache_size parametro indica a Query Planner la quantità di memoria disponibile per la memorizzazione nella cache, inclusa la cache dei file di shared_buffers PostgreSQL e del sistema operativo. Questa impostazione non alloca memoria, ma influenza se lo strumento di pianificazione sceglie l'analisi dell'indice sulle analisi sequenziali. Impostare effective_cache_size su circa 75% della memoria totale di sistema nei server di database dedicati. I valori più elevati incoraggiano lo strumento di pianificazione a usare gli indici, che in genere sono utili per la ricerca vettoriale. In Database di Azure per PostgreSQL questa configurazione viene configurata automaticamente in base al livello.

Query Planner di PostgreSQL prende decisioni su come eseguire query in base alle stime dei costi. Diversi parametri influiscono su queste stime e l'ottimizzazione per l'archiviazione SSD moderna migliora la pianificazione delle query vettoriali.

Il random_page_cost parametro stima il costo della lettura di una pagina del disco casuale rispetto a una pagina sequenziale. Il valore predefinito 4.0 riflette le caratteristiche del disco rotante in cui l'accesso casuale è significativamente più lento rispetto all'accesso sequenziale. Database di Azure per PostgreSQL usa l'archiviazione SSD in cui l'accesso casuale e sequenziale ha prestazioni simili. L'abbassamento random_page_cost a 1,1-1,5 incoraggia il pianificatore a utilizzare più facilmente le scansioni di indice, il che è vantaggioso per le ricerche vettoriali che accedono a pagine di dati disperse. È possibile modificare questa impostazione con SET random_page_cost = 1.1;.

Il effective_io_concurrency parametro indica a PostgreSQL il numero di operazioni di I/O simultanee che il sistema di archiviazione può gestire. I valori più elevati consentono alle scansioni heap bitmap di prelettura di più pagine in parallelo. L'archiviazione SSD gestisce bene l'I/O simultaneo, quindi impostare effective_io_concurrency su 200 per le istanze di Azure Database for PostgreSQL basate su SSD. Ciò migliora le prestazioni per le query che combinano la somiglianza del vettore con il filtro dei metadati.

I parallel_tuple_cost parametri e parallel_setup_cost controllano quando PostgreSQL usa l'esecuzione di query parallele. Le operazioni vettoriali possono trarre vantaggio dal parallelismo, soprattutto per le analisi sequenziali su tabelle di grandi dimensioni. I valori inferiori per parallel_tuple_cost (impostazione predefinita 0,1) e parallel_setup_cost (impostazione predefinita 1000) incoraggiano l'esecuzione parallela. Per i carichi di lavoro vettoriali con tabelle di grandi dimensioni, l'abilitazione del parallelismo può ridurre significativamente il tempo di query quando gli indici non vengono usati. È possibile controllare le impostazioni parallele correnti usando SHOW parallel_tuple_cost;, SHOW parallel_setup_cost;e SHOW max_parallel_workers_per_gather;.

Configurare parametri specifici di pgvector

L'estensione pgvector fornisce parametri di configurazione che controllano il compromesso accuratezza-velocità per le ricerche basate su indici. Questi parametri sono fondamentali per ottimizzare le prestazioni delle query vettoriali.

Quando si usano gli indici IVFFlat, il ivfflat.probes parametro controlla il numero di partizioni di indice (elenchi) in cui viene eseguita la ricerca di ogni query. Valori più elevati aumentano il ricordo (trovando più dei veri vicini più prossimi), ma rallentano le interrogazioni. Questo compromesso è fondamentale per l'ottimizzazione delle prestazioni di IVFFlat. Si bilancia il rischio di mancate corrispondenze valide rispetto al costo della ricerca di più partizioni. Il valore predefinito 1 cerca solo la singola partizione più promettente, che è veloce, ma potrebbe perdere i risultati rilevanti archiviati in partizioni adiacenti. Per i motori di raccomandazione dove la mancanza di una buona corrispondenza degrada l'esperienza dell'utente, si consiglia di iniziare con ivfflat.probes impostato su 5-10% del parametro lists e regolare in base al recall misurato.

-- Configure IVFFlat search depth
SET ivfflat.probes = 10;

-- Execute vector search
SELECT id, name, embedding <=> $1 AS distance
FROM products
ORDER BY embedding <=> $1
LIMIT 10;

Per gli indici HNSW, il hnsw.ef_search parametro controlla le dimensioni dell'elenco dei candidati dinamici durante la ricerca. Valori più grandi esplorano maggiormente il grafo, migliorando il richiamo a discapito della velocità. A differenza delle partizioni discrete di IVFFlat, la struttura del grafo di HNSW significa che questo parametro influisce sul modo in cui l'algoritmo esplora accuratamente le connessioni adiacenti prima di restituire i risultati. Il valore predefinito 40 offre un equilibrio ragionevole per molti carichi di lavoro. Per requisiti di precisione elevata, ad esempio per trovare le prime 10 corrispondenze, aumentare il valore fino a 100-200. Per le applicazioni critiche alla latenza in cui i risultati approssimativi sono accettabili, i valori fino a 20 possono funzionare. Configurare hnsw.ef_search con SET hnsw.ef_search = 100; prima di eseguire la ricerca vettoriale. Il valore ottimale dipende dai requisiti di accuratezza e dal budget di latenza. Eseguire il benchmark con query rappresentative per trovare il giusto equilibrio per l'applicazione.

Monitorare e misurare le prestazioni

L'ottimizzazione senza misurazione è un'approssimazione. Usare gli strumenti predefiniti di PostgreSQL e Monitoraggio di Azure per comprendere il comportamento delle query e convalidare le modifiche di configurazione.

Il EXPLAIN ANALYZE comando mostra come PostgreSQL esegue una query e fornisce informazioni di temporizzazione effettive. Per le query vettoriali, ciò indica se vengono usati gli indici e la posizione in cui viene impiegato il tempo. Comprendere il piano di esecuzione consente di identificare se le prestazioni scarse derivano da indici mancanti, impostazioni dei parametri non ottimali o problemi di distribuzione dei dati. Eseguire EXPLAIN ANALYZE prima della query vettoriale per visualizzare il piano di esecuzione. Cercare Analisi indice usando [index_name] (indica che è in uso l'indice vettoriale), Seq Scan (indica un'analisi sequenziale, che è lenta per le tabelle di grandi dimensioni), valori di tempo effettivi (mostra il tempo di esecuzione trascorso) e conteggi delle righe (consentono di identificare se il filtro funziona in modo efficiente). Se vengono visualizzate analisi sequenziali quando si prevede l'utilizzo dell'indice, verificare che l'operatore distance della query corrisponda alla classe di operatore dell'indice, ad esempio usando <=> con un indice creato usando vector_cosine_ops.

In alcuni casi PostgreSQL sceglie di non usare un indice disponibile. I motivi comuni per le query vettoriali includono query che restituiscono una grande parte della tabella (l'overhead dell'indice supera l'analisi sequenziale), statistiche obsolete dopo modifiche significative dei dati o un operatore distance che non corrisponde alla classe di operatore dell'indice. Eseguire ANALYZE products; per aggiornare le statistiche per una pianificazione accurata. È possibile controllare le informazioni sull'indice con SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'products';.

Azure Database per PostgreSQL espone le metriche tramite Azure Monitor che consentono di identificare i colli di bottiglia delle prestazioni. Monitorare la percentuale di CPU (un elevato utilizzo prolungato della CPU indica operazioni vettoriali associate al calcolo), la percentuale di memoria (il raggiungimento dei limiti suggerisce l'aumento del livello di calcolo o l'ottimizzazione delle query), la percentuale di I/O di archiviazione (i valori elevati indicano che i dati non rientrano nella cache) e le connessioni attive (l'avvicinamento dei limiti indica che il pool di connessioni potrebbe risultare utile). Configurare gli avvisi per queste metriche per rilevare la riduzione delle prestazioni prima che influisca sugli utenti.

Procedure migliori per l'ottimizzazione pgvector

L'ottimizzazione efficace segue un approccio sistematico anziché modifiche casuali ai parametri.

  • Stabilire prima le linee di base: Misurare la latenza delle query e l'utilizzo delle risorse prima di apportare modifiche. Senza linee di base, non è possibile determinare se le modifiche sono utili o dannose.
  • Modificare un parametro alla volta: Più modifiche simultanee rendono impossibile attribuire miglioramenti o regressioni a impostazioni specifiche.
  • Eseguire test con dati simili alla produzione: Le prestazioni delle query variano notevolmente con le dimensioni e la distribuzione dei dati. L'ottimizzazione su set di dati di test di piccole dimensioni genera spesso impostazioni che hanno esito negativo su larga scala.
  • Monitorare le regressioni: I parametri che migliorano la ricerca vettoriale potrebbero influire negativamente su altri carichi di lavoro. Monitorare l'integrità complessiva del sistema dopo le modifiche.
  • Documentare le impostazioni: Registrare ciò che è cambiato, perché e quale effetto ha avuto. Questa documentazione è estremamente utile per la risoluzione dei problemi futuri.

Risorse aggiuntive