Ottimizzare le prestazioni delle query vettoriali

Seleziona una versione della documentazione:

Questo documento mostra come ottimizzare gli indici per ottenere prestazioni delle query più veloci e un richiamo migliore.

Ottimizzare 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 ridurre lo spazio di ricerca, mentre la quantizzazione viene utilizzata per comprimere le dimensioni dell'indice. Questa riduzione velocizza il calcolo della somiglianza (ovvero della distanza) tra il vettore della query e i vettori del database.

Per ottenere sia una frequenza di query al secondo (QPS) elevata sia un richiamo elevato con le query sui vicini più prossimi, devi partizionare l'albero dell'indice ScaNN nel modo più appropriato per i tuoi dati e le tue query.

Prima di creare un indice ScaNN:

  • 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 flag del database vengono utilizzati insieme per trovare il giusto equilibrio tra richiamo e QPS. Tutti i parametri si applicano a entrambi i tipi di indice ScaNN.

Parametro di correzione Descrizione Tipo di parametro
num_leaves Il numero di partizioni da applicare a questo indice. Il numero di partizioni applicate durante la creazione di un indice influisce sul rendimento dell'indice. Aumentando le partizioni per un numero fisso di vettori, crei un indice più granulare, che migliora il richiamo e il rendimento delle query. Tuttavia, questo comporta tempi di creazione dell'indice più lunghi.

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

    Se non sai quale valore selezionare, utilizza sqrt(ROWS) come punto di partenza, dove ROWS è il numero di righe di vettori. 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 su 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 un rendimento ottimale, ti consigliamo di 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 non sai quale valore selezionare, utilizza power(ROWS, 2/3) come punto di partenza, dove ROWS è il numero di righe di vettori. 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 su 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 un rendimento ottimale, ti consigliamo di ridurre al minimo il numero di vettori in ogni partizione.
Creazione dell'indice
quantizer Il tipo di quantizzatore che vuoi utilizzare per l'albero K-means. Il valore predefinito è SQ8 per un migliore rendimento delle query.

Imposta FLAT per un richiamo migliore.
Creazione dell'indice
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 è attivata per impostazione predefinita.

Imposta false se noti un deterioramento del richiamo.
Creazione dell'indice
scann.num_leaves_to_search Il flag del database controlla il compromesso tra richiamo e QPS. Il valore predefinito è l'1% del valore impostato in num_leaves.

Più alto è il valore impostato, migliore è il richiamo, ma si traduce in un QPS inferiore e viceversa.
Runtime della query
scann.max_top_neighbors_buffer_size Il flag del database specifica le dimensioni della cache utilizzata per migliorare il rendimento delle query filtrate calcolando o classificando i vicini candidati scansionati in memoria anziché sul disco. Il valore predefinito è 20000.

Più alto è il valore impostato, migliore è il QPS nelle query filtrate, ma si traduce in un maggiore utilizzo della memoria utilizzata e viceversa.
Runtime 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.

Valori più alti comportano un richiamo migliore, ma questo approccio comporta un QPS inferiore.
Runtime 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 alberi a due livelli.
  • Indice ad albero a tre livelli: imposta esplicitamente 2 per la quantizzazione basata su alberi a tre livelli.
Creazione dell'indice

Ottimizzare 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 su una tabella in cui è già stato generato un indice ScaNN influisce sul modo in cui l'albero appreso ottimizza l'indice. Se la tua tabella è soggetta a aggiornamenti o inserimenti frequenti, ti consigliamo di reindicizzare periodicamente l'indice ScaNN esistente per migliorare l'accuratezza del richiamo.

Puoi monitorare le metriche dell'indice per determinare la quantità di mutazioni create dalla creazione dell'indice e poi reindicizzare di conseguenza. Per ulteriori informazioni sulle metriche, consulta Metriche dell'indice vettoriale metrics.

Best practice per l'ottimizzazione

A seconda del tipo di indice ScaNN che intendi 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 richiamo e 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, segui questi passaggi:

  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 finché non raggiungi l'intervallo di richiamo 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 richiamo target.

    Se utilizzi vettori ad alta dimensione (500 dimensioni o più) e vuoi migliorare il richiamo, 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 raggiungono un richiamo target:
    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 conteggio delle righe. Ad esempio, se l'indice ha num_leaves impostato sulla radice quadrata del conteggio delle righe, prova a impostarlo sul doppio della radice quadrata. Se il valore è già il doppio, 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 conteggio delle righe diviso per 100.
    2. Esegui di nuovo le query di test. Durante l'esecuzione delle query di test, prova a ridurre scann.num_leaves_to_search, trovando un valore che aumenti il QPS mantenendo un richiamo elevato. Prova diversi valori di scann.num_leaves_to_search senza ricompilare l'indice.
  5. Ripeti il passaggio 4 finché il QPS e l'intervallo di richiamo non raggiungono valori accettabili.

