Gerenciar índices vetoriais

Para enviar feedback ou solicitar suporte para esse recurso, envie um e-mail para bq-vector-search@google.com.

Neste documento, descrevemos como criar e gerenciar índices vetoriais.

Um índice vetorial é uma estrutura de dados projetada para permitir que a função VECTOR_SEARCH execute uma pesquisa vetorial mais eficiente de embeddings. Quando VECTOR_SEARCH pode usar um índice vetorial, a função usa a técnica de pesquisa do Vizinho aproximado mais perto para melhorar o desempenho da pesquisa, mas reduzindo o recall e, portanto, retornando resultados mais aproximados.

Papéis e permissões

Para criar um índice vetorial, é necessário ter a permissão bigquery.tables.createIndex do IAM na tabela em que você está criando o índice. Para remover um índice vetorial, é necessário ter a permissão bigquery.tables.deleteIndex. Cada um dos papéis do IAM predefinidos a seguir inclui as permissões necessárias para trabalhar com índices vetoriais:

  • Proprietário de dados do BigQuery (roles/bigquery.dataOwner)
  • Editor de dados do BigQuery (roles/bigquery.dataEditor)

Criar um índice vetorial

Para criar um índice de vetor, use a instrução de linguagem de definição de dados (DDL, na sigla em inglês) CREATE VECTOR INDEX:

  1. Acessar a página do BigQuery.

    Acessar o BigQuery

  2. No editor de consultas, execute a seguinte instrução SQL:

    CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
    ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
    OPTIONS(index_type = INDEX_TYPE,
      distance_type = DISTANCE_TYPE,
      ivf_options = '{"num_lists":NUM_LISTS}')
    

    Substitua:

    • INDEX_NAME: o nome do índice vetorial que você está criando. Como o índice é sempre criado no mesmo projeto e conjunto de dados da tabela base, não é necessário especificá-los no nome.
    • DATASET_NAME: o nome do conjunto de dados que contém a tabela.
    • TABLE_NAME: o nome da tabela que contém a coluna com dados de embeddings.
    • COLUMN_NAME: o nome de uma coluna que contém os dados de embeddings. A coluna precisa ter um tipo de ARRAY<FLOAT64>. A coluna não pode ter campos filhos. Todos os elementos na matriz precisam ser diferentes de NULL, e todos os valores na coluna precisam ter as mesmas dimensões da matriz.
    • INDEX_TYPE: o algoritmo a ser usado para criar o índice vetorial. IVF é o único valor compatível. Especificar IVF cria o índice vetorial como índice de arquivo invertido (IVF, na sigla em inglês). Um IVF usa um algoritmo k-means para agrupar em clusters os dados vetoriais e, em seguida, particiona esses dados com base nesses clusters. Quando você usa a função VECTOR_SEARCH para pesquisar os dados vetoriais, ela pode usar essas partições para reduzir a quantidade de dados que precisa ler a fim de determinar um resultado.
    • DISTANCE_TYPE: especifica o tipo de distância padrão a ser usado na pesquisa de vetor com esse índice. Os valores compatíveis são EUCLIDEAN e COSINE. O padrão é EUCLIDEAN.

      A criação do índice sempre usa a distância EUCLIDEAN para o treinamento, mas a distância usada na função VECTOR_SEARCH pode ser diferente.

      Se você especificar um valor para o argumento distance_type da função VECTOR_SEARCH, esse valor será usado no lugar de DISTANCE_TYPE.

    • NUM_LISTS: um valor de INT64 menor ou igual a 5.000 que determina quantas listas o algoritmo de IVF cria. O algoritmo de IVF divide todo o espaço de dados em um número de listas igual a NUM_LISTS. Assim, os pontos de dados mais próximos uns dos outros têm mais chances de serem colocados na mesma lista. Se NUM_LISTS for pequeno, você terá menos listas com mais pontos de dados, enquanto um valor maior criará mais listas com menos pontos de dados.

      É possível usar NUM_LISTS em combinação com o argumento fraction_lists_to_search na função VECTOR_SEARCH para criar uma pesquisa de vetor eficiente. Se você tiver dados distribuídos em muitos grupos pequenos no espaço de embedding, especifique um NUM_LISTS alto para criar um índice com mais listas e especifique um valor fraction_lists_to_search menor para verificar menos listas na pesquisa de vetor. Use um valor NUM_LISTS menor e um valor fraction_lists_to_search maior quando seus dados forem distribuídos em menos grupos maiores. Usar um valor num_lists alto pode fazer com que o índice vetorial demore mais para ser criado.

      Se você não especificar NUM_LISTS, o BigQuery calculará um valor apropriado.

