Criando um índice de texto
tokenizer. O argumento tokenizer especifica o tokenizer:
splitByNonAlphadivide strings em caracteres ASCII não alfanuméricos (veja também a função splitByNonAlpha).splitByString(S)divide strings com base em determinadas strings separadorasSdefinidas pelo usuário (veja também a função splitByString). Os separadores podem ser especificados usando um parâmetro opcional, por exemplo,tokenizer = splitByString([', ', '; ', '\n', '\\']). Observe que cada string pode ser composta por vários caracteres (', 'no exemplo). A lista padrão de separadores, se não for especificada explicitamente (por exemplo,tokenizer = splitByString), é um único espaço em branco[' '].ngrams(N)divide strings emN-gramas de mesmo tamanho (veja também a função ngrams). O comprimento do ngram pode ser especificado usando um parâmetro inteiro opcional entre 2 e 8, por exemplo,tokenizer = ngrams(3). O tamanho padrão do ngram, se não for especificado explicitamente (por exemplo,tokenizer = ngrams), é 3.arraynão realiza tokenização, ou seja, cada valor de uma linha é um token (veja também a função array).sparseGrams(min_length, max_length, min_cutoff_length)— usa o mesmo algoritmo da função sparseGrams para dividir uma string em todos os ngrams demin_lengthe em vários ngrams maiores, atémax_length, inclusive. Semin_cutoff_lengthfor especificado, somente N-gramas com comprimento maior ou igual amin_cutoff_lengthserão salvos no índice. Diferentemente dengrams(N), que gera apenas N-gramas de comprimento fixo,sparseGramsproduz um conjunto de N-gramas de comprimento variável dentro do intervalo especificado, permitindo uma representação mais flexível do contexto do texto. Por exemplo,tokenizer = sparseGrams(3, 5, 4)gerará 3-, 4- e 5-gramas a partir da string de entrada e salvará apenas os 4- e 5-gramas no índice.
O tokenizer
splitByString aplica os separadores de divisão da esquerda para a direita.
Isso pode criar ambiguidades.
Por exemplo, as strings separadoras ['%21', '%'] farão com que %21abc seja tokenizado como ['abc'], enquanto inverter a ordem dessas duas strings separadoras para ['%', '%21'] produzirá ['21abc'].
Na maioria dos casos, o ideal é que a correspondência priorize primeiro os separadores mais longos.
Em geral, isso pode ser feito passando as strings separadoras em ordem decrescente de comprimento.
Se as strings separadoras formarem um código de prefixo, elas podem ser passadas em qualquer ordem.preprocessor. O argumento opcional preprocessor é uma expression que transforma a string de entrada antes da tokenização.
Os casos de uso típicos do argumento preprocessor incluem
- Converter as strings de entrada em minúsculas (ou maiúsculas) para permitir correspondência sem diferenciar maiúsculas de minúsculas, por exemplo, lower, lowerUTF8; veja o primeiro exemplo abaixo.
- Normalização UTF-8, por exemplo, normalizeUTF8NFC, normalizeUTF8NFD, normalizeUTF8NFKC, normalizeUTF8NFKD, toValidUTF8.
- Remover ou transformar caracteres ou substrings indesejados, por exemplo, extractTextFromHTML, substring, idnaEncode.
INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(col))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = substringIndex(col, '\n', 1))INDEX idx(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(extractTextFromHTML(col))
Parâmetros avançados opcionais
Parâmetros avançados opcionais
Os valores padrão dos parâmetros avançados a seguir funcionam bem em praticamente todas as situações.
Não recomendamos alterá-los.O parâmetro opcional
dictionary_block_size (padrão: 128) especifica o tamanho dos blocos do dicionário em linhas.O parâmetro opcional dictionary_block_frontcoding_compression (padrão: 1) especifica se os blocos do dicionário usam front coding para compressão.O parâmetro opcional max_cardinality_for_embedded_postings (padrão: 16) especifica o limite de cardinalidade abaixo do qual as posting lists devem ser incorporadas aos blocos do dicionário.O parâmetro opcional bloom_filter_false_positive_rate (padrão: 0.1) especifica a taxa de falso positivo do filtro de Bloom do dicionário.Usando um índice de texto
Funções suportadas
WHERE de uma consulta SELECT:
= and !=
= (equals) and != (notEquals ) correspondem exatamente ao termo de busca fornecido.
Exemplo:
= e !=, mas a busca por igualdade e desigualdade só faz sentido com o tokenizer array (o que faz com que o índice armazene os valores completos da linha).
IN and NOT IN
IN (in) e NOT IN (notIn) são semelhantes às funções equals e notEquals, mas correspondem a todos (IN) ou a nenhum (NOT IN) dos termos de busca.
Exemplo:
= e !=; ou seja, IN e NOT IN só fazem sentido em conjunto com o tokenizer array.
LIKE, NOT LIKE e match
Atualmente, essas funções usam o índice de texto para filtragem somente se o tokenizer do índice for
splitByNonAlpha ou ngrams.LIKE like, NOT LIKE (notLike) e a função match com índices de texto, o ClickHouse precisa conseguir extrair tokens completos do termo de pesquisa.
Exemplo:
support no exemplo pode corresponder a support, supports, supporting etc.
Esse tipo de consulta é uma consulta de substring e não pode ser acelerada por um índice de texto.
Para usar um índice de texto em consultas LIKE, o padrão do LIKE deve ser reescrito da seguinte forma:
support garantem que o termo possa ser extraído como um token.
startsWith and endsWith
LIKE, as funções startsWith e endsWith só podem usar um índice de texto se for possível extrair tokens completos do termo de pesquisa.
Exemplo:
clickhouse é considerado um token.
support não é considerado um token porque pode corresponder a support, supports, supporting etc.
Para encontrar todas as linhas que começam com clickhouse supports, termine o padrão de pesquisa com um espaço no final:
endsWith deve ser usado com um espaço à esquerda:
hasToken and hasTokenOrNull
hasToken e hasTokenOrNull oferecem o melhor desempenho para uso com o índice text.
hasAnyTokens and hasAllTokens
has
mapContains
mapContainsKey) faz correspondência com um único token nas chaves de um map.
Exemplo:
operator[]
Array(T) e Map(K, V) com o índice de texto.
Exemplos de suporte a Array e Map no índice de texto.
Indexação de Array(String)
clickhouse) exige varrer todas as entradas:
keywords, que cria uma estrutura otimizada para pesquisa, pré-processando todas as palavras-chave e permitindo buscas instantâneas:
Importante: depois de adicionar o índice de texto, você precisa reconstruí-lo para os dados existentes:
Indexação de map
- Encontra todos os logs com limitação de taxa:
- Encontra todos os logs de um IP específico:
Importante: após adicionar o índice de texto, você precisa recriá-lo para os dados existentes:
- Encontre todas as solicitações com taxa limitada:
- Encontre todos os logs de um IP específico:
Implementação
Layout do índice
- um dicionário que associa cada token a uma lista de postings; e
- um conjunto de listas de postings, cada uma representando um conjunto de números de linha.
dictionary_block_size).
Um arquivo de blocos do dicionário (.dct) contém todos os blocos de dicionário de todos os grânulos de índice em uma part.
Arquivo de grânulos de índice (.idx)
O arquivo de grânulos de índice contém, para cada bloco de dicionário, o primeiro token do bloco, seu deslocamento relativo no arquivo de blocos do dicionário e um filtro de Bloom para todos os tokens do bloco.
Essa estrutura de índice esparso é semelhante ao índice esparso de chave primária) do ClickHouse.
O filtro de Bloom permite ignorar blocos de dicionário logo no início se o token procurado não estiver presente em um bloco de dicionário.
Arquivo de listas de postings (.pst)
As listas de postings de todos os tokens são organizadas sequencialmente no arquivo de listas de postings.
Para economizar espaço e ainda permitir operações rápidas de interseção e união, as listas de postings são armazenadas como bitmaps Roaring.
Se a cardinalidade de uma lista de postings for menor que 16 (configurável pelo parâmetro max_cardinality_for_embedded_postings), ela é incorporada ao dicionário.
Leitura direta
- Configuração query_plan_direct_read_from_text_index (padrão: 1), que especifica se a leitura direta está habilitada de modo geral.
- Configuração use_skip_indexes_on_data_read (padrão: 1), que é outro pré-requisito para a leitura direta. Observe que, em bancos de dados ClickHouse com compatibility < 25.10,
use_skip_indexes_on_data_readfica desabilitada, portanto você precisa aumentar o valor da configuração de compatibility ou definirSET use_skip_indexes_on_data_read = 1explicitamente.
ALTER TABLE ... MATERIALIZE INDEX para isso).
Funções suportadas
A otimização de leitura direta oferece suporte às funções hasToken, hasAllTokens e hasAnyTokens.
Essas funções também podem ser combinadas com os operadores AND, OR e NOT.
A cláusula WHERE também pode conter filtros adicionais que não sejam funções de pesquisa de texto (para colunas de texto ou outras colunas) — nesse caso, a otimização de leitura direta ainda será usada, mas será menos eficaz (ela se aplica apenas às funções de pesquisa de texto compatíveis).
Para verificar se uma consulta usa leitura direta, execute a consulta com EXPLAIN PLAN actions = 1.
Como exemplo, uma consulta com a leitura direta desabilitada
query_plan_direct_read_from_text_index = 1
__text_index_<index_name>_<function_name>_<id>.
Se essa coluna estiver presente, a leitura direta estará sendo usada.
Exemplo: conjunto de dados do Hacker News
hackernews:
ALTER TABLE para adicionar um índice de texto à coluna comment e, em seguida, materializá-lo:
hasToken, hasAnyTokens e hasAllTokens.
Os exemplos a seguir mostrarão a grande diferença de desempenho entre uma varredura de índice padrão e a otimização de leitura direta.
1. Usando hasToken
hasToken verifica se o texto contém um token específico.
Vamos procurar pelo token sensível a maiúsculas e minúsculas ‘ClickHouse’.
Leitura direta desabilitada (varredura padrão)
Por padrão, o ClickHouse usa o skip index para filtrar grânulos e, em seguida, lê os dados da coluna desses grânulos.
Podemos simular esse comportamento desabilitando a leitura direta.
2. Usando hasAnyTokens
hasAnyTokens verifica se o texto contém pelo menos um dos tokens informados.
Vamos procurar comentários que contenham ‘love’ ou ‘ClickHouse’.
Leitura direta desativada (varredura padrão)
3. Usando hasAllTokens
hasAllTokens verifica se o texto contém todos os tokens informados.
Vamos buscar comentários que contenham tanto ‘love’ quanto ‘ClickHouse’.
Leitura direta desativada (varredura padrão)
Mesmo com a leitura direta desativada, o skip index padrão continua eficaz.
Ele reduz as 28.7M linhas para apenas 147.46K, mas ainda precisa ler 57.03 MB da coluna.
4. Busca composta: OR, AND, NOT, …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) seria a sintaxe preferida e mais eficiente.