Indice ad albero a tre livelli

Oltre ai consigli per l'indice ScaNN ad albero a due livelli, utilizza le seguenti indicazioni e i passaggi per ottimizzare l'indice:

  • L'aumento di max_num_levels da 1 per un albero a due livelli a 2 per un albero a tre livelli riduce significativamente il tempo necessario per creare un indice, ma a scapito dell'accuratezza del richiamo. Imposta max_num_levels utilizzando il seguente consiglio:
    • Imposta il valore su 2 se il numero di righe di vettori supera i 100 milioni di righe.
    • Imposta il valore su 1 se il numero di righe di vettori è inferiore a 10 milioni di righe.
    • Imposta su 1 o 2 se il numero di righe di vettori è compreso tra 10 milioni e 100 milioni di righe, in base all'equilibrio tra il tempo di creazione dell'indice e l'accuratezza del richiamo di cui hai bisogno.

Per applicare i consigli per trovare il valore ottimale dei parametri dell'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:

    • Righe di vettori maggiori di 100 milioni di righe: imposta max_num_levels su 2 e num_leaves su power(rows, ⅔).
    • Righe di vettori inferiori a 100 milioni di righe: imposta max_num_levels su 1 e num_leaves su sqrt(rows).
    • Righe di vettori comprese tra 10 milioni e 100 milioni di righe: inizia impostando max_num_levels su 1 e num_leaves su 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 con il valore num_leaves per un'accuratezza del richiamo ottimale.

  3. Se non sei soddisfatto del tempo di creazione dell'indice:

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

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

    • Se il valore max_num_levels è 2, elimina l'indice. Ricompila l'indice con lo stesso valore max_num_levels e ottimizza il valore num_leaves per un'accuratezza del richiamo ottimale.

Ottimizzare un indice IVF

L'ottimizzazione dei valori impostati per i parametri lists, ivf.probes e quantizer può contribuire a ottimizzare il rendimento dell'applicazione:

Parametro di correzione 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 dell'indice
quantizer Il tipo di quantizzatore che vuoi utilizzare per l'albero K-means. Il valore predefinito è SQ8 per un migliore rendimento delle query. Imposta FLAT per un richiamo migliore. Creazione dell'indice
ivf.probes Il numero di elenchi più vicini da esplorare durante la ricerca. Il punto di partenza per questo valore è
sqrt(lists).
Runtime della query

Considera il seguente esempio 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

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

Parametro di correzione 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 dell'indice
ivfflat.probes Il numero di elenchi più vicini da esplorare durante la ricerca. Il punto di partenza per questo valore è
sqrt(lists).
Runtime 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 su tabelle di grandi dimensioni.

Considera il seguente esempio 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

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

Parametro di correzione Descrizione Tipo di parametro
m Il numero massimo di connessioni per nodo nel grafico. Puoi iniziare con il valore predefinito 16(predefinito) e provare con valori più alti in base alle dimensioni del set di dati. Creazione dell'indice
ef_construction Le dimensioni dell'elenco di candidati dinamici mantenuto durante la creazione del grafico, che aggiorna costantemente i migliori candidati attuali per i vicini più prossimi di un nodo. Imposta questo valore su un valore superiore al doppio del valore m, ad esempio 64(predefinito). Creazione dell'indice
ef_search Le dimensioni dell'elenco di candidati dinamici utilizzato durante la ricerca. Puoi iniziare impostando questo valore su m o ef_construction, quindi modificarlo osservando il richiamo. Il valore predefinito è 40. Runtime della query

Considera il seguente esempio 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);

Analizzare le query

Utilizza il comando EXPLAIN ANALYZE per analizzare gli insight delle query, come mostrato nella seguente query SQL di esempio.

  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 come il tempo impiegato, il numero di righe scansionate 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 vettoriale

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

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

SELECT * FROM pg_stat_ann_indexes;

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

Passaggi successivi