O exemplo a seguir cria um índice vetorial na coluna embedding de my_table:

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF');

O exemplo a seguir cria um índice vetorial na coluna embedding de my_table e especifica o tipo de distância a ser usado e as opções de IVF:

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF', distance_type = 'COSINE',
ivf_options = '{"num_lists": 2500}')

A atualização do índice

Os índices vetoriais são totalmente gerenciados pelo BigQuery e atualizados automaticamente quando a tabela indexada é alterada. Se você excluir a coluna indexada de uma tabela ou renomear essa tabela, o índice vetorial será excluído automaticamente.

Se você criar um índice vetorial em uma tabela com menos de 10 MB, ele não será preenchido. Da mesma forma, se você excluir dados de uma tabela indexada e o tamanho dela ficar abaixo de 10 MB, o índice vetorial será desativado temporariamente. Nesse caso, as consultas de pesquisa de vetor não usam o índice, e o código indexUnusedReasons na seção vectorSearchStatistics do recurso Job é BASE_TABLE_TOO_SMALL. Sem o índice, VECTOR_SEARCH volta automaticamente a usar força bruta para encontrar os vizinhos de embeddings mais perto.

As consultas que usam a função VECTOR_SEARCH sempre retornam resultados corretos, mesmo que parte dos dados ainda não tenha sido indexada.

Encontrar informações sobre índices vetoriais

É possível verificar a existência e a prontidão de um índice vetorial consultando INFORMATION_SCHEMA. As visualizações a seguir contêm metadados sobre índices vetoriais:

  • A visualização INFORMATION_SCHEMA.VECTOR_INDEXES tem informações sobre os índices vetoriais em um conjunto de dados.

    Depois que a instrução CREATE VECTOR INDEX for concluída, o índice ainda precisará ser preenchido antes de ser usado. É possível usar as colunas last_refresh_time e coverage_percentage para verificar a prontidão de um índice vetorial. Se o índice vetorial não estiver pronto, ainda será possível usar a função VECTOR_SEARCH em uma tabela. Talvez ela seja executada mais lentamente sem o índice.

  • A visualização INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS tem informações sobre as colunas indexadas por vetor de todas as tabelas em um conjunto de dados.

  • A visualização INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS tem informações sobre as opções usadas pelos índices vetoriais em um conjunto de dados.

Exemplos de índice vetorial

O exemplo a seguir mostra todos os índices vetoriais ativos das tabelas no conjunto de dados my_dataset, localizado no projeto my_project. Isso inclui os nomes, as instruções DDL usadas para criá-los e a porcentagem de cobertura. Se uma tabela base indexada for menor que 10 MB, o índice não será preenchido. Nesse caso, o valor de coverage_percentage será 0.

SELECT table_name, index_name, ddl, coverage_percentage
FROM my_project.my_dataset.INFORMATION_SCHEMA.VECTOR_INDEXES
WHERE index_status = 'ACTIVE';

O resultado será semelhante ao seguinte:

