Questa pagina descrive come trovare i vicini più prossimi approssimativi (ANN), creare indici vettoriali ed eseguire query sugli incorporamenti vettoriali utilizzando le seguenti funzioni di distanza ANN in Spanner:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
Quando un set di dati è piccolo, puoi utilizzare K-Nearest Neighbors (KNN) per trovare i vettori k-nearest esatti. Tuttavia, man mano che il set di dati cresce, aumentano anche la latenza e il costo di una ricerca KNN. Puoi utilizzare ANN per trovare i k-nearest neighbors approssimativi con latenza e costi notevolmente ridotti.
K-nearest neighbors approssimati
In una ricerca ANN, i k vettori restituiti non sono i k vicini più prossimi perché la ricerca ANN calcola distanze approssimative e potrebbe non esaminare tutti i vettori nel set di dati. Occasionalmente, vengono restituiti alcuni vettori che non rientrano tra i primi k-nearest neighbor. Questo fenomeno è noto come perdita di richiamo. La quantità di perdita di richiamo accettabile dipende dal caso d'uso, ma nella maggior parte dei casi, perdere un po' di richiamo in cambio di prestazioni del database migliorate è un compromesso accettabile.
Per ulteriori dettagli sulle funzioni di distanza approssimativa di Spanner, vedi:
APPROX_COSINE_DISTANCE
in GoogleSQLAPPROX_EUCLIDEAN_DISTANCE
in GoogleSQLAPPROX_DOT_PRODUCT
in GoogleSQL
Indice vettoriale
Spanner accelera le ricerche vettoriali ANN utilizzando un indice vettoriale specializzato. Questo indice sfrutta Scalable Nearest Neighbor (ScaNN) di Google Research, un algoritmo per la ricerca dei vicini più prossimi altamente efficiente.
L'indice vettoriale utilizza una struttura ad albero per partizionare i dati e facilitare ricerche più rapide. Spanner offre configurazioni ad albero a due e tre livelli:
- Configurazione ad albero a due livelli: i nodi foglia (
num_leaves
) contengono gruppi di vettori strettamente correlati insieme al loro centroide corrispondente. Il livello radice è costituito dai centroidi di tutti i nodi foglia. - Configurazione ad albero a tre livelli: simile nel concetto a un albero a due livelli, ma
introduce un ulteriore livello di ramificazione (
num_branches
), da cui i centroidi dei nodi foglia vengono ulteriormente partizionati per formare il livello radice (num_leaves
).
Spanner sceglie un indice per te. Tuttavia, se sai che un indice specifico funziona meglio, puoi utilizzare il suggerimento FORCE_INDEX
per scegliere di utilizzare l'indice vettoriale più appropriato per il tuo caso d'uso.
Per ulteriori informazioni, consulta la sezione Estratti conto VECTOR INDEX
.
Limitazioni
- Non puoi dividere in anticipo gli indici vettoriali. Per ulteriori informazioni, consulta la panoramica della pre-suddivisione.
Crea indice vettoriale
Per ottimizzare il recupero e il rendimento di un indice vettoriale, ti consigliamo di:
Crea l'indice vettoriale dopo che la maggior parte delle righe con gli incorporamenti è stata scritta nel database. Potresti anche dover ricreare periodicamente l'indice vettoriale dopo aver inserito nuovi dati. Per ulteriori informazioni, vedi Ricostruire l'indice vettoriale.
Utilizza la clausola
STORING
per archiviare una copia di una colonna nell'indice vettoriale. Se un valore della colonna è memorizzato nell'indice vettoriale, Spanner esegue il filtraggio a livello di foglia dell'indice per migliorare le prestazioni della query. Ti consigliamo di memorizzare una colonna se viene utilizzata in una condizione di filtro. Per saperne di più sull'utilizzo diSTORING
in un indice, vedi Creare un indice per le scansioni solo indice.
Quando crei la tabella, la colonna di incorporamento deve essere un array di tipo di dati
FLOAT32
(consigliato) o FLOAT64
e deve avere un'annotazione vector_length,
che indica la dimensione dei vettori.
La seguente istruzione DDL crea una tabella Documents
con una colonna di incorporamento DocEmbedding
con una lunghezza del vettore:
CREATE TABLE Documents (
DocId INT64 NOT NULL,
...
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
Dopo aver compilato la tabella Documents
, puoi creare un indice vettoriale con un albero a due livelli e 1000 nodi foglia in una tabella Documents
con una colonna di incorporamento DocEmbedding
utilizzando la distanza del coseno:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(DocEmbedding)
STORING (WordCount)
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
Per creare un indice vettoriale con un albero a tre livelli e 1.000.000 di nodi foglia:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);
Se la colonna di incorporamento non è contrassegnata come NOT NULL
nella definizione della tabella, devi dichiararla con una clausola WHERE COLUMN_NAME IS NOT NULL
:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
Filtrare un indice vettoriale
Puoi anche creare un indice vettoriale filtrato per trovare gli elementi più simili nel tuo database che corrispondono alla condizione di filtro. Un indice vettoriale filtrato indicizza selettivamente le righe che soddisfano le condizioni di filtro specificate, migliorando le prestazioni di ricerca.
Nel seguente esempio, la tabella Documents
ha una colonna chiamata Category
.
Nella nostra ricerca vettoriale, vogliamo indicizzare la categoria "Tecnologia", quindi creiamo una
colonna generata che restituisce NULL se la condizione della categoria non è soddisfatta.
CREATE TABLE Documents (
DocId INT64 NOT NULL,
Category STRING(MAX),
NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
Poi creiamo un indice vettoriale con un filtro. L'indice vettoriale TechDocEmbeddingIndex
indicizza solo i documenti della categoria "Tecnologia".
CREATE VECTOR INDEX TechDocEmbeddingIndex
ON Documents(DocEmbedding)
STORING(NullIfFiltered)
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
OPTIONS (...);
Quando Spanner esegue la seguente query, che ha filtri che
corrispondono a TechDocEmbeddingIndex
, sceglie automaticamente ed è accelerata da
TechDocEmbeddingIndex
. La query cerca solo documenti nella categoria "Tecnologia". Puoi anche utilizzare {@FORCE_INDEX=TechDocEmbeddingIndex}
per forzare
Spanner a utilizzare TechDocEmbeddingIndex
in modo esplicito.
SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;
Eseguire query sugli incorporamenti vettoriali
Per eseguire una query su un indice vettoriale, utilizza una delle tre funzioni di distanza approssimata:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
Le limitazioni quando si utilizzano le funzioni di distanza approssimativa includono quanto segue:
- La funzione di distanza approssimativa deve calcolare la distanza tra una colonna di incorporamento e un'espressione costante (ad esempio, un parametro o un valore letterale).
- L'output della funzione di distanza approssimativa deve essere utilizzato in una clausola
ORDER BY
come unica chiave di ordinamento e dopoORDER BY
deve essere specificato unLIMIT
. - La query deve filtrare esplicitamente le righe non indicizzate. Nella maggior parte dei casi,
ciò significa che la query deve includere una clausola
WHERE <column_name> IS NOT NULL
che corrisponda alla definizione dell'indice vettoriale, a meno che la colonna non sia già contrassegnata comeNOT NULL
nella definizione della tabella.
Per un elenco dettagliato delle limitazioni, consulta la pagina di riferimento della funzione di distanza approssimativa.
Esempi
Per cercare i 100 vettori più vicini a [1.0, 2.0, 3.0]
:
SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
Se la colonna di embedding ammette valori nulli:
SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
Best practice
Segui queste best practice per ottimizzare gli indici vettoriali e migliorare i risultati delle query.
Ottimizza le opzioni di ricerca vettoriale
Il valore di ricerca vettoriale più ottimale dipende dal caso d'uso, dal set di dati vettoriali e dai vettori di query. Potresti dover eseguire un'ottimizzazione iterativa per trovare i valori migliori per il tuo carico di lavoro specifico.
Ecco alcune linee guida utili da seguire quando scegli i valori appropriati:
tree_depth
(livello ad albero): se la tabella da indicizzare ha meno di 10 milioni di righe, utilizza untree_depth
di2
. In caso contrario, untree_depth
di3
supporta tabelle con un massimo di circa 10 miliardi di righe.num_leaves
: utilizza la radice quadrata del numero di righe nel set di dati. Un valore maggiore può aumentare il tempo di creazione dell'indice vettoriale. Evita di impostarenum_leaves
maggiore ditable_row_count/1000
, in quanto ciò comporta foglie troppo piccole e un rendimento scarso.num_leaves_to_search
: questa opzione specifica il numero di nodi foglia dell'indice che vengono cercati. L'aumento dinum_leaves_to_search
migliora il richiamo, ma aumenta anche la latenza e i costi. Ti consigliamo di utilizzare un numero pari all'1% del numero totale di foglie definito nell'istruzioneCREATE VECTOR INDEX
come valore pernum_leaves_to_search
. Se utilizzi una clausola di filtro, aumenta questo valore per ampliare la ricerca.
Se si ottiene un richiamo accettabile, ma il costo delle query è troppo elevato,
con conseguente basso QPS massimo, prova ad aumentare num_leaves
seguendo questi
passaggi:
- Imposta
num_leaves
su un multiplo k del suo valore originale (ad esempio,2 * sqrt(table_row_count)
). - Imposta
num_leaves_to_search
in modo che sia lo stesso multiplo k del suo valore originale. - Prova a ridurre
num_leaves_to_search
per migliorare il costo e le QPS mantenendo il richiamo.
Migliorare il ricordo
Esistono diverse possibilità di peggioramento del richiamo, tra cui:
num_leaves_to_search
è troppo piccolo: potresti avere più difficoltà a trovare i vicini più prossimi per alcuni vettori di query, quindi aumentarenum_leaves_to_search
per cercare più foglie può contribuire a migliorare il richiamo. Le query recenti potrebbero essere cambiate per includere un numero maggiore di questi vettori difficili.L'indice vettoriale deve essere ricreato: la struttura ad albero dell'indice vettoriale è ottimizzata per il set di dati al momento della creazione ed è statica in seguito. Pertanto, se vengono aggiunti vettori significativamente diversi dopo la creazione dell'indice vettoriale iniziale, la struttura ad albero potrebbe non essere ottimale, con conseguente richiamo inferiore.
Ricrea l'indice vettoriale
Per ricompilare l'indice vettoriale senza tempi di inattività:
- Crea un nuovo indice vettoriale sulla stessa colonna di incorporamento dell'indice vettoriale attuale, aggiornando i parametri (ad esempio
OPTIONS
) in modo appropriato. - Una volta completata la creazione dell'indice, utilizza il suggerimento
FORCE_INDEX
per indicare il nuovo indice per aggiornare la query di ricerca vettoriale. In questo modo la query utilizza il nuovo indice vettoriale. Potresti anche dover eseguire di nuovo l'ottimizzazionenum_leaves_to_search
nella nuova query. - Elimina l'indice vettoriale obsoleto.
Passaggi successivi
Scopri di più sulle funzioni GoogleSQL
APPROXIMATE_COSINE_DISTANCE()
,APPROXIMATE_EUCLIDEAN_DISTANCE()
,APPROXIMATE_DOT_PRODUCT()
.Scopri di più sulle istruzioni
VECTOR INDEX
GoogleSQL.Prova la guida Inizia a utilizzare la ricerca vettoriale di Spanner per un esempio passo passo di utilizzo di ANN.