Skip to main content
O problema de encontrar os N pontos mais próximos, em um espaço multidimensional (vetorial), para um determinado ponto é conhecido como busca do vizinho mais próximo ou, resumidamente, busca vetorial. Existem duas abordagens gerais para resolver a busca vetorial:
  • A busca vetorial exata calcula a distância entre o ponto fornecido e todos os pontos do espaço vetorial. Isso garante a melhor precisão possível, ou seja, os pontos retornados são, de fato, os vizinhos mais próximos. Como o espaço vetorial é percorrido exaustivamente, a busca vetorial exata pode ser lenta demais para uso em cenários reais.
  • A busca vetorial aproximada se refere a um conjunto de técnicas (por exemplo, estruturas de dados especiais, como grafos e florestas aleatórias) que calculam resultados muito mais rapidamente do que a busca vetorial exata. A precisão do resultado normalmente é “boa o suficiente” para uso prático. Muitas técnicas aproximadas oferecem parâmetros para ajustar o equilíbrio entre a precisão do resultado e o tempo de busca.
Uma busca vetorial (exata ou aproximada) pode ser escrita em SQL da seguinte forma:
Os pontos no espaço vetorial são armazenados em uma coluna vectors do tipo array, por exemplo, Array(Float64), Array(Float32) ou Array(BFloat16). O vetor de referência é um array constante e é definido como uma expressão de tabela comum. <DistanceFunction> calcula a distância entre o ponto de referência e todos os pontos armazenados. Qualquer uma das funções de distância disponíveis pode ser usada para isso. <N> especifica quantos vizinhos devem ser retornados. Uma busca vetorial exata pode ser realizada usando a consulta SELECT acima, sem alterações. O tempo de execução dessas consultas geralmente é proporcional ao número de vetores armazenados e à sua dimensão, ou seja, ao número de elementos do Array. Além disso, como o ClickHouse faz uma varredura por força bruta de todos os vetores, o tempo de execução também depende do número de threads usadas pela consulta (consulte a configuração max_threads).

Exemplo

retorna

Índices de similaridade vetorial

O ClickHouse fornece um índice especial de “similaridade vetorial” para realizar busca vetorial aproximada.
Os índices de similaridade vetorial estão disponíveis no ClickHouse versão 25.8 ou superior. Se você encontrar problemas, abra uma issue no repositório do ClickHouse.

Como criar um índice de similaridade vetorial

Um índice de similaridade vetorial pode ser criado em uma nova tabela da seguinte forma:
Alternativamente, para adicionar um índice de similaridade vetorial a uma tabela existente:
Índices de similaridade vetorial são tipos especiais de índices de skipping (veja aqui e aqui). Assim, a instrução ALTER TABLE acima faz com que o índice seja criado apenas para dados inseridos futuramente na tabela. Para criar o índice também para os dados existentes, você precisa materializá-lo:
A função <distance_function> deve ser
  • L2Distance, a distância euclidiana, que representa o comprimento do segmento de reta entre dois pontos no espaço euclidiano,
  • cosineDistance, a distância de cosseno, que representa o ângulo entre dois vetores não nulos, ou
  • dotProduct, o produto escalar (produto interno), que representa a soma dos produtos elemento a elemento de dois vetores. Equivalente a cosineDistance em dados normalizados.
