Créer et gérer des index vectoriels

Cette page explique comment créer et gérer des index vectoriels Spanner, qui utilisent la recherche approximative du voisin le plus proche (ANN) et des structures arborescentes pour accélérer les recherches de similarité vectorielle dans vos données.

Spanner accélère les recherches vectorielles approximatives du voisin le plus proche (ANN) à l'aide d'un index vectoriel spécialisé. Cet index exploite l'algorithme Scalable Nearest Neighbor (ScaNN) de Google Research, un algorithme de recherche du voisin le plus proche très efficace.

L'index vectoriel utilise une structure arborescente pour partitionner les données et faciliter les recherches plus rapides. Spanner propose des configurations d'arborescence à deux et trois niveaux :

  • Configuration d'arborescence à deux niveaux : les nœuds feuilles (num_leaves) contiennent des groupes de vecteurs étroitement liés ainsi que leur centroïde correspondant. Le niveau racine est constitué des centroïdes de tous les nœuds feuilles.
  • Configuration d'arborescence à trois niveaux : conceptuellement similaire à une arborescence à deux niveaux, mais avec une couche de branche supplémentaire (num_branches) à partir de laquelle les centroïdes des nœuds feuilles sont partitionnés pour former le niveau racine (num_leaves).

Spanner choisit un index pour vous. Toutefois, si vous savez qu'un index spécifique fonctionne mieux, vous pouvez utiliser l'FORCE_INDEXindicateur pour choisir d'utiliser l'index vectoriel le plus approprié à votre cas d'utilisation.

Pour en savoir plus, consultez les instructions VECTOR INDEX pour GoogleSQL et les instructions INDEX pour PostgreSQL.

Limites

Créer un index vectoriel

Pour optimiser le rappel et les performances d'un index vectoriel, nous vous recommandons de procéder comme suit :

  • Créez votre index vectoriel une fois que la plupart des lignes avec des embeddings sont écrites dans votre base de données. Vous devrez peut-être aussi reconstruire périodiquement l'index vectoriel après avoir inséré de nouvelles données. Pour en savoir plus, consultez la section Reconstruire l'index vectoriel.

  • Pour GoogleSQL, utilisez la clause STORING, et pour PostgreSQL, utilisez la clause INCLUDE afin de stocker une copie d'une colonne dans l'index vectoriel. Si une valeur de colonne est stockée dans l'index vectoriel, Spanner effectue un filtrage au niveau feuille de l'index pour améliorer les performances des requêtes. Nous vous recommandons de stocker une colonne si elle est utilisée dans une condition de filtrage.

  • Utilisez des colonnes clés sans embedding dans l'index vectoriel. Les colonnes clés sont semblables aux colonnes STORING ou INCLUDE, mais permettent au moteur de requête d'effectuer un filtrage plus efficace lors de la recherche vectorielle. Pour en savoir plus, consultez Créer un index vectoriel (GoogleSQL) ou Instructions d'index (PostgreSQL).

Lorsque vous créez votre table, la colonne d'embedding doit être un tableau de type de données FLOAT32 (GoogleSQL) ou float4[] (PostgreSQL) (recommandé) et comporter une annotation de longueur de vecteur (vector_length=>N pour GoogleSQL ou VECTOR LENGTH N pour PostgreSQL), indiquant la dimension des vecteurs.

La longueur de vecteur optimale dépend de votre charge de travail, de la taille de l'ensemble de données et des ressources de calcul disponibles. Essayez différentes dimensions pour trouver la plus petite taille qui maintient la précision et les performances de votre application.

L'instruction LDD suivante crée une table Documents avec une colonne d'embedding DocEmbedding et une longueur de vecteur :

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)
);

Une fois que vous avez rempli votre table Documents, vous pouvez créer un index vectoriel avec une arborescence à deux niveaux et 1 000 nœuds feuilles sur la table Documents avec une colonne d'embedding DocEmbedding à l'aide de la distance cosinus :

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;

Si votre colonne d'embedding n'est pas marquée comme NOT NULL dans la définition de la table, vous devez la déclarer avec une clause WHERE COLUMN_NAME IS NOT NULL dans la définition de l'index vectoriel, où COLUMN_NAME est le nom de votre colonne d'embedding. Pour créer un index vectoriel avec une arborescence à trois niveaux et 1 000 000 de nœuds feuilles sur la colonne d'embedding pouvant accepter la valeur nulle NullableDocEmbedding à l'aide de la distance cosinus :

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;

Filtrer un index vectoriel

Vous pouvez également créer un index vectoriel filtré pour trouver les éléments les plus similaires de votre base de données qui correspondent à la condition de filtre. Un index vectoriel filtré indexe de manière sélective les lignes qui répondent aux conditions de filtre spécifiées, ce qui améliore les performances de recherche.

Dans l'exemple suivant, la table Documents2 comporte une colonne appelée Category. Dans notre recherche vectorielle, nous voulons indexer la catégorie "Tech". Nous créons donc une colonne générée qui prend la valeur NULL si la condition de catégorie n'est pas remplie.

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)
);

Ensuite, nous créons un index vectoriel avec un filtre. L'index vectoriel TechDocEmbeddingIndex n'indexe que les documents de la catégorie "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;

Lorsque Spanner exécute la requête suivante, qui comporte des filtres correspondant à TechDocEmbeddingIndex, il sélectionne automatiquement TechDocEmbeddingIndex et l'accélère. La requête ne recherche que les documents de la catégorie "Tech". Vous pouvez également utiliser l'indicateur FORCE_INDEX (@{FORCE_INDEX=TechDocEmbeddingIndex} pour GoogleSQL ou /*@ FORCE_INDEX = tech_doc_embedding_index */ pour PostgreSQL) afin de forcer Spanner à utiliser explicitement l'index.

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;

Pour améliorer les performances des requêtes, vous pouvez inclure des colonnes clés sans embedding dans votre index vectoriel. Cela permet au moteur de requête d'effectuer un filtrage plus efficace lors de la recherche vectorielle.

Dans l'instruction de création d'index, vous devez répertorier ces colonnes clés supplémentaires après la colonne d'embedding. Par exemple, l'instruction suivante crée un index vectoriel qui inclut les colonnes clés DocName et Author pour un filtrage plus efficace :

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;

Étape suivante