Ajustar a performance da consulta de vetor

Este documento mostra como ajustar seus índices para melhorar a performance da consulta e a recuperação.

Ajustar um índice ScaNN

O índice ScaNN usa indexação baseada em quantização de árvore. Em técnicas de quantização em árvore, os índices aprendem uma árvore de pesquisa com uma função de quantização (ou hash). Quando você executa uma consulta, a árvore de pesquisa é usada para podar o espaço de pesquisa, enquanto a quantização é usada para compactar o tamanho do índice. Essa poda acelera a pontuação da similaridade (ou seja, distância) entre o vetor de consulta e os vetores do banco de dados.

Para alcançar uma alta taxa de consultas por segundo (QPS) e uma alta recuperação com as consultas de vizinho mais próximo, é necessário particionar a árvore do índice ScaNN de uma forma mais adequada aos seus dados e consultas.

Antes de criar um índice ScaNN, faça o seguinte:

  • Verifique se uma tabela com seus dados já foi criada.
  • Verifique se o valor definido para a flag maintenance_work_mem e a shared_buffers é menor do que a memória total da máquina para evitar problemas ao gerar o índice.

Parâmetros de ajuste

Os parâmetros de índice e as flags do banco de dados a seguir são usados juntos para encontrar o equilíbrio certo de recuperação e QPS. Todos os parâmetros se aplicam aos dois tipos de índice ScaNN.

Parâmetro de ajuste Descrição Tipo de parâmetro
num_leaves O número de partições a serem aplicadas a este índice. O número de partições em que você aplica ao criar um índice afeta a performance do índice. Ao aumentar as partições para um número definido de vetores, você cria um índice mais refinado, o que melhora a recuperação e a performance da consulta. No entanto, isso tem o custo de tempos mais longos de criação de índices.

Como as árvores de três níveis são criadas mais rapidamente do que as de dois níveis, é possível aumentar o num_leaves_value ao criar um índice de árvore de três níveis para ter um desempenho melhor.
  • Índice de dois níveis: defina esse valor como qualquer valor entre 1 e 1048576.

    Se você não tiver certeza sobre a seleção do valor exato, use sqrt(ROWS) como ponto de partida, em que ROWS é o número de linhas do vetor. O número de vetores que cada partição contém é calculado por
    ROWS/sqrt(ROWS) = sqrt(ROWS).

    Como é possível criar um índice de árvore de dois níveis em um conjunto de dados com menos de 10 milhões de linhas de vetor, cada partição vai conter menos de (sqrt(10M)) vetores, ou seja, 3200. Para um desempenho ideal, é recomendável minimizar o número de vetores em cada partição.
  • Índice de três níveis: defina esse valor como qualquer valor entre 1 e 1048576.

    Se você não tiver certeza sobre a seleção do valor exato, use power(ROWS, 2/3) como ponto de partida, em que ROWS é o número de linhas do vetor. O número de vetores que cada partição contém é calculado por
    ROWS/power(ROWS, 2/3) = power(ROWS, 1/3).

    Como um índice de árvore de três níveis pode ser criado em um conjunto de dados com mais de 100 milhões de linhas de vetor, cada partição vai conter mais de
    (power(100M, 1/3)), ou seja, 465. Para um desempenho ideal, é recomendável minimizar o número de vetores em cada partição.
Criação de índice
quantizer O tipo de quantizador que você quer usar para a árvore K-means. O valor padrão é SQ8 para melhorar o desempenho da consulta.

Defina como FLAT para melhorar a recuperação.
Criação de índice
enable_pca Ativa a análise de componentes principais (PCA, na sigla em inglês), que é uma técnica de redução de dimensão usada para reduzir automaticamente o tamanho da incorporação quando possível. Essa opção é ativada por padrão.

Defina como false se você notar uma deterioração na recuperação.
Criação de índice
scann.num_leaves_to_search O sinalizador do banco de dados controla a compensação entre o recall e o QPS. O valor padrão é 1% do valor definido em num_leaves.