Para dados normalizados, L2Distance geralmente é a melhor escolha; caso contrário, recomenda-se cosineDistance para compensar a escala.
Para as funções de distância L2Distance e cosineDistance, um valor menor significa maior similaridade, enquanto para dotProduct, um valor maior significa maior similaridade. Como resultado, índices vetoriais com L2Distance e cosineDistance só podem ser usados por consultas SELECT [...] ORDER BY [...] ASC (ASC é o padrão de ORDER BY), enquanto índices vetoriais criados para dotProduct só podem ser usados por consultas SELECT [...] ORDER BY [...] DESC.
<dimensions> especifica a cardinalidade do array (número de elementos) na coluna subjacente. Se o ClickHouse encontrar um array com cardinalidade diferente durante a criação do índice, o índice será descartado e um erro será retornado. O parâmetro opcional GRANULARITY <N> refere-se ao tamanho dos grânulos de índice (veja aqui). Ao contrário dos skip indexes comuns, que usam uma granularidade de índice padrão de 1, os índices de similaridade vetorial usam 100 milhões como granularidade de índice padrão. Esse valor garante que apenas poucos índices sejam criados internamente, mesmo para partes grandes. Recomendamos alterar a granularidade do índice apenas para usuários avançados que entendam as implicações do que estão fazendo (veja abaixo). Os índices de similaridade vetorial são genéricos no sentido de que podem acomodar diferentes métodos de busca aproximada. O método efetivamente usado é especificado pelo parâmetro <type>. No momento, o único método disponível é HNSW (artigo acadêmico), uma técnica popular e de última geração para busca vetorial aproximada baseada em grafos hierárquicos de proximidade. Se HNSW for usado como tipo, os usuários poderão especificar opcionalmente outros parâmetros específicos do HNSW:
Os seguintes parâmetros específicos do HNSW estão disponíveis:
  • <quantization> controla a quantização dos vetores no grafo de proximidade. Os valores possíveis são f64, f32, f16, bf16, i8 ou b1. O valor padrão é bf16. Observe que esse parâmetro não afeta a representação dos vetores na coluna subjacente.
  • <hnsw_max_connections_per_layer> controla o número de vizinhos por nó do grafo, também conhecido como o hiperparâmetro HNSW M. O valor padrão é 32. O valor 0 significa usar o valor padrão.
  • <hnsw_candidate_list_size_for_construction> controla o tamanho da lista dinâmica de candidatos durante a construção do grafo HNSW, também conhecido como o hiperparâmetro HNSW ef_construction. O valor padrão é 128. O valor 0 significa usar o valor padrão.
Os valores padrão de todos os parâmetros específicos do HNSW funcionam razoavelmente bem na maioria dos casos de uso. Portanto, não recomendamos personalizar os parâmetros específicos do HNSW. Aplicam-se as seguintes restrições adicionais:
  • Índices de similaridade vetorial só podem ser criados em colunas do tipo Array(Float32), Array(Float64) ou Array(BFloat16). Arrays de tipos de ponto flutuante anuláveis e de baixa cardinalidade, como Array(Nullable(Float32)) e Array(LowCardinality(Float32)), não são permitidos.
  • Índices de similaridade vetorial devem ser criados em uma única coluna.
  • Índices de similaridade vetorial podem ser criados em expressões calculadas (por exemplo, INDEX index_name arraySort(vectors) TYPE vector_similarity([...])), mas esses índices não podem ser usados posteriormente para busca aproximada de vizinhos.
  • Índices de similaridade vetorial exigem que todos os arrays na coluna subjacente tenham <dimension> elementos — isso é verificado durante a criação do índice. Para detectar violações desse requisito o mais cedo possível, os usuários podem adicionar uma restrição à coluna vetorial, por exemplo, CONSTRAINT same_length CHECK length(vectors) = 256.
  • Da mesma forma, os valores de array na coluna subjacente não podem estar vazios ([]) nem ter o valor padrão (também []).
Estimando o consumo de armazenamento e memória Um vetor gerado para uso com um modelo de IA típico (por exemplo, um Large Language Model, LLMs) consiste em centenas ou milhares de valores de ponto flutuante. Assim, um único vetor pode consumir vários kilobytes de memória. Os usuários que quiserem estimar o armazenamento necessário para a coluna vetorial subjacente na tabela, bem como a memória principal necessária para o índice de similaridade vetorial, podem usar as duas fórmulas abaixo: Consumo de armazenamento da coluna vetorial na tabela (não comprimido):
Exemplo com o conjunto de dados dbpedia:
O índice de similaridade vetorial deve ser totalmente carregado do disco na memória principal para realizar as buscas. Da mesma forma, o índice vetorial também é construído integralmente na memória e depois salvo em disco. Consumo de memória necessário para carregar um índice vetorial:
Exemplo com o conjunto de dados dbpedia:
A fórmula acima não considera a memória adicional exigida pelos índices de similaridade vetorial para alocar estruturas de dados em tempo de execução, como buffers pré-alocados e caches.

Usando um índice de similaridade vetorial

