Criar e gerenciar índices vetoriais

Esta página explica como criar e gerenciar índices vetoriais do Spanner, que usam a pesquisa aproximada do vizinho mais próximo (ANN) e estruturas baseadas em árvores para acelerar as pesquisas de similaridade vetorial nos seus dados.

O Spanner acelera as pesquisas de vetores de vizinho mais próximo aproximado (ANN) usando um índice de vetor especializado. Esse índice usa o vizinho mais próximo escalonável (ScaNN) da Pesquisa do Google, um algoritmo de vizinho mais próximo altamente eficiente.

O índice de vetor usa uma estrutura baseada em árvore para particionar dados e facilitar pesquisas mais rápidas. O Spanner oferece configurações de árvore 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 estreitamente relacionados e o 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 em conceito a uma árvore de dois níveis, mas introduzindo uma camada de ramificação adicional (num_branches), de onde os centroides do nó 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 dica FORCE_INDEX 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 de vetor, recomendamos que você:

  • Crie o índice vetorial depois que a maioria das linhas com embeddings for gravada no banco de dados. Talvez seja necessário reconstruir periodicamente o índice de vetor 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 de vetor. Se um valor de coluna for armazenado no índice de vetor, o Spanner vai realizar a filtragem no nível da folha do índice para melhorar o desempenho da consulta. Recomendamos que você armazene uma coluna se ela for usada em uma condição de filtro.

  • Use colunas de chave não de embedding no índice de vetor. As colunas de chave são semelhantes às colunas STORING ou INCLUDE, mas permitem que o mecanismo de consulta faça a filtragem de maneira mais eficiente durante a pesquisa vetorial. Para mais informações, consulte Criar índice de vetor (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 tamanho ideal do vetor depende da sua carga de trabalho, do tamanho do conjunto de dados e dos recursos computacionais disponíveis. Teste diferentes dimensões para encontrar o menor tamanho que mantenha a precisão e o desempenho do seu aplicativo.

A instrução DDL a seguir cria uma tabela Documents com uma coluna de incorporação 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, crie um índice de vetor 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 de 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, declare-a com uma cláusula WHERE COLUMN_NAME IS NOT NULL na definição do índice de vetor, em que COLUMN_NAME é o nome da coluna de embedding. Para criar um índice de vetor com uma árvore de três níveis e 1.000.000 nós folha na coluna de embedding anulável NullableDocEmbedding usando a distância de 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 correspondem à condição de filtro. Um índice de vetor filtrado indexa seletivamente as linhas que atendem às condições de filtro especificadas, melhorando o desempenho da pesquisa.

No exemplo a seguir, a tabela Documents2 tem uma coluna chamada Category. Na nossa pesquisa vetorial, queremos indexar a categoria "Tecnologia". Por isso, criamos uma coluna gerada que resulta em 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 de vetor TechDocEmbeddingIndex indexa apenas documentos na categoria "Tecnologia".

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 a TechDocEmbeddingIndex, ele escolhe e é acelerado automaticamente por TechDocEmbeddingIndex. A consulta pesquisa apenas documentos na categoria "Tecnologia". Você também pode 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 o desempenho da consulta, inclua colunas de chave não incorporadas no seu índice de vetor. Isso permite que o mecanismo de consulta realize a filtragem de maneira mais eficiente durante a pesquisa vetorial.

Na instrução de criação do índice, liste essas colunas de chave adicionais depois da coluna de incorporação. Por exemplo, a instrução a seguir cria um índice de vetores 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