Criar e gerenciar índices vetoriais

Esta página explica como criar e gerenciar índices vetoriais do Spanner, que usam a pesquisa de vizinhos mais próximos aproximados (ANN, na sigla em inglês) e estruturas baseadas em árvores para acelerar as pesquisas de similaridade vetorial nos seus dados.

O Spanner acelera as pesquisas de vetores do vizinho mais próximo aproximado (ANN) usando um índice vetorial especializado. Esse índice aproveita o Google Research's Scalable Nearest Neighbor (ScaNN), um algoritmo de vizinho mais próximo altamente eficiente.

O índice vetorial usa uma estrutura baseada em árvores para particionar dados e facilitar pesquisas mais rápidas. O Spanner oferece configurações de árvores de dois e três níveis:

  • Configuração de árvore de dois níveis: os nós folha (num_leaves) contêm grupos de vetores intimamente relacionados, além do centroide correspondente. O nível raiz consiste nos centroides de todos os nós folha.
  • Configuração de árvore de três níveis: semelhante ao conceito de uma árvore de dois níveis, mas introduzindo uma camada de ramificação adicional (num_branches), em que os centroides dos nós folha são ainda mais particionados para formar o nível raiz (num_leaves).

O Spanner escolhe um índice para você. No entanto, se você souber que um índice específico funciona melhor, use a FORCE_INDEX dica para escolher o índice vetorial mais adequado ao seu caso de uso.

Para mais informações, consulte as instruções VECTOR INDEX para GoogleSQL e INDEX para PostgreSQL.

Limitações

Criar índice vetorial

Para otimizar o recall e a performance de um índice vetorial, recomendamos que você:

  • Crie o índice vetorial depois que a maioria das linhas com embeddings forem gravadas no banco de dados. Talvez também seja necessário recriar o índice vetorial periodicamente depois de inserir novos dados. Para mais informações, consulte Recriar o índice vetorial.

  • Para o GoogleSQL, use a cláusula STORING e, para o PostgreSQL, use a cláusula INCLUDE para armazenar uma cópia de uma coluna no índice vetorial. Se um valor de coluna for armazenado no índice vetorial, o Spanner vai realizar a filtragem no nível da folha do índice para melhorar a performance da consulta. Recomendamos armazenar uma coluna se ela for usada em uma condição de filtragem.

  • Use colunas de chave não embedding no índice vetorial. As colunas de chave são semelhantes às colunas STORING ou INCLUDE, mas permitem que o mecanismo de consulta execute a filtragem com mais eficiência durante a pesquisa vetorial. Para mais informações, consulte Criar índice vetorial (GoogleSQL) ou Instruções de índice (PostgreSQL).

Ao criar a tabela, a coluna de embedding precisa ser uma matriz do tipo de dados FLOAT32 (GoogleSQL) ou float4[] (PostgreSQL) (recomendado) e ter uma anotação de comprimento do vetor (vector_length=>N para GoogleSQL ou VECTOR LENGTH N para PostgreSQL), indicando a dimensão dos vetores.

O comprimento ideal do vetor depende da sua carga de trabalho, do tamanho do conjunto de dados e dos recursos computacionais disponíveis. Faça testes com dimensões diferentes para encontrar o menor tamanho que mantenha a acurácia e o desempenho do aplicativo.

A instrução DDL a seguir cria uma tabela Documents com uma coluna de embedding DocEmbedding com um comprimento de vetor:

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

Depois de preencher a tabela Documents, você pode criar um índice vetorial com uma árvore de dois níveis e 1.000 nós folha na tabela Documents com uma coluna de embedding DocEmbedding usando a distância do cosseno:

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 a coluna de embedding não estiver marcada como NOT NULL na definição da tabela, você deve declará-la com uma cláusula WHERE COLUMN_NAME IS NOT NULL na definição do índice vetorial, em que COLUMN_NAME é o nome da coluna de embedding. Para criar um índice vetorial com uma árvore de três níveis e 1.000.000 de nós folha na coluna de embedding anulável NullableDocEmbedding usando a distância do cosseno:

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;

Filtrar um índice vetorial

Também é possível criar um índice vetorial filtrado para encontrar os itens mais semelhantes no banco de dados que correspondam à condição de filtro. Um índice vetorial filtrado indexa seletivamente as linhas que atendem às condições de filtro especificadas, melhorando a performance da pesquisa.

No exemplo a seguir, a tabela Documents2 tem uma coluna chamada Category. Na pesquisa vetorial, queremos indexar a categoria "Tech". Por isso, criamos uma coluna gerada que é avaliada como NULL se a condição de categoria não for atendida.

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

Em seguida, criamos um índice vetorial com um filtro. O índice vetorial TechDocEmbeddingIndex indexa apenas documentos na 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 o Spanner executa a consulta a seguir, que tem filtros que correspondem ao TechDocEmbeddingIndex, ele é escolhido automaticamente e acelerado por TechDocEmbeddingIndex. A consulta pesquisa apenas documentos na categoria "Tech". Também é possível usar a dica FORCE_INDEX (@{FORCE_INDEX=TechDocEmbeddingIndex} para GoogleSQL ou /*@ FORCE_INDEX = tech_doc_embedding_index */ para PostgreSQL) para forçar o Spanner a usar o índice explicitamente.

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 melhorar a performance da consulta, inclua colunas de chave não embedding no índice vetorial. Isso permite que o mecanismo de consulta execute a filtragem com mais eficiência durante a pesquisa vetorial.

Na instrução de criação de índice, liste essas colunas de chave adicionais após a coluna de embedding. Por exemplo, a instrução a seguir cria um índice vetorial que inclui as colunas de chave DocName e Author para uma filtragem mais 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;

A seguir