Para usar índices de similaridade vetorial, a configuração compatibility deve estar definida como '' (o valor padrão), ou '25.1' ou posterior.
Os índices de similaridade vetorial suportam consultas SELECT neste formato:
O otimizador de consultas do ClickHouse tenta corresponder ao template de consulta acima e utilizar os índices de similaridade vetorial disponíveis. Uma consulta só pode usar um índice de similaridade vetorial se a função de distância na consulta SELECT for a mesma que a função de distância na definição do índice. Usuários avançados podem fornecer um valor personalizado para a configuração hnsw_candidate_list_size_for_search (também conhecida como hiperparâmetro HNSW “ef_search”) para ajustar o tamanho da lista de candidatos durante a busca (por exemplo, SELECT [...] SETTINGS hnsw_candidate_list_size_for_search = <value>). O valor padrão da configuração, 256, funciona bem na maioria dos casos de uso. Valores mais altos resultam em maior precisão, porém com desempenho mais lento. Se a consulta puder usar um índice de similaridade vetorial, o ClickHouse verifica se o LIMIT <N> fornecido nas consultas SELECT está dentro de limites razoáveis. Mais especificamente, um erro é retornado se <N> for maior que o valor da configuração max_limit_for_vector_search_queries, cujo valor padrão é 100. Valores de LIMIT muito grandes podem tornar as buscas mais lentas e geralmente indicam um erro de uso. Para verificar se uma consulta SELECT utiliza um índice de similaridade vetorial, você pode adicionar o prefixo EXPLAIN indexes = 1 à consulta. Como exemplo, consulte
pode retornar
Neste exemplo, 1 milhão de vetores do dataset dbpedia, cada um com dimensão 1536, estão armazenados em 575 grânulos, ou seja, 1,7 mil linhas por grânulo. A consulta solicita 10 vizinhos e o índice de similaridade vetorial localiza esses 10 vizinhos em 10 grânulos distintos. Esses 10 grânulos serão lidos durante a execução da consulta. Os índices de similaridade vetorial são utilizados quando a saída contém Skip e o nome e tipo do índice vetorial (no exemplo, idx e vector_similarity). Nesse caso, o índice de similaridade vetorial descartou dois dos quatro grânulos, ou seja, 50% dos dados. Quanto mais grânulos puderem ser descartados, mais eficaz será o uso do índice.
Para forçar o uso do índice, você pode executar a consulta SELECT com a configuração force_data_skipping_indexes (informe o nome do índice como valor da configuração).
Pós-filtragem e Pré-filtragem Os usuários podem, opcionalmente, especificar uma cláusula WHERE com condições de filtro adicionais para a consulta SELECT. O ClickHouse avaliará essas condições de filtro usando a estratégia de pós-filtragem ou de pré-filtragem. Em resumo, ambas as estratégias determinam a ordem em que os filtros são avaliados:
  • Pós-filtragem significa que o índice de similaridade vetorial é avaliado primeiro; depois, ClickHouse avalia o(s) filtro(s) adicional(is) especificado(s) na cláusula WHERE.
  • Pré-filtragem significa que a ordem de avaliação do filtro é inversa.
As estratégias apresentam diferentes trade-offs:
  • A pós-filtragem tem o problema geral de poder retornar menos linhas do que o número solicitado na cláusula LIMIT <N>. Essa situação ocorre quando uma ou mais linhas de resultado retornadas pelo índice de similaridade vetorial não atendem aos filtros adicionais.
  • A pré-filtragem geralmente ainda é um problema sem solução. Alguns bancos de dados vetoriais especializados oferecem algoritmos de pré-filtragem, mas a maioria dos bancos de dados relacionais (incluindo o ClickHouse) recorre à busca exata de vizinhos, isto é, a uma varredura por força bruta sem índice.
A estratégia usada depende da condição de filtro. Filtros adicionais fazem parte da chave de partição Se a condição de filtro adicional fizer parte da chave de partição, o ClickHouse aplicará a eliminação de partições. Por exemplo, uma tabela é particionada por intervalo pela coluna year e a seguinte consulta é executada:
O ClickHouse descartará todas as partições, exceto a de 2025. Filtros adicionais não podem ser avaliados usando índices Se condições de filtro adicionais não puderem ser avaliadas usando índices (índice de chave primária, índice de skipping), o ClickHouse aplicará pós-filtragem. Filtros adicionais podem ser avaliados usando o índice de chave primária Se condições de filtro adicionais puderem ser avaliadas usando a chave primária (ou seja, elas formam um prefixo da chave primária) e
  • se a condição de filtro eliminar pelo menos uma linha dentro de uma parte, o ClickHouse recorrerá à pré-filtragem para os intervalos “remanescentes” dentro da parte,
  • se a condição de filtro não eliminar nenhuma linha dentro de uma parte, o ClickHouse aplicará pós-filtragem à parte.
