Esta página descreve como encontrar vizinhos mais próximos aproximados (ANN, na sigla em inglês), criar índices de vetores e consultar embeddings de vetores usando as seguintes funções de distância de ANN no Spanner:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
Quando um conjunto de dados é pequeno, você pode usar vizinhos k-mais próximos (KNN) para encontrar os vetores k-mais próximos exatos. No entanto, à medida que o conjunto de dados cresce, a latência e o custo de uma pesquisa de KNN também aumentam. É possível usar o ANN para encontrar os k-vizinhos mais próximos aproximados com latência e custo significativamente reduzidos.
Vizinhos k-mais próximos aproximados
Em uma pesquisa de ANN, os vetores k retornados não são os k vizinhos mais próximos verdadeiros porque a pesquisa de ANN calcula distâncias aproximadas e pode não analisar todos os vetores no conjunto de dados. Às vezes, alguns vetores que não estão entre os k vizinhos mais próximos são retornados. Isso é conhecido como perda de recall. A quantidade de perda de recall aceitável depende do caso de uso, mas, na maioria deles, perder um pouco de recall em troca de um desempenho melhor do banco de dados é uma troca aceitável.
Para mais detalhes sobre as funções de distância aproximada do Spanner, consulte:
APPROX_COSINE_DISTANCE
no GoogleSQLAPPROX_EUCLIDEAN_DISTANCE
no GoogleSQLAPPROX_DOT_PRODUCT
no GoogleSQL
Índice vetorial
O Spanner acelera as pesquisas de vetores de ANN usando um índice de vetor especializado. Esse índice usa o Vizinho mais próximo escalonável (ScaNN) da Google Research, 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
.
Limitações
- Não é possível pré-dividir índices vetoriais. Para mais informações, consulte Visão geral da pré-divisão.
Criar índice de vetor
Para otimizar o recall e a performance de um índice de vetor, recomendamos que você:
Crie o índice de vetor 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 Reconstruir o índice vetorial.
Use a cláusula
STORING
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 a performance da consulta. Recomendamos que você armazene uma coluna se ela for usada em uma condição de filtragem. Para mais informações sobre como usarSTORING
em um índice, consulte Criar um índice para verificações somente de índice.
Ao criar a tabela, a coluna de incorporação precisa ser uma matriz do tipo de dados FLOAT32
(recomendado) ou FLOAT64
e ter uma anotação vector_length, indicando a dimensão dos vetores.
A instrução DDL a seguir cria uma tabela Documents
com uma coluna de embedding DocEmbedding
com um comprimento de vetor:
CREATE TABLE Documents (
DocId INT64 NOT NULL,
...
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
Depois de preencher a tabela Documents
, crie um índice de vetor com uma árvore de dois níveis e 1.000 nós folha em uma tabela Documents
com uma coluna de embedding DocEmbedding
usando a distância de cosseno:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(DocEmbedding)
STORING (WordCount)
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
Para criar um índice de vetor com uma árvore de três níveis e 1.000.000 nós folha:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 3, num_branches=1000, num_leaves = 1000000);
Se a coluna de incorporação não estiver marcada como NOT NULL
na definição da tabela, declare-a com uma cláusula WHERE COLUMN_NAME IS NOT NULL
:
CREATE VECTOR INDEX DocEmbeddingIndex
ON Documents(NullableDocEmbedding)
STORING (WordCount)
WHERE NullableDocEmbedding IS NOT NULL
OPTIONS (distance_type = 'COSINE', tree_depth = 2, num_leaves = 1000);
Filtrar um índice vetorial
Também é possível criar um índice de vetor 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 Documents
tem uma coluna chamada Category
.
Na nossa pesquisa vetorial, queremos indexar a categoria "Tecnologia". Por isso, criamos uma coluna gerada que avalia como NULL se a condição de categoria não for atendida.
CREATE TABLE Documents (
DocId INT64 NOT NULL,
Category STRING(MAX),
NullIfFiltered BOOL AS (IF(Category = 'Tech', TRUE, NULL)) HIDDEN,
DocEmbedding ARRAY<FLOAT32>(vector_length=>128),
) PRIMARY KEY (DocId);
Em seguida, criamos um índice de vetor com um filtro. O índice de vetor TechDocEmbeddingIndex
indexa apenas documentos na categoria "Tecnologia".
CREATE VECTOR INDEX TechDocEmbeddingIndex
ON Documents(DocEmbedding)
STORING(NullIfFiltered)
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
OPTIONS (...);
Quando o Spanner executa a consulta a seguir, que tem filtros que correspondem ao TechDocEmbeddingIndex
, ele escolhe e é acelerado automaticamente pelo TechDocEmbeddingIndex
. A consulta pesquisa apenas documentos na categoria "Tecnologia". Também é possível usar {@FORCE_INDEX=TechDocEmbeddingIndex}
para forçar o Spanner a usar TechDocEmbeddingIndex
explicitamente.
SELECT *
FROM Documents
WHERE DocEmbedding IS NOT NULL AND NullIfFiltered IS NOT NULL
ORDER BY APPROX_(....)
LIMIT 10;
Consultar embeddings de vetor
Para consultar um índice vetorial, use uma das três funções de distância aproximada:
APPROX_COSINE_DISTANCE
APPROX_EUCLIDEAN_DISTANCE
APPROX_DOT_PRODUCT
As restrições ao usar as funções de distância aproximada incluem:
- A função de distância aproximada precisa calcular a distância entre uma coluna de incorporação e uma expressão constante (por exemplo, um parâmetro ou um literal).
- A saída da função de distância aproximada precisa ser usada em uma cláusula
ORDER BY
como a única chave de classificação, e umLIMIT
precisa ser especificado após oORDER BY
. - A consulta precisa filtrar explicitamente as linhas que não estão indexadas. Na maioria dos casos, isso significa que a consulta precisa incluir uma cláusula
WHERE <column_name> IS NOT NULL
que corresponda à definição do índice de vetor, a menos que a coluna já esteja marcada comoNOT NULL
na definição da tabela.
Para uma lista detalhada de limitações, consulte a página de referência da função de distância aproximada.
Exemplos
Para pesquisar os 100 vetores mais próximos de [1.0, 2.0, 3.0]
:
SELECT DocId
FROM Documents
WHERE WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], DocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
Se a coluna de incorporação for anulável:
SELECT DocId
FROM Documents
WHERE NullableDocEmbedding IS NOT NULL AND WordCount > 1000
ORDER BY APPROX_EUCLIDEAN_DISTANCE(
ARRAY<FLOAT32>[1.0, 2.0, 3.0], NullableDocEmbedding,
options => JSON '{"num_leaves_to_search": 10}')
LIMIT 100
Práticas recomendadas
Siga estas práticas recomendadas para otimizar seus índices de vetores e melhorar os resultados da consulta.
Ajustar as opções de pesquisa vetorial
O valor ideal da pesquisa de vetor depende do caso de uso, do conjunto de dados de vetor e dos vetores de consulta. Talvez seja necessário realizar um ajuste iterativo para encontrar os melhores valores para sua carga de trabalho específica.
Confira algumas diretrizes úteis para escolher valores adequados:
tree_depth
(nível da árvore): se a tabela indexada tiver menos de 10 milhões de linhas, use umtree_depth
de2
. Caso contrário, umtree_depth
de3
aceita tabelas de até 10 bilhões de linhas.num_leaves
: use a raiz quadrada do número de linhas no conjunto de dados. Um valor maior pode aumentar o tempo de criação do índice de vetor. Evite definirnum_leaves
maior quetable_row_count/1000
, porque isso resulta em folhas muito pequenas e desempenho ruim.num_leaves_to_search
: essa opção especifica quantos nós folha do índice são pesquisados. Aumentarnum_leaves_to_search
melhora o recall, mas também aumenta a latência e o custo. Recomendamos usar um número que seja 1% do número total de folhas definidas na instruçãoCREATE VECTOR INDEX
como o valor denum_leaves_to_search
. Se você estiver usando uma cláusula de filtro, aumente esse valor para ampliar a pesquisa.
Se um recall aceitável for alcançado, mas o custo da consulta for muito alto, resultando em um QPS máximo baixo, tente aumentar num_leaves
seguindo estas etapas:
- Defina
num_leaves
como um múltiplo k do valor original (por exemplo,2 * sqrt(table_row_count)
). - Defina
num_leaves_to_search
como o mesmo múltiplo k do valor original. - Teste reduzir
num_leaves_to_search
para melhorar o custo e o QPS, mantendo o recall.
Melhorar o recall
Há várias possibilidades de piora do recall, incluindo:
num_leaves_to_search
é muito pequeno: pode ser mais difícil encontrar os vizinhos mais próximos para alguns vetores de consulta. Aumentarnum_leaves_to_search
para pesquisar mais folhas pode ajudar a melhorar o recall. As consultas recentes podem ter mudado para conter mais desses vetores desafiadores.O índice de vetor precisa ser recriado: a estrutura de árvore do índice de vetor é otimizada para o conjunto de dados no momento da criação e permanece estática depois disso. Portanto, se vetores significativamente diferentes forem adicionados após a criação do índice de vetor inicial, a estrutura de árvore poderá ser inadequada, resultando em recall menor.
Recriar o índice de vetor
Para recriar o índice de vetores sem inatividade:
- Crie um novo índice vetorial na mesma coluna de embedding do índice vetorial atual, atualizando os parâmetros (por exemplo,
OPTIONS
) conforme necessário. - Depois que a criação do índice for concluída, use a dica
FORCE_INDEX
para apontar para o novo índice e atualizar a consulta de pesquisa vetorial. Isso garante que a consulta use o novo índice de vetor. Talvez também seja necessário ajustarnum_leaves_to_search
na nova consulta. - Remova o índice vetorial desatualizado.
A seguir
Saiba mais sobre as funções GoogleSQL
APPROXIMATE_COSINE_DISTANCE()
,APPROXIMATE_EUCLIDEAN_DISTANCE()
,APPROXIMATE_DOT_PRODUCT()
.Saiba mais sobre as instruções
VECTOR INDEX
do GoogleSQL.Confira o Guia de início rápido da pesquisa de vetores do Spanner para um exemplo detalhado de como usar a ANN.