Ottimizzare il rendimento delle query vettoriali

Questo documento illustra come ottimizzare gli indici per ottenere prestazioni di query più rapide e una migliore capacità di recupero.

Sintonizzare un indice ScaNN

L'indice ScaNN utilizza l'indicizzazione basata sulla quantizzazione ad albero. Nelle tecniche di quantizzazione ad albero, gli indici apprendono un albero di ricerca insieme a una funzione di quantizzazione (o hashing). Quando esegui una query, l'albero di ricerca viene utilizzato per potare lo spazio di ricerca, mentre la quantizzazione viene utilizzata per comprimere le dimensioni dell'indice. Questa potatura accelera il calcolo del punteggio della somiglianza (ovvero della distanza) tra il vettore della query e i vettori del database.

Per ottenere sia un numero elevato di query al secondo (QPS) che un'elevata percentuale di recall con le query del vicino più vicino, devi suddividere la struttura ad albero dell'indice ScaNN nel modo più appropriato per i tuoi dati e le tue query.

Prima di creare un indice ScaNN, completa quanto segue:

  • Assicurati che sia già stata creata una tabella con i tuoi dati.
  • Assicurati che il valore impostato per i flag maintenance_work_mem e shared_buffers sia inferiore alla memoria totale della macchina per evitare problemi durante la generazione dell'indice.

Parametri di correzione

I seguenti parametri dell'indice e i flag del database vengono utilizzati insieme per trovare il giusto equilibrio tra il richiamo e la QPS. Tutti i parametri si applicano a entrambi i tipi di indici ScaNN.

Parametro di ottimizzazione Descrizione Tipo di parametro
num_leaves Il numero di partizioni da applicare a questo indice. Il numero di partizioni a cui applichi un indice durante la creazione influisce sulle prestazioni dell'indice. Aumentando le partizioni per un numero predefinito di vettori, crei un indice più granulare, che migliora il recupero e le prestazioni delle query. Tuttavia, questo comporta tempi di creazione dell'indice più lunghi.

Poiché gli alberi a tre livelli vengono creati più velocemente di quelli a due livelli, puoi aumentare il valore num_leaves_value quando crei un indice ad albero a tre livelli per ottenere prestazioni migliori.
  • Indice a due livelli: imposta questo valore su un valore compreso tra 1 e 1048576.

    Se hai dubbi sulla selezione del valore esatto, utilizza sqrt(ROWS) come punto di partenza, dove ROWS è il numero di righe del vettore. Il numero di vettori contenuti in ogni partizione viene calcolato da
    ROWS/sqrt(ROWS) = sqrt(ROWS).

    Poiché è possibile creare un indice ad albero a due livelli in un set di dati con meno di 10 milioni di righe di vettori, ogni partizione conterrà meno di (sqrt(10M)) vettori, ovvero 3200 vettori. Per prestazioni ottimali, è consigliabile ridurre al minimo il numero di vettori in ogni partizione.
  • Indice a tre livelli: imposta questo valore su un valore compreso tra 1 e 1048576.

    Se hai dubbi sulla selezione del valore esatto, utilizza power(ROWS, 2/3) come punto di partenza, dove ROWS è il numero di righe del vettore. Il numero di vettori contenuti in ogni partizione viene calcolato da
    ROWS/power(ROWS, 2/3) = power(ROWS, 1/3).

    Poiché è possibile creare un indice ad albero a tre livelli in un set di dati con più di 100 milioni di righe di vettori, ogni partizione conterrà più di
    (power(100M, 1/3)) vettori, ovvero 465 vettori. Per prestazioni ottimali, è consigliabile ridurre al minimo il numero di vettori in ogni partizione.
Creazione di indici
quantizer Il tipo di quantizzatore da utilizzare per l'albero K-means. Il valore predefinito è SQ8 per migliorare le prestazioni delle query.

Impostalo su FLAT per migliorare il recupero.
Creazione di indici
enable_pca Attiva l'analisi delle componenti principali (PCA), una tecnica di riduzione delle dimensioni utilizzata per ridurre automaticamente le dimensioni dell'embedding, se possibile. Questa opzione è attiva per impostazione predefinita.

Imposta su false se noti un peggioramento del recupero.
Creazione di indici
scann.num_leaves_to_search Il flag del database controlla il compromesso tra il richiamo e le QPS. Il valore predefinito è l'1% del valore impostato in num_leaves.

