Crea y administra índices vectoriales

En esta página, se explica cómo crear y administrar índices de vectores de Spanner, que usan la búsqueda de vecinos más cercanos aproximados (ANN) y estructuras basadas en árboles para acelerar las búsquedas de similitud de vectores en tus datos.

Spanner acelera las búsquedas vectoriales de vecino más cercano aproximado (ANN) con un índice vectorial especializado. Este índice aprovecha el vecino más cercano escalable (ScaNN) de Google Research, un algoritmo de vecino más cercano altamente eficiente.

El índice de vectores usa una estructura basada en un árbol para particionar los datos y facilitar búsquedas más rápidas. Spanner ofrece configuraciones de árbol de dos y tres niveles:

  • Configuración de árbol de dos niveles: Los nodos hoja (num_leaves) contienen grupos de vectores estrechamente relacionados junto con su centroide correspondiente. El nivel raíz consta de los centroides de todos los nodos hoja.
  • Configuración de árbol de tres niveles: Es similar en concepto a un árbol de dos niveles, pero introduce una capa de rama adicional (num_branches), desde la cual los centroides de los nodos hoja se particionan aún más para formar el nivel raíz (num_leaves).

Spanner elige un índice por ti. Sin embargo, si sabes que un índice específico funciona mejor, puedes usar la sugerencia FORCE_INDEX para elegir usar el índice de vectores más adecuado para tu caso de uso.

Para obtener más información, consulta las instrucciones VECTOR INDEX para GoogleSQL y las instrucciones INDEX para PostgreSQL.

Limitaciones

Crear índice vectorial

Para optimizar la recuperación y el rendimiento de un índice de vectores, te recomendamos que hagas lo siguiente:

  • Crea tu índice de vectores después de que se escriban en tu base de datos la mayoría de las filas con embeddings. También es posible que debas volver a compilar el índice de vectores periódicamente después de insertar datos nuevos. Para obtener más información, consulta Cómo volver a compilar el índice vectorial.

  • En GoogleSQL, usa la cláusula STORING y, en PostgreSQL, usa la cláusula INCLUDE para almacenar una copia de una columna en el índice de vectores. Si un valor de columna se almacena en el índice de vectores, Spanner realiza el filtrado a nivel de la hoja del índice para mejorar el rendimiento de la consulta. Te recomendamos que almacenes una columna si se usa en una condición de filtrado.

  • Usa columnas de clave que no sean de embedding en el índice de vectores. Las columnas clave son similares a las columnas STORING o INCLUDE, pero permiten que el motor de consultas realice el filtrado de manera más eficiente durante la búsqueda de vectores. Para obtener más información, consulta Crea un índice vectorial (GoogleSQL) o Instrucciones de índice (PostgreSQL).

Cuando crees la tabla, la columna de incorporación debe ser un array del tipo de datos FLOAT32 (GoogleSQL) o float4[] (PostgreSQL) (recomendado) y tener una anotación de longitud del vector (vector_length=>N para GoogleSQL o VECTOR LENGTH N para PostgreSQL) que indique la dimensión de los vectores.

La longitud óptima del vector depende de tu carga de trabajo, el tamaño del conjunto de datos y los recursos de procesamiento disponibles. Experimenta con diferentes dimensiones para encontrar el tamaño más pequeño que mantenga la precisión y el rendimiento de tu aplicación.

La siguiente instrucción DDL crea una tabla Documents con una columna de incorporación DocEmbedding con una longitud de vector:

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

Después de propagar tu tabla Documents, puedes crear un índice vectorial con un árbol de dos niveles y 1,000 nodos hoja en la tabla Documents con una columna de incorporación DocEmbedding usando la distancia 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;

Si tu columna de incorporación no está marcada como NOT NULL en la definición de la tabla, debes declararla con una cláusula WHERE COLUMN_NAME IS NOT NULL en la definición del índice de vectores, donde COLUMN_NAME es el nombre de tu columna de incorporación. Para crear un índice vectorial con un árbol de tres niveles y 1,000,000 de nodos hoja en la columna de incorporación anulable NullableDocEmbedding con la distancia del coseno, haz lo siguiente:

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 índice vectorial

También puedes crear un índice de vectores filtrado para encontrar los elementos más similares de tu base de datos que coincidan con la condición del filtro. Un índice de vectores filtrado indexa de forma selectiva las filas que satisfacen las condiciones de filtro especificadas, lo que mejora el rendimiento de la búsqueda.

En el siguiente ejemplo, la tabla Documents2 tiene una columna llamada Category. En nuestra búsqueda de vectores, queremos indexar la categoría "Tecnología", por lo que creamos una columna generada que se evalúa como NULL si no se cumple la condición de categoría.

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

Luego, creamos un índice de vectores con un filtro. El índice de vectores TechDocEmbeddingIndex solo indexa documentos de la categoría "Tecnología".

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;

Cuando Spanner ejecuta la siguiente consulta, que tiene filtros que coinciden con TechDocEmbeddingIndex, selecciona automáticamente TechDocEmbeddingIndex y se acelera con él. La búsqueda solo se realiza en los documentos de la categoría "Tecnología". También puedes usar la sugerencia FORCE_INDEX (@{FORCE_INDEX=TechDocEmbeddingIndex} para GoogleSQL o /*@ FORCE_INDEX = tech_doc_embedding_index */ para PostgreSQL) para forzar a Spanner a usar el índice de forma explícita.

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;

Para mejorar el rendimiento de las consultas, puedes incluir columnas clave que no sean de incorporación en tu índice de vectores. Esto permite que el motor de consultas realice el filtrado de manera más eficiente durante la búsqueda de vectores.

En la sentencia de creación del índice, debes enumerar estas columnas clave adicionales después de la columna de incorporación. Por ejemplo, la siguiente instrucción crea un índice de vectores que incluye las columnas de clave DocName y Author para un filtrado más eficiente:

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;

¿Qué sigue?