Em casos de uso práticos, este último caso é bastante improvável. Filtros adicionais podem ser avaliados usando índice de skipping Se condições de filtro adicionais puderem ser avaliadas usando índices de skipping (índice minmax, índice set etc.), o ClickHouse aplicará pós-filtragem. Nesses casos, o índice de similaridade vetorial é avaliado primeiro, pois espera-se que ele elimine mais linhas do que os outros índices de skipping. Para um controle mais preciso sobre pós-filtragem vs. pré-filtragem, duas configurações podem ser usadas: A configuração vector_search_filter_strategy (padrão: auto, que implementa as heurísticas acima) pode ser definida como prefilter. Isso é útil para forçar a pré-filtragem nos casos em que as condições de filtro adicionais são altamente seletivas. Como exemplo, a consulta a seguir pode se beneficiar da pré-filtragem:
Supondo que apenas um número muito pequeno de livros custe menos de 2 dólares, a pós-filtragem pode retornar zero linhas, porque os 10 resultados mais próximos retornados pelo índice vetorial podem ter preço acima de 2 dólares. Ao forçar a pré-filtragem (adicione SETTINGS vector_search_filter_strategy = 'prefilter' à consulta), o ClickHouse primeiro encontra todos os livros com preço inferior a 2 dólares e, em seguida, executa uma busca vetorial por força bruta nesses livros. Como abordagem alternativa para resolver o problema acima, vector_search_index_fetch_multiplier (padrão: 1.0, máximo: 1000.0) pode ser configurado com um valor > 1.0 (por exemplo, 2.0). O número de vizinhos mais próximos buscados no índice vetorial é multiplicado pelo valor dessa configuração, e então o filtro adicional é aplicado a essas linhas para retornar a quantidade de linhas definida por LIMIT. Como exemplo, podemos executar a consulta novamente, mas com o multiplicador 3.0:
O ClickHouse buscará 3,0 x 10 = 30 vizinhos mais próximos do índice vetorial em cada parte e, em seguida, avaliará os filtros adicionais. Apenas os dez vizinhos mais próximos serão retornados. Observamos que definir vector_search_index_fetch_multiplier pode mitigar o problema, mas, em casos extremos (condição WHERE muito seletiva), ainda é possível que sejam retornadas menos de N linhas solicitadas. Rescoring Os skip indexes no ClickHouse geralmente filtram no nível de grânulo, ou seja, uma busca em um skip index (internamente) retorna uma lista de grânulos com possível correspondência, o que reduz a quantidade de dados lidos na varredura subsequente. Isso funciona bem para skip indexes em geral, mas, no caso dos índices de similaridade vetorial, cria um “descompasso de granularidade”. Em mais detalhes, o índice de similaridade vetorial determina os números das linhas dos N vetores mais semelhantes para um determinado vetor de referência, mas depois precisa extrapolar esses números de linha para números de grânulos. Em seguida, o ClickHouse carrega esses grânulos do disco e repete o cálculo de distância para todos os vetores nesses grânulos. Essa etapa é chamada de rescoring e, embora teoricamente possa melhorar a precisão — lembre-se de que o índice de similaridade vetorial retorna apenas um resultado aproximado —, ela claramente não é ideal em termos de desempenho. Por isso, o ClickHouse fornece uma otimização que desativa o rescoring e retorna diretamente do índice os vetores mais semelhantes e suas distâncias. Essa otimização vem habilitada por padrão; consulte a configuração vector_search_with_rescoring. Em alto nível, ela funciona assim: o ClickHouse disponibiliza os vetores mais semelhantes e suas distâncias como uma coluna virtual _distances. Para ver isso, execute uma consulta de busca vetorial com EXPLAIN header = 1:
Uma consulta executada sem rescoring (vector_search_with_rescoring = 0) e com réplicas paralelas ativadas pode voltar a usar rescoring.

Ajuste de desempenho