Quanto maior o valor definido, melhor é o recall, mas isso resulta em QPS menor e vice-versa.
Tempo de execução da consulta
scann.max_top_neighbors_buffer_size A flag do banco de dados especifica o tamanho do cache usado para melhorar a performance das consultas filtradas ao pontuar ou classificar os vizinhos candidatos verificados na memória em vez do disco. O valor padrão é 20000.

Quanto maior o valor definido, melhor é o QPS em consultas filtradas, mas isso resulta em maior uso de memória e vice-versa.
Tempo de execução da consulta
scann.pre_reordering_num_neighbors A flag do banco de dados, quando definida, especifica o número de vizinhos candidatos a serem considerados durante as fases de reordenação depois que a pesquisa inicial identifica um conjunto de candidatos. Defina um valor maior do que o número de vizinhos que você quer que a consulta retorne.

Conjuntos de valores mais altos resultam em um recall melhor, mas essa abordagem resulta em QPS mais baixo.
Tempo de execução da consulta
max_num_levels O número máximo de níveis da árvore de clusterização K-means.
  • Índice de árvore de dois níveis: definido por padrão para quantização baseada em árvore de dois níveis.
  • Índice de árvore de três níveis: defina como 2 explicitamente para quantização baseada em árvore de três níveis.
Criação de índice

Ajustar um índice ScaNN

Confira os exemplos a seguir de índices ScaNN de dois e três níveis que mostram como os parâmetros de ajuste são definidos:

Índice de dois níveis

SET LOCAL scann.num_leaves_to_search = 1;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 1/2)]);

Índice de três níveis

SET LOCAL scann.num_leaves_to_search = 10;
SET LOCAL scann.pre_reordering_num_neighbors=50;

CREATE INDEX my-scann-index ON my-table
  USING scann (vector_column cosine)
  WITH (num_leaves = [power(1000000, 2/3)], max_num_levels = 2);

Qualquer operação de inserção ou atualização em uma tabela em que um índice ScaNN já foi gerado afeta a forma como a árvore aprendida otimiza o índice. Se a tabela for propensa a atualizações ou inserções frequentes, recomendamos reindexar periodicamente o índice ScaNN para melhorar a precisão da recuperação.

É possível monitorar as métricas de índice para determinar a quantidade de mutações criadas desde a criação do índice e, em seguida, reindexar de acordo. Para mais informações sobre métricas, consulte Métricas de índice vetorial.

Práticas recomendadas para ajuste

Com base no tipo de índice ScaNN que você pretende usar, as recomendações para ajustar o índice variam. Esta seção fornece recomendações sobre como ajustar os parâmetros de índice para um equilíbrio ideal entre recuperação e QPS.

Índice de árvore de dois níveis

Para aplicar recomendações e encontrar os valores ideais de num_leaves e num_leaves_to_search para seu conjunto de dados, siga estas etapas:

  1. Crie o índice ScaNN com num_leaves definido como a raiz quadrada da contagem de linhas da tabela indexada.
  2. Execute as consultas de teste, aumentando o valor de scann.num_of_leaves_to_search, até atingir o intervalo de recuperação desejado, por exemplo, 95%. Para mais informações sobre como analisar suas consultas, consulte Analisar suas consultas.
  3. Anote a proporção entre scann.num_leaves_to_search e num_leaves que será usada nas próximas etapas. Essa proporção fornece uma aproximação do conjunto de dados que vai ajudar você a alcançar a recuperação desejada.

    Se você estiver trabalhando com vetores de alta dimensão (500 ou mais) e quiser melhorar a recuperação, tente ajustar o valor de scann.pre_reordering_num_neighbors. Como ponto de partida, defina o valor como 100 * sqrt(K), em que K é o limite definido na consulta.
  4. Se a QPS estiver muito baixa depois que as consultas atingirem uma recuperação desejada, siga estas etapas:
    1. Recrie o índice, aumentando o valor de num_leaves e scann.num_leaves_to_search de acordo com as seguintes orientações:
      • Defina num_leaves como um fator maior da raiz quadrada da contagem de linhas. Por exemplo, se o índice tiver num_leaves definido como a raiz quadrada da contagem de linhas, tente definir o dobro da raiz quadrada. Se o valor já for o dobro, tente definir o valor para triplicar a raiz quadrada.
      • Aumente scann.num_leaves_to_search conforme necessário para manter a proporção com num_leaves, que você anotou na etapa 3.
      • Defina num_leaves como um valor menor ou igual à contagem de linhas dividida por 100.
    2. Execute as consultas de teste novamente. Enquanto executa as consultas de teste, tente reduzir scann.num_leaves_to_search, encontrando um valor que aumente a QPS mantendo o recall alto. Tente valores diferentes de scann.num_leaves_to_search sem reconstruir o índice.
  5. Repita a etapa 4 até que a QPS e o intervalo de recuperação atinjam valores aceitáveis.