Maggiore è il valore impostato, migliore è il richiamo, ma il QPS è inferiore e viceversa.
Tempo di esecuzione della query
scann.max_top_neighbors_buffer_size Il flag del database specifica le dimensioni della cache utilizzata per migliorare le prestazioni delle query filtrate assegnando un punteggio o un ranking ai candidati vicini sottoposti a scansione in memoria anziché sul disco. Il valore predefinito è 20000.

Maggiore è il valore impostato, migliore è il QPS nelle query filtrate, ma aumenta l'utilizzo della memoria e viceversa.
Tempo di esecuzione della query
scann.pre_reordering_num_neighbors Se impostato, il flag del database specifica il numero di vicini candidati da considerare durante le fasi di riordinamento dopo che la ricerca iniziale ha identificato un insieme di candidati. Imposta questo valore su un valore superiore al numero di vicini che vuoi che la query restituisca.

Gli insiemi di valori più elevati generano un richiamo migliore, ma questo approccio comporta un QPS inferiore.
Tempo di esecuzione della query
max_num_levels Il numero massimo di livelli dell'albero di clustering K-means.
  • Indice ad albero a due livelli: impostato per impostazione predefinita per la quantizzazione basata su albero a due livelli.
  • Indice ad albero a tre livelli: impostato su 2 in modo esplicito per la quantizzazione basata su albero a tre livelli.
Creazione di indici

Sintonizzare un indice ScaNN

Considera i seguenti esempi di indici ScaNN a due e tre livelli che mostrano come vengono impostati i parametri di ottimizzazione:

Indice a due livelli

SET LOCAL scann.num_leaves_to_search = 1;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 1/2)]);

Indice a tre livelli

SET LOCAL scann.num_leaves_to_search = 10;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 2/3)], max_num_levels = 2);

Qualsiasi operazione di inserimento o aggiornamento in una tabella in cui è già stato generato un indice ScaNN influisce sul modo in cui l'albero appreso ottimizza l'indice. Se la tabella è soggetta a aggiornamenti o inserimenti frequenti, ti consigliamo di eseguire periodicamente la reindicizzazione dell'indice ScaNN esistente per migliorare l'accuratezza del recupero.

Puoi monitorare le metriche dell'indice per determinare il numero di mutazioni create dall'inizializzazione dell'indice e poi eseguire nuovamente l'indicizzazione di conseguenza. Per maggiori informazioni sulle metriche, consulta Metriche dell'indice vettoriale.

Best practice per la messa a punto

A seconda del tipo di indice ScaNN che prevedi di utilizzare, i consigli per l'ottimizzazione dell'indice variano. Questa sezione fornisce consigli su come ottimizzare i parametri dell'indice per un equilibrio ottimale tra il recupero e le QPS.

Indice ad albero a due livelli

Per applicare i consigli che ti aiutano a trovare i valori ottimali di num_leaves e num_leaves_to_search per il tuo set di dati:

  1. Crea l'indice ScaNN con num_leaves impostato sulla radice quadrata del conteggio delle righe della tabella indicizzata.
  2. Esegui le query di test, aumentando il valore di scann.num_of_leaves_to_search, fino a raggiungere l'intervallo di recupero target, ad esempio il 95%. Per ulteriori informazioni sull'analisi delle query, consulta Analizzare le query.
  3. Prendi nota del rapporto tra scann.num_leaves_to_search e num_leaves che verrà utilizzato nei passaggi successivi. Questo rapporto fornisce un'approssimazione del set di dati che ti aiuterà a raggiungere il recupero target.

    Se utilizzi vettori di dimensioni elevate (500 dimensioni o più) e vuoi migliorare il recupero, prova a ottimizzare il valore di scann.pre_reordering_num_neighbors. Come punto di partenza, imposta il valore su 100 * sqrt(K), dove K è il limite impostato nella query.
  4. Se il QPS è troppo basso dopo che le query hanno raggiunto un recupero target, segui questi passaggi:
    1. Ricrea l'indice aumentando il valore di num_leaves e scann.num_leaves_to_search in base alle seguenti indicazioni:
      • Imposta num_leaves su un fattore maggiore della radice quadrata del numero di righe. Ad esempio, se nell'indice num_leaves è impostato sulla radice quadrata del numero di righe, prova a impostarlo sul doppio della radice quadrata. Se il valore è già raddoppiato, prova a impostarlo sul triplo della radice quadrata.
      • Aumenta scann.num_leaves_to_search in base alle esigenze per mantenere il rapporto con num_leaves, che hai annotato nel passaggio 3.
      • Imposta num_leaves su un valore minore o uguale al numero di righe diviso per 100.
    2. Esegui di nuovo le query di test. Mentre esegui le query di test, prova a ridurre scann.num_leaves_to_search, trovando un valore che aumenti il QPS mantenendo alto il recupero. Prova valori diversi per scann.num_leaves_to_search senza ricostruire l'indice.
  5. Ripeti il passaggio 4 finché sia la QPS sia l'intervallo di recupero non hanno raggiunto valori accettabili.