Ajuste da compressão Em praticamente todos os casos de uso, os vetores na coluna subjacente são densos e não se comprimem bem. Como resultado, a compressão deixa mais lentas as inserções e leituras na/da coluna de vetores. Por isso, recomendamos desativar a compressão. Para fazer isso, especifique CODEC(NONE) para a coluna de vetores assim:
Ajustando a criação de índices O ciclo de vida dos índices de similaridade vetorial está ligado ao ciclo de vida das partes. Em outras palavras, sempre que uma nova parte com um índice de similaridade vetorial definido é criada, o índice também é criado. Isso normalmente acontece quando os dados são inseridos ou durante mesclagens. Infelizmente, o HNSW é conhecido pelos longos tempos de criação de índices, o que pode tornar inserções e mesclagens significativamente mais lentas. Idealmente, os índices de similaridade vetorial devem ser usados apenas quando os dados são imutáveis ou raramente mudam. Para acelerar a criação de índices, as seguintes técnicas podem ser usadas: Primeiro, a criação de índices pode ser paralelizada. O número máximo de threads para criação de índices pode ser configurado usando a configuração do servidor max_build_vector_similarity_index_thread_pool_size. Para um desempenho ideal, esse valor deve ser configurado de acordo com o número de núcleos de CPU. Segundo, para acelerar instruções INSERT, os usuários podem desativar a criação de índices de skipping em partes recém-inseridas usando a configuração de sessão materialize_skip_indexes_on_insert. Consultas SELECT nessas partes recorrerão à busca exata. Como as partes inseridas tendem a ser pequenas em comparação com o tamanho total da tabela, espera-se que o impacto disso no desempenho seja insignificante. Terceiro, para acelerar mesclagens, os usuários podem desativar a criação de índices de skipping em partes mescladas usando a configuração de sessão materialize_skip_indexes_on_merge. Isso, em conjunto com a instrução ALTER TABLE […] MATERIALIZE INDEX […], fornece controle explícito sobre o ciclo de vida dos índices de similaridade vetorial. Por exemplo, a criação de índices pode ser adiada até que todos os dados tenham sido ingeridos ou até um período de baixa carga do sistema, como no fim de semana. Ajustando o uso de índices Consultas SELECT precisam carregar índices de similaridade vetorial na memória principal para usá-los. Para evitar que o mesmo índice de similaridade vetorial seja carregado repetidamente na memória principal, o ClickHouse fornece um cache dedicado em memória para esses índices. Quanto maior esse cache, menos carregamentos desnecessários ocorrerão. O tamanho máximo do cache pode ser configurado usando a configuração do servidor vector_similarity_index_cache_size. Por padrão, o cache pode crescer até 5 GB. As seguintes mensagens de log (system.text_log) indicam que o índice de similaridade vetorial está sendo carregado. Se essas mensagens aparecerem repetidamente em diferentes consultas de busca vetorial, isso indica que o tamanho do cache está baixo demais.
O cache do índice de similaridade vetorial armazena grânulos do índice vetorial. Se os grânulos individuais do índice vetorial forem maiores que o tamanho do cache, eles não serão armazenados em cache. Portanto, calcule o tamanho do índice vetorial (com base na fórmula em “Estimando o consumo de armazenamento e memória” ou em system.data_skipping_indices) e dimensione o cache de acordo.
Reiteramos que verificar e, se necessário, aumentar o cache do índice vetorial deve ser a primeira etapa ao investigar consultas lentas de busca vetorial. O tamanho atual do cache do índice de similaridade vetorial é exibido em system.metrics:
Os acertos e as falhas de cache de uma consulta com um determinado ID de consulta podem ser obtidos em system.query_log:
Para casos de uso em produção, recomendamos dimensionar o cache de modo que todos os índices vetoriais permaneçam na memória o tempo todo. Ajustando a quantização A quantização é uma técnica para reduzir o consumo de memória dos vetores e os custos computacionais de criar e percorrer índices vetoriais. Os índices vetoriais do ClickHouse oferecem suporte às seguintes opções de quantização: A quantização reduz a precisão das buscas vetoriais em comparação com a busca nos valores originais de ponto flutuante em precisão total (f32). No entanto, na maioria dos datasets, a quantização brain float de meia precisão (bf16) resulta em perda de precisão desprezível; por isso, os índices de similaridade vetorial usam essa técnica de quantização por padrão. A quantização em quarto de precisão (i8) e a quantização binária (b1) causam perda de precisão perceptível em buscas vetoriais. Recomendamos essas duas quantizações apenas se o tamanho do índice de similaridade vetorial for significativamente maior que a DRAM disponível. Nesse caso, também sugerimos habilitar o rescoring (vector_search_index_fetch_multiplier, vector_search_with_rescoring) para melhorar a precisão. A quantização binária é recomendada apenas para 1) embeddings normalizados (ou seja, comprimento do vetor = 1; os modelos da OpenAI geralmente são normalizados) e 2) quando a distância de cosseno é usada como função de distância. Internamente, a quantização binária usa a distância de Hamming para construir e pesquisar o grafo de proximidade. A etapa de rescoring usa os vetores originais em precisão total armazenados na tabela para identificar os vizinhos mais próximos por meio da distância de cosseno. Ajustando a transferência de dados O vetor de referência em uma consulta de busca vetorial é fornecido pelo usuário e, em geral, obtido por meio de uma chamada a um Large Language Model (LLM). Um código Python típico que executa uma busca vetorial no ClickHouse pode ser assim
Vetores de embedding (search_v no trecho acima) podem ter uma dimensionalidade muito alta. Por exemplo, a OpenAI fornece modelos que geram vetores de embedding com 1536 ou até 3072 dimensões. No código acima, o driver Python do ClickHouse substitui o vetor de embedding por uma string legível e, em seguida, envia a consulta SELECT inteiramente como uma string. Supondo que o vetor de embedding seja composto por 1536 valores de ponto flutuante de precisão simples, a string enviada chega a 20 kB de comprimento. Isso gera alto consumo de CPU para tokenização, parsing e milhares de conversões de string para float. Além disso, é necessário um espaço considerável no arquivo de log do servidor ClickHouse, o que também causa crescimento excessivo em system.query_log. Observe que a maioria dos modelos de LLM retorna um vetor de embedding como uma lista ou um array do NumPy de floats nativos. Portanto, recomendamos que aplicações Python vinculem o parâmetro do vetor de referência em formato binário usando o seguinte estilo:
No exemplo, o vetor de referência é enviado como está, em formato binário, e reinterpretado como um array de números de ponto flutuante no servidor. Isso economiza tempo de CPU no servidor e evita o aumento desnecessário dos logs do servidor e de system.query_log.

