Creare e gestire indici vettoriali

Questa pagina spiega come creare e gestire gli indici vettoriali di Spanner, che utilizzano la ricerca del vicino più prossimo approssimato (ANN) e le strutture basate su alberi per accelerare le ricerche di similarità vettoriale sui dati.

Spanner accelera le ricerche vettoriali del vicino più prossimo approssimato (ANN) utilizzando un indice vettoriale specializzato. Questo indice sfrutta Google Research's ScaNN (Scalable Nearest Neighbor), un algoritmo del vicino più prossimo altamente efficiente.

L'indice vettoriale utilizza una struttura basata su alberi per partizionare i dati e facilitare le ricerche più rapide. Spanner offre configurazioni di alberi a due e tre livelli:

  • Configurazione dell'albero a due livelli: i nodi foglia (num_leaves) contengono gruppi di vettori strettamente correlati insieme al centroide corrispondente. Il livello principale è costituito dai centroidi di tutti i nodi foglia.
  • Configurazione dell'albero a tre livelli: simile nel concetto a un albero a due livelli, ma introduce un livello di ramificazione aggiuntivo (num_branches), da cui i centroidi dei nodi foglia vengono ulteriormente partizionati per formare il livello principale (num_leaves).

Spanner sceglie un indice per te. Tuttavia, se sai che un indice specifico funziona meglio, puoi utilizzare l'FORCE_INDEX hint per scegliere di utilizzare l'indice vettoriale più appropriato per il tuo caso d'uso.

Per saperne di più, consulta le istruzioni VECTOR INDEX per GoogleSQL e le istruzioni INDEX per PostgreSQL.

Limitazioni

Crea indice vettoriale

Per ottimizzare il richiamo e il rendimento di un indice vettoriale, ti consigliamo di:

  • Crea l'indice vettoriale dopo aver scritto la maggior parte delle righe con gli embedding nel database. Potresti anche dover ricompilare periodicamente l'indice vettoriale dopo aver inserito nuovi dati. Per saperne di più, consulta Ricompila l'indice vettoriale.

  • Per GoogleSQL, utilizza la clausola STORING e per PostgreSQL, utilizza la clausola INCLUDE per archiviare una copia di una colonna nell'indice vettoriale. Se un valore di colonna è archiviato nell'indice vettoriale, Spanner esegue il filtraggio a livello di foglia dell'indice per migliorare il rendimento delle query. Ti consigliamo di archiviare una colonna se viene utilizzata in una condizione di filtro.

  • Utilizza colonne chiave non di embedding nell'indice vettoriale. Le colonne chiave sono simili alle colonne STORING o INCLUDE, ma consentono al motore di query di eseguire il filtraggio in modo più efficiente durante la ricerca vettoriale. Per saperne di più, consulta Crea indice vettoriale (GoogleSQL) o Istruzioni dell'indice (PostgreSQL).

Quando crei la tabella, la colonna di embedding deve essere un array del FLOAT32 (GoogleSQL) o float4[] (PostgreSQL) tipo di dati (consigliato) e avere un' annotazione di lunghezza del vettore (vector_length=>N per GoogleSQL o VECTOR LENGTH N per PostgreSQL), che indica la dimensione dei vettori.

La lunghezza ottimale del vettore dipende dal carico di lavoro, dalle dimensioni del set di dati e dalle risorse di calcolo disponibili. Sperimenta con dimensioni diverse per trovare la dimensione più piccola che mantenga l'accuratezza e il rendimento della tua applicazione.

La seguente istruzione DDL crea una tabella Documents con una colonna di embedding DocEmbedding con una lunghezza del vettore:

GoogleSQL

CREATE TABLE Documents (
  UserId INT64 NOT NULL,
  DocId INT64 NOT NULL,
  Author STRING (1024),
  DocContents Bytes(MAX),
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128) NOT NULL,
  NullableDocEmbedding ARRAY<FLOAT32>(vector_length=>128),
  WordCount INT64
) PRIMARY KEY (DocId);

PostgreSQL

CREATE TABLE documents (
  user_id bigint not null,
  doc_id bigint not null,
  author varchar(1024),
  doc_contents bytea,
  doc_embedding float4[] VECTOR LENGTH 128 not null,
  nullable_doc_embedding float4[] VECTOR LENGTH 128,
  word_count bigint,
  PRIMARY KEY (doc_id)
);

Dopo aver compilato la tabella Documents, puoi creare un indice vettoriale con un albero a due livelli e 1000 nodi foglia nella tabella Documents con una colonna di embedding DocEmbedding utilizzando la distanza del coseno:

GoogleSQL

CREATE VECTOR INDEX DocEmbeddingIndex
  ON Documents(DocEmbedding)
  STORING (WordCount)
  OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);

PostgreSQL

CREATE INDEX doc_embedding_index
  ON documents
  USING scann(doc_embedding)
  INCLUDE (word_count)
  WITH (distance_type = 'COSINE', num_leaves = 1000)
  WHERE doc_embedding IS NOT NULL;