+-------------+-------------+-----------------------------------------------------------------------------------------------+---------------------+
| table_name  | index_name  | ddl                                                                                           | coverage_percentage |
+-------------+-------------+-----------------------------------------------------------------------------------------------+---------------------+
| small_table | myindex1    | CREATE VECTOR INDEX `myindex1` ON `my_project.my_dataset.small_table`(embeddings)             | 100                 |
|             |             | OPTIONS (distance_type = 'EUCLIDEAN', index_type = 'IVF', ivf_options = '{"numLists": 3}')    |                     |
+-------------+-------------+-----------------------------------------------------------------------------------------------+---------------------+
| large_table | myindex2    | CREATE VECTOR INDEX `myindex2` ON `my_project.my_dataset.large_table`(vectors)                |  42                 |
|             |             | OPTIONS (distance_type = 'EUCLIDEAN', index_type = 'IVF', ivf_options = '{"numLists": 12}')   |                     |
+-------------+-------------+-----------------------------------------------------------------------------------------------+---------------------+

Exemplos de colunas de índice vetorial

A consulta a seguir extrai informações de colunas que têm índices vetoriais:

SELECT table_name, index_name, index_column_name, index_field_path
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS;

O resultado será semelhante ao seguinte:

+------------+------------+-------------------+------------------+
| table_name | index_name | index_column_name | index_field_path |
+------------+------------+-------------------+------------------+
| table1     | indexa     | a                 | a                |
| table2     | indexb     | b                 | b                |
| table3     | indexc     | c                 | c                |
+------------+------------+-------------------+------------------+

Exemplos de opções de índice vetorial

A consulta a seguir extrai informações de opções de índice vetorial:

SELECT table_name, index_name, option_name, option_type, option_value
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS;

O resultado será semelhante ao seguinte:

+------------+------------+------------------+------------------+--------------------+
| table_name | index_name | option_name      | option_type      | option_value       |
+------------+------------+------------------+------------------+--------------------+
| table1     | indexa     | distance_type    | STRING           | EUCLIDEAN          |
| table1     | indexa     | index_type       | STRING           | IVF                |
| table2     | indexb     | ivf_options      | STRING           | {"num_lists": 100} |
| table2     | indexb     | index_type       | STRING           | IVF                |
+------------+------------+------------------+------------------+--------------------+

Uso do índice vetorial

As informações sobre o uso do índice vetorial estão disponíveis nos metadados do job que executou a consulta de pesquisa de vetor. Para ver os metadados do job, use o console do Google Cloud, a ferramenta de linha de comando bq, a API BigQuery ou as bibliotecas de cliente.

Ao usar o console do Google Cloud, é possível encontrar informações sobre o uso do índice vetorial nos campos Modo de uso do índice vetorial e Motivos da não utilização do índice vetorial.

Ao usar a ferramenta bq ou a API BigQuery, você encontra informações sobre o uso do índice vetorial na seção VectorSearchStatistics do recurso Job.

O modo de uso do índice indica se um índice vetorial foi usado fornecendo um dos seguintes valores:

  • UNUSED: nenhum índice vetorial foi usado.
  • PARTIALLY_USED: algumas funções VECTOR_SEARCH na consulta usaram índices vetoriais e outras não.
  • FULLY_USED: cada função VECTOR_SEARCH na consulta usou um índice vetorial.

Quando o valor do modo de uso do índice é UNUSED ou PARTIALLY_USED, os motivos da não utilização do índice indicam por que os índices vetoriais não foram usados na consulta.

Por exemplo, os resultados a seguir retornados por bq show --format=prettyjson -j my_job_id mostram que o índice não foi usado porque a opção use_brute_force foi especificada na função VECTOR_SEARCH:

"vectorSearchStatistics": {
  "indexUnusedReasons": [
    {
      "baseTable": {
        "datasetId": "my_dataset",
        "projectId": "my_project",
        "tableId": "my_table"
      },
      "code": "INDEX_SUPPRESSED_BY_FUNCTION_OPTION",
      "message": "No vector index was used for the base table `my_project:my_dataset.my_table` because use_brute_force option has been specified."
    }
  ],
  "indexUsageMode": "UNUSED"
}

Opções de gerenciamento de índice