Administração e monitoramento

O tamanho em disco dos índices de similaridade vetorial pode ser consultado em system.data_skipping_indices:
Exemplo de saída:

Diferenças em relação aos índices de skipping regulares

Assim como os índices de skipping regulares, os índices de similaridade vetorial são construídos sobre grânulos, e cada bloco indexado consiste em GRANULARITY = [N] grânulos ([N] = 1 por padrão para índices de skipping normais). Por exemplo, se a granularidade do índice primário da tabela for 8192 (configuração index_granularity = 8192) e GRANULARITY = 2, então cada bloco indexado conterá 16384 linhas. No entanto, as estruturas de dados e os algoritmos para busca aproximada de vizinhos são inerentemente orientados a linhas. Eles armazenam uma representação compacta de um conjunto de linhas e também retornam linhas para consultas de busca vetorial. Isso gera algumas diferenças um tanto contraintuitivas na forma como os índices de similaridade vetorial se comportam em comparação com os índices de skipping normais. Quando um usuário define um índice de similaridade vetorial em uma coluna, o ClickHouse cria internamente um “subíndice” de similaridade vetorial para cada bloco de índice. O subíndice é “local”, no sentido de que conhece apenas as linhas do bloco de índice ao qual pertence. No exemplo anterior, supondo que uma coluna tenha 65536 linhas, obtemos quatro blocos de índice (abrangendo oito grânulos) e um subíndice de similaridade vetorial para cada bloco de índice. Em teoria, um subíndice consegue retornar diretamente as linhas com os N pontos mais próximos dentro do seu bloco de índice. No entanto, como o ClickHouse carrega dados do disco para a memória na granularidade de grânulos, os subíndices extrapolam as linhas correspondentes para a granularidade dos grânulos. Isso difere dos índices de skipping regulares, que ignoram dados na granularidade dos blocos de índice. O parâmetro GRANULARITY determina quantos subíndices de similaridade vetorial são criados. Valores maiores de GRANULARITY significam menos subíndices de similaridade vetorial, porém maiores, até o ponto em que uma coluna (ou o data part de uma coluna) tenha apenas um único subíndice. Nesse caso, o subíndice tem uma visão “global” de todas as linhas da coluna e pode retornar diretamente todos os grânulos da coluna (part) com linhas relevantes (há no máximo LIMIT [N] desses grânulos). Em uma segunda etapa, o ClickHouse carregará esses grânulos e identificará as linhas realmente melhores realizando um cálculo de distância por força bruta sobre todas as linhas dos grânulos. Com um valor pequeno de GRANULARITY, cada subíndice retorna até LIMIT N grânulos. Como resultado, mais grânulos precisam ser carregados e pós-filtrados. Observe que a precisão da busca é igualmente boa nos dois casos; apenas o desempenho do processamento difere. Em geral, recomenda-se usar um GRANULARITY alto para índices de similaridade vetorial e recorrer a valores menores de GRANULARITY apenas em caso de problemas, como consumo excessivo de memória pelas estruturas de similaridade vetorial. Se nenhum GRANULARITY tiver sido especificado para índices de similaridade vetorial, o valor padrão será 100 milhões.