Se la colonna di embedding non è contrassegnata come NOT NULL nella definizione della tabella, devi dichiararla con una clausola WHERE COLUMN_NAME IS NOT NULL nella definizione dell'indice vettoriale, dove COLUMN_NAME è il nome della colonna di embedding. Per creare un indice vettoriale con un albero a tre livelli e 1000000 nodi foglia nella colonna di embedding nullable NullableDocEmbedding utilizzando la distanza del coseno:

GoogleSQL

CREATE VECTOR INDEX DocEmbeddingThreeLevelIndex
  ON Documents(NullableDocEmbedding)
  STORING (WordCount)
  WHERE NullableDocEmbedding IS NOT NULL
  OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);

PostgreSQL

CREATE INDEX doc_embedding_index
  ON documents
  USING scann(nullable_doc_embedding)
  INCLUDE (word_count)
  WITH (distance_type = 'COSINE', tree_depth = 3, num_branches = 1000, num_leaves = 1000000)
  WHERE nullable_doc_embedding IS NOT NULL;

Filtra un indice vettoriale

Puoi anche creare un indice vettoriale filtrato per trovare gli elementi più simili nel database che corrispondono alla condizione di filtro. Un indice vettoriale filtrato indicizza in modo selettivo le righe che soddisfano le condizioni di filtro specificate, migliorando il rendimento della ricerca.

Nell'esempio seguente, la tabella Documents2 ha una colonna denominata Category. Nella ricerca vettoriale, vogliamo indicizzare la categoria "Tech", quindi creiamo una colonna generata che restituisce NULL se la condizione della categoria non è soddisfatta.

GoogleSQL

CREATE TABLE Documents2 (
  UserId INT64 NOT NULL,
  DocId INT64 NOT NULL,
  DocName STRING (1024),
  Author STRING (1024),
  DocContents Bytes(MAX),
  Category STRING(MAX),
  NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
  DocEmbedding ARRAY<FLOAT32>(vector_length=>128)
) PRIMARY KEY (DocId);

PostgreSQL

CREATE TABLE documents2 (
  user_id bigint not null,
  doc_id bigint not null,
  doc_name varchar(1024),
  author varchar(1024),
  doc_contents bytea,
  category varchar,
  null_if_filtered boolean GENERATED ALWAYS AS (CASE WHEN category = 'Tech' THEN true END) VIRTUAL HIDDEN,
  doc_embedding float4[] VECTOR LENGTH 128,
  PRIMARY KEY (doc_id)
);

Poi creiamo un indice vettoriale con un filtro. L'indice vettoriale TechDocEmbeddingIndex indicizza solo i documenti nella categoria "Tech".

GoogleSQL

CREATE VECTOR INDEX TechDocEmbeddingIndex
  ON Documents2(DocEmbedding)
  STORING(NullIfFiltered)
  WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
  OPTIONS (...);

PostgreSQL

CREATE INDEX tech_doc_embedding_index
  ON documents2
  USING scann(doc_embedding)
  INCLUDE (null_if_filtered)
  WITH (distance_type = 'COSINE', num_leaves = 1000)
  WHERE doc_embedding IS NOT NULL AND null_if_filtered IS NOT NULL;

Quando Spanner esegue la seguente query, che contiene filtri che corrispondono a TechDocEmbeddingIndex, la sceglie automaticamente e viene accelerata da TechDocEmbeddingIndex. La query cerca solo i documenti nella categoria "Tech". Puoi anche utilizzare l'hint FORCE_INDEX (@{FORCE_INDEX=TechDocEmbeddingIndex} per GoogleSQL o /*@ FORCE_INDEX = tech_doc_embedding_index */ per PostgreSQL) per forzare Spanner a utilizzare l'indice in modo esplicito.

GoogleSQL

SELECT *
FROM Documents2
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;

PostgreSQL

SELECT *
FROM documents2
WHERE doc_embedding IS NOT NULL AND null_if_filtered IS NOT NULL
ORDER BY spanner.approx_cosine_distance(doc_embedding, ARRAY[1.0::float4, 2.0::float4, 3.0::float4])
LIMIT 10;

Per migliorare il rendimento delle query, puoi includere colonne chiave non di embedding nell'indice vettoriale. In questo modo, il motore di query può eseguire il filtraggio in modo più efficiente durante la ricerca vettoriale.

Nell'istruzione di creazione dell'indice, devi elencare queste colonne chiave aggiuntive dopo la colonna di embedding. Ad esempio, la seguente istruzione crea un indice vettoriale che include le colonne chiave DocName e Author per un filtraggio più efficiente:

GoogleSQL

CREATE VECTOR INDEX DocEmbeddingIndexWithKeys
  ON Documents2(DocEmbedding, DocName, Author)
  STORING(NullIfFiltered)
  WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
  OPTIONS (...);

PostgreSQL

CREATE INDEX doc_embedding_index_with_keys
  ON documents2
  USING scann(doc_embedding, doc_name, author)
  INCLUDE (null_if_filtered)
  WITH (distance_type = 'COSINE', num_leaves = 1000)
  WHERE doc_embedding IS NOT NULL AND null_if_filtered IS NOT NULL;

Passaggi successivi