Índice de árvore de três níveis

Além das recomendações para o índice ScaNN de árvore de dois níveis, use as orientações e as etapas a seguir para ajustar o índice:

  • Aumentar o max_num_levels de 1 para uma árvore de dois níveis para 2 para uma árvore de três níveis reduz significativamente o tempo de criação de um índice, mas à custa da precisão de recuperação. Defina max_num_levels usando a seguinte recomendação:
    • Defina o valor como 2 se o número de linhas do vetor exceder 100 milhões.
    • Defina o valor como 1 se o número de linhas do vetor for inferior a 10 milhões.
    • Defina como 1 ou 2 se o número de linhas do vetor estiver entre 10 milhões e 100 milhões, com base no equilíbrio do tempo de criação do índice e na precisão de recuperação necessária.

Para aplicar recomendações e encontrar o valor ideal dos parâmetros de índice num_leaves e max_num_levels, siga estas etapas:

  1. Crie o índice ScaNN com as seguintes combinações de num_leaves e max_num_levels com base no seu conjunto de dados:

    • Linhas de vetor maiores que 100 milhões: defina max_num_levels como 2 e num_leaves como power(rows, ⅔).
    • Linhas de vetor com menos de 100 milhões de linhas: defina max_num_levels como 1 e num_leaves como sqrt(rows).
    • Linhas de vetor entre 10 milhões e 100 milhões: comece definindo max_num_levels como 1 e num_leaves como sqrt(rows).
  2. Execute as consultas de teste. Para mais informações sobre como analisar consultas, consulte Analisar suas consultas.

    Se o tempo de criação do índice for satisfatório, mantenha o valor max_num_levels e teste o valor num_leaves para uma precisão de recuperação ideal.

  3. Se você não estiver satisfeito com o tempo de criação do índice, faça o seguinte:

    • Se o valor max_num_levels for 1, exclua o índice. Recrie o índice com o valor max_num_levels definido como 2.

      Execute as consultas e ajuste o valor de num_leaves para ter a precisão de recuperação ideal.

    • Se o valor de max_num_levels for 2, exclua o índice. Recrie o índice com o mesmo valor de max_num_levels e ajuste o valor de num_leaves para uma precisão de recuperação ideal.

Ajustar um índice IVF

Ajustar os valores definidos para os parâmetros lists, ivf.probes e quantizer pode ajudar a otimizar o desempenho do seu aplicativo:

Parâmetro de ajuste Descrição Tipo de parâmetro
lists O número de listas criadas durante a criação do índice. O ponto de partida para definir esse valor é (rows)/1000 para até um milhão de linhas e sqrt(rows) para mais de um milhão de linhas. Criação de índice
quantizer O tipo de quantizador que você quer usar para a árvore K-means. O valor padrão é SQ8 para melhorar a performance da consulta. Defina como FLAT para melhorar a recuperação. Criação de índice
ivf.probes o número de listas mais próximas para explorar durante a pesquisa. O ponto de partida desse valor é
sqrt(lists).
Tempo de execução da consulta

Considere o exemplo a seguir, que mostra um índice IVF com os parâmetros de ajuste definidos:

SET LOCAL ivf.probes = 10;