Indice ad albero a tre livelli

Oltre ai consigli per l'indice ad albero a due livelli ScaNN, segui le indicazioni e i passaggi riportati di seguito per ottimizzare l'indice:

  • L'aumento del valore max_num_levels da 1 per un albero a due livelli a 2 per un albero a tre livelli riduce notevolmente il tempo necessario per creare un indice, ma a spese dell'accuratezza del richiamo. Imposta max_num_levels utilizzando il seguente consiglio:
    • Imposta il valore su 2 se il numero di righe del vettore supera i 100 milioni.
    • Imposta il valore su 1 se il numero di righe del vettore è inferiore a 10 milioni.
    • Imposta 1 o 2 se il numero di righe del vettore è compreso tra 10 milioni e 100 milioni, in base al bilanciamento del tempo di creazione dell'indice e alla precisione del recupero necessaria.

Per applicare i consigli per trovare il valore ottimale dei parametri di indice num_leaves e max_num_levels:

  1. Crea l'indice ScaNN con le seguenti combinazioni di num_leaves e max_num_levels in base al tuo set di dati:

    • Vettori di righe superiori a 100 milioni di righe: imposta max_num_levels su 2 e num_leaves su power(rows, ⅔).
    • Righe di vettori inferiori a 100 milioni: imposta max_num_levels su 1 e num_leaves su sqrt(rows).
    • Vettori di righe compresi tra 10 e 100 milioni di righe: inizia impostando max_num_levels come 1 e num_leaves come sqrt(rows).
  2. Esegui le query di test. Per ulteriori informazioni sull'analisi delle query, consulta Analizzare le query.

    Se il tempo di creazione dell'indice è soddisfacente, mantieni il valore max_num_levels e prova il valore num_leaves per un'accuratezza del recupero ottimale.

  3. Se il tempo di creazione dell'indice non ti soddisfa, procedi nel seguente modo:

    • Se il valore max_num_levels è 1, elimina l'indice. Ricostruisci l'indice con il valore max_num_levels impostato su 2.

      Esegui le query e ottimizza il valore num_leaves per un'accuratezza del recupero ottimale.

    • Se il valore max_num_levels è 2, elimina l'indice. Ricostruisci l'indice con lo stesso valore max_num_levels e ottimizza il valore num_leaves per una precisione di recupero ottimale.

Ottimizzare un indice IVF

La regolazione dei valori impostati per i parametri lists, ivf.probes e quantizer potrebbe contribuire a ottimizzare il rendimento della tua applicazione:

Parametro di ottimizzazione Descrizione Tipo di parametro
lists Il numero di elenchi creati durante la creazione dell'indice. Il punto di partenza per l'impostazione di questo valore è (rows)/1000 per un massimo di un milione di righe e sqrt(rows) per più di un milione di righe. Creazione di indici
quantizer Il tipo di quantizzatore da utilizzare per l'albero K-means. Il valore predefinito è SQ8 per un rendimento migliore delle query. Impostala su FLAT per un ricordo migliore. Creazione di indici
ivf.probes il numero di elenchi più vicini da esplorare durante la ricerca. Il punto di partenza per questo valore è
sqrt(lists).
Tempo di esecuzione della query

Prendi in considerazione l'esempio seguente che mostra un indice IVF con i parametri di ottimizzazione impostati:

SET LOCAL ivf.probes = 10;