Para criar índices vetoriais e fazer o BigQuery mantê-los, há duas opções:

  • Use o pool de slots compartilhado padrão: quando os dados que você planeja indexar estiverem abaixo do limite por organização, será possível usar o pool de slots compartilhado para gerenciamento de índices.
  • Use sua própria reserva: para atingir um progresso de indexação mais previsível e consistente em cargas de trabalho de produção maiores, use suas próprias reservas para o gerenciamento de índices.

Usar slots compartilhados

Se você não tiver configurado o projeto para usar uma reserva dedicada para indexação, o gerenciamento de índices será processado no pool de slots compartilhado e sem custos financeiros, sujeito às restrições a seguir.

Se você adicionar dados a uma tabela base que faça com que o tamanho total das tabelas indexadas excedam o limite da sua organização, o BigQuery pausará o gerenciamento de índice em todas as tabelas indexadas. Quando isso acontece, o campo index_status na visualização INFORMATION_SCHEMA.VECTOR_INDEXES exibe PENDING DISABLEMENT e o índice é colocado em fila para exclusão. Embora o índice esteja com a desativação pendente, ele ainda é usado nas consultas e há cobrança pelo armazenamento do índice. Depois que um índice é excluído, o campo index_status mostra o índice como TEMPORARILY DISABLED. Nesse estado, as consultas não usam o índice e não há cobrança pelo armazenamento dele. Nesse caso, o código IndexUnusedReason é BASE_TABLE_TOO_LARGE.

Se você excluir os dados da tabela base e o tamanho total das tabelas base indexadas estiver abaixo do limite por organização, o gerenciamento de índice será retomado para todas as tabelas base indexadas. O campo index_status na visualização INFORMATION_SCHEMA.VECTOR_INDEXES é ACTIVE, as consultas podem usar o índice e há cobrança pelo armazenamento do índice.

O BigQuery não oferece garantias sobre a capacidade disponível do pool compartilhado ou a capacidade de indexação que você vê. Para aplicativos de produção, convém usar slots dedicados para o processamento de índice.

Usar sua própria reserva

Em vez de usar o pool de slots compartilhado padrão, é possível designar sua própria reserva para indexar as tabelas. Usar sua própria reserva garante um desempenho previsível e consistente de jobs de gerenciamento de índice, como criação, atualização e otimizações em segundo plano.

  • Não há limites de tamanho de tabela quando um job de indexação for executado na reserva.
  • O uso da sua própria reserva oferece flexibilidade no gerenciamento do índice. Se você precisar criar um índice muito grande ou fazer uma grande atualização para uma tabela indexada, adicione temporariamente mais slots à atribuição.

Para indexar as tabelas em um projeto com uma reserva designada, crie uma reserva na região onde as tabelas base estão localizadas. Em seguida, atribua o projeto à reserva com o job_type definido como BACKGROUND:

SQL

Use a instrução DDL CREATE ASSIGNMENT.

  1. No Console do Google Cloud, acesse a página BigQuery.

    Ir para o BigQuery

  2. No editor de consultas, digite a seguinte instrução:

    CREATE ASSIGNMENT
      `ADMIN_PROJECT_ID.region-LOCATION.RESERVATION_NAME.ASSIGNMENT_ID`
    OPTIONS (
      assignee = 'projects/PROJECT_ID',
      job_type = 'BACKGROUND');
    

    Substitua:

    • ADMIN_PROJECT_ID: o ID do projeto de administração que é proprietário do recurso de reserva.
    • LOCATION: o local da reserva
    • RESERVATION_NAME: o nome da reserva
    • ASSIGNMENT_ID: o ID da atribuição.

      Ele precisa ser exclusivo do projeto e do local, começar e terminar com uma letra minúscula ou um número e conter apenas letras minúsculas, números e traços.

    • PROJECT_ID: o ID do projeto que contém as tabelas a serem indexadas. Este projeto está atribuído à reserva.

  3. Clique em Executar.

Para mais informações sobre como executar consultas, acesse Executar uma consulta interativa.

bq

Use o comando bq mk:

bq mk \
    --project_id=ADMIN_PROJECT_ID \
    --location=LOCATION \
    --reservation_assignment \
    --reservation_id=RESERVATION_NAME \
    --assignee_id=PROJECT_ID \
    --job_type=BACKGROUND \
    --assignee_type=PROJECT

Substitua:

  • ADMIN_PROJECT_ID: o ID do projeto de administração que é proprietário do recurso de reserva.
  • LOCATION: o local da reserva
  • RESERVATION_NAME: o nome da reserva
  • PROJECT_ID: o ID do projeto a ser atribuído a essa reserva.

Veja seus jobs de indexação

Um novo job de indexação é criado sempre que um índice é criado ou atualizado em uma única tabela. Para ver informações sobre o job, consulte as visualizações INFORMATION_SCHEMA.JOBS*. É possível filtrar por jobs de indexação definindo job_type IS NULL AND SEARCH(job_id, '`search_index`') na cláusula WHERE da sua consulta. O exemplo a seguir lista os cinco jobs de indexação mais recentes no projeto my_project:

SELECT *
FROM
 region-us.INFORMATION_SCHEMA.JOBS
WHERE
  project_id  = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
ORDER BY
 creation_time DESC
LIMIT 5;

Escolha o tamanho da reserva

Para escolher o número certo de slots para sua reserva, considere quando os jobs de gerenciamento de índice são executados, quantos slots eles usam e como é o uso ao longo do tempo. O BigQuery aciona um job de gerenciamento de índice nas seguintes situações:

  • Você cria um índice em uma tabela.
  • Os dados são modificados em uma tabela indexada.
  • O esquema de uma tabela muda, e isso afeta as colunas que são indexadas.
  • Os dados e metadados do índice são otimizados ou atualizados periodicamente.

O número de slots necessários para um job de gerenciamento de índice em uma tabela depende dos seguintes fatores:

  • o tamanho da tabela
  • a taxa de ingestão de dados para a tabela
  • a taxa de instruções DML aplicadas à tabela
  • o atraso aceitável para a criação e manutenção do índice
  • A complexidade do índice, normalmente determinada por atributos dos dados, como o número de termos duplicados
Monitore o uso e o progresso

A melhor maneira de avaliar o número de slots necessários para executar com eficiência os jobs de gerenciamento de índice é monitorar a utilização deles e ajustar o tamanho da reserva adequadamente. A consulta a seguir produz o uso diário de slots para jobs de gerenciamento de índice. Somente os últimos 30 dias são incluídos na região us-west1:

SELECT
  TIMESTAMP_TRUNC(job.creation_time, DAY) AS usage_date,
  -- Aggregate total_slots_ms used for index-management jobs in a day and divide
  -- by the number of milliseconds in a day. This value is most accurate for
  -- days with consistent slot usage.
  SAFE_DIVIDE(SUM(job.total_slot_ms), (1000 * 60 * 60 * 24)) AS average_daily_slot_usage
FROM
  `region-us-west1`.INFORMATION_SCHEMA.JOBS job
WHERE
  project_id = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
GROUP BY
  usage_date
ORDER BY
  usage_date DESC
limit 30;

Quando não há slots suficientes para executar jobs de gerenciamento de índice, um índice pode ficar dessincronizado com a tabela, e os jobs de indexação poderão falhar. Nesse caso, o BigQuery recria o índice do zero. Para evitar um índice não sincronizado, verifique se você tem slots suficientes para oferecer suporte a atualizações de índice da ingestão e da otimização de dados. Para mais informações sobre o monitoramento do uso do slot, consulte gráficos de recursos de administrador.

Excluir um índice vetorial

Quando você não precisar mais de um índice vetorial ou quiser alterar qual coluna será indexada em uma tabela, exclua o índice dessa tabela usando a instrução DDL DROP VECTOR INDEX.

Exemplo:

DROP VECTOR INDEX my_index ON my_dataset.indexed_table;

Se uma tabela indexada for excluída, o índice dela será excluído automaticamente.

A seguir