CREATE INDEX my-ivf-index ON my-table
  USING ivf (vector_column cosine)
  WITH (lists = 100, quantizer = 'SQ8');

Ajustar um índice IVFFlat

Ajustar os valores definidos para os parâmetros lists e ivfflat.probes pode ajudar a otimizar o desempenho do aplicativo:

Parâmetro de ajuste Descrição Tipo de parâmetro
lists O número de listas criadas durante a criação do índice. O ponto de partida para definir esse valor é (rows)/1000 para até um milhão de linhas e sqrt(rows) para mais de um milhão de linhas. Criação de índice
ivfflat.probes O número de listas mais próximas para explorar durante a pesquisa. O ponto de partida desse valor é
sqrt(lists).
Tempo de execução da consulta

Antes de criar um índice IVFFlat, verifique se a flag max_parallel_maintenance_workers do seu banco de dados está definida com um valor suficiente para agilizar a criação de índices em tabelas grandes.

Considere o exemplo a seguir, que mostra um índice IVFFlat com os parâmetros de ajuste definidos:

SET LOCAL ivfflat.probes = 10;

CREATE INDEX my-ivfflat-index ON my-table
  USING ivfflat (vector_column cosine)
  WITH (lists = 100);

Ajustar um índice HNSW

Ajustar os valores definidos para os parâmetros m, ef_construction e hnsw.ef_search pode ajudar a otimizar o desempenho do aplicativo.

Parâmetro de ajuste Descrição Tipo de parâmetro
m O número máximo de conexões por nó no gráfico. Você pode começar com o valor padrão 16(padrão) e testar valores mais altos com base no tamanho do conjunto de dados. Criação de índice
ef_construction O tamanho da lista de candidatos dinâmicos mantida durante a construção do gráfico, que atualiza constantemente os melhores candidatos atuais para vizinhos mais próximos de um nó. Defina esse valor como qualquer valor maior que o dobro do valor de m. Por exemplo, 64(padrão). Criação de índice
ef_search O tamanho da lista dinâmica de candidatos usada durante a pesquisa. Você pode começar a definir esse valor como m ou ef_construction e, em seguida, mudar enquanto observa a recuperação. O valor padrão é 40. Tempo de execução da consulta

Considere o exemplo a seguir, que mostra um índice hnsw com os parâmetros de ajuste definidos:

SET LOCAL hnsw.ef_search = 40;

CREATE INDEX my-hnsw-index ON my-table
  USING hnsw (vector_column cosine)
  WITH (m = 16, ef_construction = 200);

Analisar suas consultas

Use o comando EXPLAIN ANALYZE para analisar os insights da consulta, conforme mostrado no exemplo de consulta SQL a seguir.

  EXPLAIN ANALYZE SELECT result-column FROM my-table
    ORDER BY EMBEDDING_COLUMN ::vector
    USING INDEX my-scann-index
    <-> embedding('textembedding-gecko@003', 'What is a database?')
    LIMIT 1;

O exemplo de resposta QUERY PLAN inclui informações como o tempo gasto, o número de linhas digitalizadas ou retornadas e os recursos usados.

Limit  (cost=0.42..15.27 rows=1 width=32) (actual time=0.106..0.132 rows=1 loops=1)
  ->  Index Scan using my-scann-index on my-table  (cost=0.42..858027.93 rows=100000 width=32) (actual time=0.105..0.129 rows=1 loops=1)
        Order By: (embedding_column <-> embedding('textgecko@003', 'What is a database?')::vector(768))
        Limit value: 1
Planning Time: 0.354 ms
Execution Time: 0.141 ms

Conferir métricas do índice vetorial

É possível usar as métricas do índice de vetor para analisar a performance do índice, identificar áreas para melhoria e ajustar o índice com base nas métricas, se necessário.

Para conferir todas as métricas do índice de vetor, execute a consulta SQL abaixo, que usa a visualização pg_stat_ann_indexes:

SELECT * FROM pg_stat_ann_indexes;

Para mais informações sobre a lista completa de métricas, consulte Métricas de índice vetorial.

A seguir