CREATE INDEX my-ivf-index ON my-table
  USING ivf (vector_column cosine)
  WITH (lists = 100, quantizer = 'SQ8');

Ottimizzare un indice IVFFlat

La regolazione dei valori impostati per i parametri lists e ivfflat.probes può contribuire a ottimizzare il rendimento dell'applicazione:

Parametro di ottimizzazione Descrizione Tipo di parametro
lists Il numero di elenchi creati durante la creazione dell'indice. Il punto di partenza per l'impostazione di questo valore è (rows)/1000 per un massimo di un milione di righe e sqrt(rows) per più di un milione di righe. Creazione di indici
ivfflat.probes Il numero di elenchi più vicini da esplorare durante la ricerca. Il punto di partenza per questo valore è
sqrt(lists).
Tempo di esecuzione della query

Prima di creare un indice IVFFlat, assicurati che il flag max_parallel_maintenance_workers del database sia impostato su un valore sufficiente per velocizzare la creazione dell'indice in tabelle di grandi dimensioni.

Prendi in considerazione l'esempio seguente che mostra un indice IVFFlat con i parametri di ottimizzazione impostati:

SET LOCAL ivfflat.probes = 10;

CREATE INDEX my-ivfflat-index ON my-table
  USING ivfflat (vector_column cosine)
  WITH (lists = 100);

Ottimizzare un indice HNSW

La regolazione dei valori impostati per i parametri m, ef_construction e hnsw.ef_search può contribuire a ottimizzare il rendimento dell'applicazione.

Parametro di ottimizzazione Descrizione Tipo di parametro
m Il numero massimo di connessioni da un nodo del grafo. Puoi iniziare con il valore predefinito 16(predefinito) e provare valori più elevati in base alle dimensioni del set di dati. Creazione di indici
ef_construction La dimensione dell'elenco di candidati dinamici mantenuto durante la costruzione del grafo, che aggiorna costantemente i candidati migliori attuali per i vicini più vicini di un nodo. Imposta questo valore su un valore superiore al doppio del valore m, ad esempio 64(valore predefinito). Creazione di indici
ef_search Le dimensioni dell'elenco di candidati dinamici utilizzato durante la ricerca. Puoi iniziare impostando questo valore su m o ef_construction, per poi modificarlo mentre osservi il recupero. Il valore predefinito è 40. Tempo di esecuzione della query

Prendi in considerazione l'esempio seguente che mostra un indice hnsw con i parametri di ottimizzazione impostati:

SET LOCAL hnsw.ef_search = 40;

CREATE INDEX my-hnsw-index ON my-table
  USING hnsw (vector_column cosine)
  WITH (m = 16, ef_construction = 200);

Analizza le query

Utilizza il comando EXPLAIN ANALYZE per analizzare gli approfondimenti sulle query, come mostrato nell'esempio di query SQL seguente.

  EXPLAIN ANALYZE SELECT result-column FROM my-table
    ORDER BY EMBEDDING_COLUMN ::vector
    USING INDEX my-scann-index
    <-> embedding('textembedding-gecko@003', 'What is a database?')
    LIMIT 1;

La risposta di esempio QUERY PLAN include informazioni quali il tempo impiegato, il numero di righe analizzate o restituite e le risorse utilizzate.

Limit  (cost=0.42..15.27 rows=1 width=32) (actual time=0.106..0.132 rows=1 loops=1)
  ->  Index Scan using my-scann-index on my-table  (cost=0.42..858027.93 rows=100000 width=32) (actual time=0.105..0.129 rows=1 loops=1)
        Order By: (embedding_column <-> embedding('textgecko@003', 'What is a database?')::vector(768))
        Limit value: 1
Planning Time: 0.354 ms
Execution Time: 0.141 ms

Visualizzare le metriche dell'indice di vettori

Puoi utilizzare le metriche dell'indice di vettori per esaminare il rendimento dell'indice, identificare le aree di miglioramento e, se necessario, ottimizzare l'indice in base alle metriche.

Per visualizzare tutte le metriche dell'indice vettoriale, esegui la seguente query SQL, che utilizza la vista pg_stat_ann_indexes:

SELECT * FROM pg_stat_ann_indexes;

Per ulteriori informazioni sull'elenco completo delle metriche, consulta Metriche dell'indice vettoriale.

Passaggi successivi