Exemplo

Consultas:
Query
Response
Mais conjuntos de dados de exemplo que usam busca vetorial aproximada:

Quantized Bit (QBit)

Uma abordagem comum para acelerar a busca vetorial exata é usar um tipo de dado float com menor precisão. Por exemplo, se os vetores forem armazenados como Array(BFloat16) em vez de Array(Float32), o tamanho dos dados será reduzido pela metade, e espera-se que o tempo de execução das consultas diminua proporcionalmente. Esse método é conhecido como quantização. Embora acelere o processamento, ele pode reduzir a precisão dos resultados, apesar de realizar uma varredura exaustiva de todos os vetores. Com a quantização tradicional, perdemos precisão tanto durante a busca quanto no armazenamento dos dados. No exemplo acima, armazenaríamos BFloat16 em vez de Float32, o que significa que nunca poderemos realizar uma busca mais precisa depois, mesmo que isso seja desejado. Uma alternativa é armazenar duas cópias dos dados: uma quantizada e outra com precisão total. Embora isso funcione, exige armazenamento redundante. Considere um cenário em que temos Float64 como dado original e queremos executar buscas com diferentes níveis de precisão (16 bits, 32 bits ou 64 bits completos). Precisaríamos armazenar três cópias separadas dos dados. O ClickHouse oferece o tipo de dado Quantized Bit (QBit), que resolve essas limitações ao:
  1. Armazenar os dados originais com precisão total.
  2. Permitir que a precisão da quantização seja especificada em tempo de consulta.
Isso é feito armazenando os dados em um formato agrupado por bits (ou seja, todos os i-ésimos bits de todos os vetores são armazenados juntos), o que permite leituras apenas no nível de precisão solicitado. Assim, você obtém os benefícios de velocidade da redução de E/S e do processamento proporcionados pela quantização, ao mesmo tempo em que mantém todos os dados originais disponíveis quando necessário. Quando a precisão máxima é selecionada, a busca se torna exata. Para declarar uma coluna do tipo QBit, use a seguinte sintaxe:
Onde:
  • element_type – o tipo de cada elemento do vetor. Os tipos aceitos são BFloat16, Float32 e Float64
  • dimension – o número de elementos em cada vetor

Criando uma tabela QBit e adicionando dados

Vamos encontrar os vizinhos mais próximos de um vetor que representa a palavra ‘lemon’ usando a distância L2. O terceiro parâmetro da função de distância especifica a precisão em bits — valores mais altos oferecem mais exatidão, mas exigem mais processamento. Você pode encontrar todas as funções de distância disponíveis para QBit aqui. Busca com precisão total (64 bits):
Busca de precisão reduzida:
Observe que, com a quantização de 12 bits, obtemos uma boa aproximação das distâncias, com execução da consulta mais rápida. A ordenação relativa permanece em grande parte consistente, com ‘apple’ ainda sendo a correspondência mais próxima.

Considerações de desempenho

O ganho de desempenho do QBit vem da redução das operações de E/S, já que menos dados precisam ser lidos do armazenamento ao usar uma precisão menor. Além disso, quando o QBit contém dados Float32, se o parâmetro de precisão for 16 ou menos, há ganhos adicionais com a redução do processamento. O parâmetro de precisão controla diretamente o equilíbrio entre exatidão e velocidade:
  • Maior precisão (mais próxima da largura original dos dados): Resultados mais exatos, consultas mais lentas
  • Menor precisão: Consultas mais rápidas com resultados aproximados e menor uso de memória

Referências

Blog:
Última modificação em 29 de junho de 2026