Skip to main content
O motor MergeTree e outros motores da família MergeTree (por exemplo, ReplacingMergeTree, AggregatingMergeTree) são os motores de tabela mais usados e mais robustos no ClickHouse. Os motores de tabela da família MergeTree foram projetados para altas taxas de ingestão de dados e volumes massivos de dados. As operações de inserção criam partes de tabela, que são mescladas por um processo em segundo plano com outras partes de tabela. Principais recursos dos motores de tabela da família MergeTree.
  • A chave primária da tabela determina a ordem de classificação dentro de cada parte de tabela (índice clusterizado). A chave primária também não faz referência a linhas individuais, mas a blocos de 8192 linhas chamados grânulos. Isso torna as chaves primárias de grandes conjuntos de dados pequenas o suficiente para permanecerem carregadas na memória principal, ao mesmo tempo em que ainda fornecem acesso rápido aos dados em disco.
  • As tabelas podem ser particionadas usando uma expressão de partição arbitrária. O partition pruning garante que as partições sejam excluídas da leitura quando a consulta permitir.
  • Os dados podem ser replicados em vários nós do cluster para alta disponibilidade, failover e upgrades sem indisponibilidade. Consulte Replicação de dados.
  • Os motores de tabela MergeTree oferecem suporte a vários tipos de estatísticas e métodos de amostragem para ajudar na otimização de consultas.
Apesar do nome semelhante, o motor Merge é diferente dos motores *MergeTree.

Criando tabelas

Para uma descrição detalhada dos parâmetros, consulte a instrução CREATE TABLE

Cláusulas de consulta

ENGINE

ENGINE — Nome e parâmetros do motor. ENGINE = MergeTree(). O motor MergeTree não tem parâmetros.

ORDER BY

ORDER BY — A chave de ordenação. Uma tupla de nomes de colunas ou expressões arbitrárias. Exemplo: ORDER BY (CounterID + 1, EventDate). Se nenhuma chave primária estiver definida (ou seja, PRIMARY KEY não tiver sido especificada), ClickHouse usa a chave de ordenação como chave primária. Se nenhuma ordenação for necessária, você pode usar a sintaxe ORDER BY tuple(). Como alternativa, se a configuração create_table_empty_primary_key_by_default estiver habilitada, ORDER BY () é adicionado implicitamente às instruções CREATE TABLE. Consulte Selecionando uma chave primária.

PARTITION BY

PARTITION BY — A chave de particionamento. Opcional. Na maioria dos casos, você não precisa de uma chave de particionamento e, se precisar particionar, em geral não precisa de uma chave de particionamento com granularidade maior do que por mês. O particionamento não acelera as consultas (ao contrário da expressão ORDER BY). Nunca use um particionamento granular demais. Não particione seus dados por identificadores ou nomes de clientes (em vez disso, use o identificador ou nome do cliente como a primeira coluna na expressão ORDER BY). Para particionar por mês, use a expressão toYYYYMM(date_column), em que date_column é uma coluna com uma data do tipo Date. Os nomes das partições aqui têm o formato "YYYYMM".

CHAVE PRIMÁRIA

PRIMARY KEY — A chave primária, se for diferente da chave de ordenação. Opcional. Especificar uma chave de ordenação (usando a cláusula ORDER BY) implica especificar também uma chave primária. Em geral, não é necessário especificar a chave primária além da chave de ordenação.

SAMPLE BY

SAMPLE BY — Uma expressão de amostragem. Opcional. Se for especificada, deve estar contida na chave primária. A expressão de amostragem deve resultar em um inteiro sem sinal. Exemplo: SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID)).

TTL

TTL — Uma lista de regras que especificam a duração do armazenamento das linhas e a lógica de movimentação automática de partes entre disks e volumes. Opcional. A expressão deve resultar em um Date ou DateTime, por exemplo, TTL date + INTERVAL 1 DAY. O tipo de regra DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'|GROUP BY especifica uma ação a ser executada com a parte se a expressão for satisfeita (atingir o momento atual): remoção de linhas expiradas, movimentação de uma parte (se a expressão for satisfeita para todas as linhas de uma parte) para o disk especificado (TO DISK 'xxx') ou para o volume (TO VOLUME 'xxx'), ou agregação de valores nas linhas expiradas. O tipo padrão da regra é remoção (DELETE). É possível especificar uma lista com várias regras, mas não deve haver mais de uma regra DELETE. Para mais detalhes, consulte TTL para colunas e tabelas

CONFIGURAÇÕES

Consulte Configurações do MergeTree. Exemplo da configuração Sections
No exemplo, definimos o particionamento por mês. Também definimos uma expressão de amostragem como um hash do ID do usuário. Isso permite pseudorrandomizar os dados na tabela para cada CounterID e EventDate. Se você definir uma cláusula SAMPLE ao selecionar os dados, o ClickHouse retornará uma amostra de dados pseudorrandômica uniforme para um subconjunto de usuários. A configuração index_granularity pode ser omitida, porque 8192 é o valor padrão.

Armazenamento de dados

Uma tabela consiste em partes de dados ordenadas pela chave primária. Quando dados são inseridos em uma tabela, partes de dados separadas são criadas, e cada uma delas é ordenada lexicograficamente pela chave primária. Por exemplo, se a chave primária for (CounterID, Date), os dados da parte serão ordenados por CounterID e, dentro de cada CounterID, por Date. Os dados pertencentes a partições diferentes são separados em partes diferentes. Em segundo plano, o ClickHouse mescla as partes de dados para tornar o armazenamento mais eficiente. Partes pertencentes a partições diferentes não são mescladas. O mecanismo de mesclagem não garante que todas as linhas com a mesma chave primária fiquem na mesma parte de dados. As partes de dados podem ser armazenadas no formato Wide ou Compact. No formato Wide, cada coluna é armazenada em um arquivo separado em um sistema de arquivos; no formato Compact, todas as colunas são armazenadas em um único arquivo. O formato Compact pode ser usado para aumentar o desempenho de inserções pequenas e frequentes. O formato de armazenamento dos dados é controlado pelas configurações min_bytes_for_wide_part e min_rows_for_wide_part do mecanismo da tabela. Se o número de bytes ou de linhas em uma parte de dados for menor que o valor da configuração correspondente, a parte será armazenada no formato Compact. Caso contrário, será armazenada no formato Wide. Se nenhuma dessas configurações estiver definida, as partes de dados serão armazenadas no formato Wide. Cada parte de dados é dividida logicamente em grânulos. Um grânulo é o menor conjunto de dados indivisível que o ClickHouse lê ao selecionar dados. O ClickHouse não divide linhas nem valores, portanto cada grânulo sempre contém um número inteiro de linhas. A primeira linha de um grânulo é marcada com o valor da chave primária dessa linha. Para cada parte de dados, o ClickHouse cria um arquivo de índice que armazena as marcas. Para cada coluna, esteja ela na chave primária ou não, o ClickHouse também armazena as mesmas marcas. Essas marcas permitem localizar dados diretamente nos arquivos de coluna. O tamanho do grânulo é limitado pelas configurações index_granularity e index_granularity_bytes do mecanismo da tabela. O número de linhas em um grânulo fica no intervalo [1, index_granularity], dependendo do tamanho das linhas. O tamanho de um grânulo pode exceder index_granularity_bytes se o tamanho de uma única linha for maior que o valor da configuração. Nesse caso, o tamanho do grânulo é igual ao tamanho da linha.

Chaves primárias e índices nas consultas

Considere a chave primária (CounterID, Date) como exemplo. Nesse caso, a ordenação e o índice podem ser ilustrados da seguinte forma:
Se a consulta de dados especificar:
  • CounterID in ('a', 'h'), o servidor lê os dados nos intervalos de marcas [0, 3) e [6, 8).
  • CounterID IN ('a', 'h') AND Date = 3, o servidor lê os dados nos intervalos de marcas [1, 3) e [7, 8).
  • Date = 3, o servidor lê os dados no intervalo de marcas [1, 10].
Os exemplos acima mostram que usar um índice é sempre mais eficiente do que fazer uma varredura completa. Um índice esparso permite que dados adicionais sejam lidos. Ao ler um único intervalo da chave primária, até index_granularity * 2 linhas adicionais em cada bloco de dados podem ser lidas. Índices esparsos permitem trabalhar com um número muito grande de linhas na tabela porque, na maioria dos casos, esses índices cabem na RAM do computador. O ClickHouse não exige uma chave primária exclusiva. Você pode inserir várias linhas com a mesma chave primária. Você pode usar expressões do tipo Nullable nas cláusulas PRIMARY KEY e ORDER BY, mas isso é fortemente desaconselhado. Para permitir esse recurso, ative a configuração allow_nullable_key. O princípio NULLS_LAST se aplica aos valores NULL na cláusula ORDER BY.

Selecionando uma chave primária

O número de colunas na chave primária não é explicitamente limitado. Dependendo da estrutura dos dados, você pode incluir mais ou menos colunas na chave primária. Isso pode:
  • Melhorar o desempenho de um índice. Se a chave primária for (a, b), adicionar outra coluna c melhorará o desempenho se as seguintes condições forem atendidas:
    • Houver consultas com uma condição na coluna c.
    • Forem comuns intervalos longos de dados (várias vezes maiores que a index_granularity) com valores idênticos para (a, b). Em outras palavras, quando adicionar outra coluna permite pular intervalos de dados bastante longos.
  • Melhorar a compressão dos dados. O ClickHouse ordena os dados pela chave primária, portanto, quanto maior a consistência, melhor a compressão.
  • Fornecer lógica adicional ao mesclar partes de dados nos motores CollapsingMergeTree e SummingMergeTree. Nesse caso, faz sentido especificar a chave de ordenação diferente da chave primária.
Uma chave primária longa afetará negativamente o desempenho das inserções e o consumo de memória, mas colunas extras na chave primária não afetam o desempenho do ClickHouse durante consultas SELECT. Você pode criar uma tabela sem chave primária usando a sintaxe ORDER BY tuple(). Nesse caso, o ClickHouse armazena os dados na ordem de inserção. Se você quiser preservar a ordem dos dados ao inseri-los com consultas INSERT ... SELECT, defina max_insert_threads = 1. Para selecionar dados na ordem original, use consultas SELECT single-threaded.

Escolhendo uma chave primária diferente da chave de ordenação

É possível especificar uma chave primária (uma expressão com valores gravados no arquivo de índice para cada marca) diferente da chave de ordenação (uma expressão usada para ordenar as linhas nas partes de dados). Nesse caso, a tupla de expressão da chave primária deve ser um prefixo da tupla de expressão da chave de ordenação. Esse recurso é útil ao usar os motores de tabela SummingMergeTree e AggregatingMergeTree. Em um caso comum de uso desses motores, a tabela tem dois tipos de colunas: dimensões e medidas. As consultas típicas agregam os valores das colunas de medida com GROUP BY arbitrário e filtragem por dimensões. Como SummingMergeTree e AggregatingMergeTree agregam linhas com o mesmo valor da chave de ordenação, é natural adicionar todas as dimensões a ela. Como resultado, a expressão de chave passa a consistir em uma longa lista de colunas, e essa lista precisa ser atualizada com frequência à medida que novas dimensões são adicionadas. Nesse caso, faz sentido manter apenas algumas colunas na chave primária para garantir varreduras de intervalo eficientes e adicionar as colunas de dimensão restantes à tupla da chave de ordenação. O ALTER da chave de ordenação é uma operação leve porque, quando uma nova coluna é adicionada simultaneamente à tabela e à chave de ordenação, as partes de dados existentes não precisam ser alteradas. Como a chave de ordenação antiga é um prefixo da nova chave de ordenação e não há dados na coluna recém-adicionada, os dados ficam ordenados tanto pela chave de ordenação antiga quanto pela nova no momento da modificação da tabela.

Uso de índices e partições em consultas

Para consultas SELECT, o ClickHouse analisa se um índice pode ser usado. Um índice pode ser usado se a cláusula WHERE/PREWHERE contiver uma expressão (como um dos elementos da conjunção ou por completo) que represente uma operação de comparação de igualdade ou desigualdade, ou se contiver IN ou LIKE com um prefixo fixo em colunas ou expressões que façam parte da chave primária ou da chave de particionamento, ou em determinadas funções parcialmente repetitivas dessas colunas, ou em relações lógicas entre essas expressões. Assim, é possível executar rapidamente consultas em um ou vários intervalos da chave primária. Neste exemplo, as consultas serão rápidas quando executadas para uma tag de rastreamento específica, para uma tag específica e um intervalo de datas, para uma tag específica e uma data, para várias tags com um intervalo de datas e assim por diante. Vamos analisar o motor configurado da seguinte forma:
Nesse caso, nas consultas:
O ClickHouse usará o índice da chave primária para descartar dados fora do critério e a chave de particionamento mensal para descartar partições que estejam fora dos intervalos de datas adequados. As consultas acima mostram que o índice é usado mesmo para expressões complexas. A leitura da tabela é organizada de forma que usar o índice não possa ser mais lento do que uma varredura completa. No exemplo abaixo, o índice não pode ser usado.
Para verificar se o ClickHouse pode usar o índice ao executar uma consulta, use as configurações force_index_by_date e force_primary_key. A chave de partição por mês permite ler apenas os blocos de dados que contêm datas dentro do intervalo correto. Nesse caso, o bloco de dados pode conter dados de várias datas (até um mês inteiro). Dentro de um bloco, os dados são ordenados pela chave primária, que talvez não tenha a data como primeira coluna. Por isso, usar uma consulta com apenas uma condição de data, sem especificar o prefixo da chave primária, fará com que mais dados sejam lidos do que no caso de uma única data.

Uso do índice para expressões determinísticas em chaves primárias

A chave primária pode conter expressões, não apenas nomes de colunas. Essas expressões não se limitam a cadeias simples de funções: elas podem ser árvores de expressões arbitrárias (por exemplo, funções aninhadas e expressões compostas), desde que sejam determinísticas. Uma expressão é determinística se sempre retorna o mesmo resultado para os mesmos valores de entrada (por exemplo: length(), toDate(), lower(), left(), cityHash64(), toUUID(); ao contrário de now() ou rand()). Se a chave primária contiver expressões determinísticas, o ClickHouse poderá aplicá-las a valores constantes da consulta e usar o resultado para criar condições no índice da chave primária. Isso permite ignorar dados para predicados como =, IN e has. Um caso de uso comum é manter a chave primária compacta (por exemplo, armazenar um hash em vez de uma String longa), ao mesmo tempo em que se permite que predicados na coluna original usem o índice. Exemplo de uma chave primária determinística (mas não injetiva):
Exemplos de predicados que podem usar o índice:
Nesses casos, o ClickHouse calcula length('alice') (e outras constantes) uma vez e usa os valores de comprimento para restringir os intervalos no índice da chave primária. Como o comprimento de uma string não é injetivo, diferentes strings user_id podem ter o mesmo comprimento, então o índice pode ler grânulos extras (falsos positivos). O resultado permanece correto porque o predicado original (user_id = ..., IN etc.) ainda é aplicado após a leitura. Se a expressão determinística também for injetiva (entradas diferentes não podem produzir a mesma saída para os tipos de argumento usados), o ClickHouse também pode usar o índice de forma eficaz para as formas negadas: !=, NOT IN e NOT has(...). Por exemplo, reverse(p) e hex(p) são injetivas para String. Exemplo de uma chave primária injetiva:
Expressões injetivas mais complexas também são suportadas, por exemplo:
Exemplos de predicados que podem usar o índice:

Uso de índice para chaves primárias parcialmente monotônicas

Considere, por exemplo, os dias do mês. Eles formam uma sequência monotônica ao longo de um mês, mas não em períodos mais longos. Essa é uma sequência parcialmente monotônica. Se um usuário criar a tabela com uma chave primária parcialmente monotônica, o ClickHouse criará um índice esparso como de costume. Quando um usuário seleciona dados desse tipo de tabela, o ClickHouse analisa as condições da consulta. Se o usuário quiser obter dados entre duas marcas do índice e ambas estiverem dentro do mesmo mês, o ClickHouse poderá usar o índice nesse caso específico, porque consegue calcular a distância entre os parâmetros de uma consulta e as marcas do índice. O ClickHouse não pode usar o índice se os valores da chave primária no intervalo de parâmetros da consulta não representarem uma sequência monotônica. Nesse caso, o ClickHouse usa o método de varredura completa. O ClickHouse usa essa lógica não apenas para sequências de dias do mês, mas para qualquer chave primária que represente uma sequência parcialmente monotônica.

Data skipping indexes

A declaração do índice fica na seção de colunas da consulta CREATE.
Para tabelas da família *MergeTree, é possível especificar data skipping indexes. Esses índices agregam algumas informações sobre a expressão especificada em blocos, que consistem em granularity_value grânulos (o tamanho do grânulo é especificado usando a configuração index_granularity no engine da tabela). Em seguida, esses agregados são usados em consultas SELECT para reduzir a quantidade de dados a ser lida do disco, descartando grandes blocos de dados em que a condição da cláusula WHERE não pode ser satisfeita. A cláusula GRANULARITY pode ser omitida; o valor padrão de granularity_value é 1. Exemplo
Os índices do exemplo podem ser usados pelo ClickHouse para reduzir a quantidade de dados lidos do disco nas consultas a seguir:
Os data skipping indexes também podem ser criados em colunas compostas:

Tipos de Skip Index

O motor de tabela MergeTree oferece suporte aos seguintes tipos de skip indexes. Para mais informações sobre como os skip indexes podem ser usados na otimização de desempenho, consulte “Entendendo os data skipping indexes do ClickHouse”.

Índice skip do tipo MinMax

Para cada grânulo do índice, os valores mínimo e máximo de uma expressão são armazenados. (Se a expressão for do tipo tuple, são armazenados os valores mínimo e máximo de cada elemento da tupla.)
Syntax

Set

Para cada grânulo de índice, são armazenados no máximo max_rows valores únicos da expressão especificada. max_rows = 0 significa “armazenar todos os valores únicos”.
Syntax

Filtro de Bloom

Para cada grânulo de índice, armazena um filtro de Bloom para as colunas especificadas.
Syntax
O parâmetro false_positive_rate pode assumir um valor entre 0 e 1 (por padrão: 0.025) e especifica a probabilidade de gerar um resultado positivo (o que aumenta a quantidade de dados a serem lidos). Os seguintes tipos de dados são suportados:
  • (U)Int*
  • Float*
  • Enum
  • Date
  • DateTime
  • String
  • FixedString
  • Array
  • LowCardinality
  • Nullable
  • UUID
  • Map
Tipo de dado Map: especificando a criação do índice com chaves ou valoresPara o tipo de dado Map, o cliente pode especificar se o índice deve ser criado para chaves ou para valores usando as funções mapKeys ou mapValues.
Tipo de dado JSON: indexando caminhos JSONPara o tipo de dado JSON, um índice de filtro de Bloom pode ser criado sobre o conjunto de caminhos usando a função JSONAllPaths. Isso permite ignorar grânulos em que um caminho JSON consultado está ausente. Consulte Data skipping indexes para JSON para mais detalhes.

Filtro de Bloom de n-gramas (Obsoleto)

Com a disponibilidade geral (GA) do índice text a partir da versão 26.2 do ClickHouse, o índice ngrambf_v1 não é mais recomendado para busca em texto completo.Consulte a página “Busca em texto completo com índices de texto” para mais detalhes.
Cada grânulo de índice armazena um filtro de Bloom para os n-gramas das colunas especificadas.
Syntax
Este índice só funciona com os seguintes tipos de dados: Para estimar os parâmetros de ngrambf_v1, você pode usar as seguintes Funções Definidas pelo Usuário (UDFs).
UDFs for ngrambf_v1
Para usar essas funções, é necessário especificar pelo menos dois parâmetros:
  • total_number_of_all_grams
  • probability_of_false_positives
Por exemplo, há 4300 ngrams no grânulo, e você espera que os falsos positivos sejam inferiores a 0.0001. Os outros parâmetros podem então ser estimados executando as seguintes consultas:
É claro que você também pode usar essas funções para estimar parâmetros para outras condições. As funções acima fazem referência à calculadora de filtro de Bloom aqui.

Filtro de Bloom de token

Com a disponibilidade geral (GA) do índice text a partir da versão 26.2 do ClickHouse, o índice tokenbf_v1 não é mais recomendado para busca em texto completo.Consulte a página “Pesquisa de texto completo com índices de texto” para mais detalhes.
Syntax

Filtro de Bloom de gramas esparsos

O filtro de Bloom de gramas esparsos é semelhante a ngrambf_v1, mas usa tokens de gramas esparsos em vez de ngrams.
Syntax

Índice de texto

Cria um índice invertido sobre dados textuais tokenizados, permitindo uma busca de texto completo eficiente e determinística. Veja aqui para mais detalhes.

Similaridade vetorial

Suporta busca aproximada por vizinho mais próximo; veja aqui para mais detalhes.

Suporte a funções

As condições na cláusula WHERE contêm chamadas a funções que operam sobre colunas. Se a coluna fizer parte de um índice, o ClickHouse tentará usar esse índice ao processar essas funções. O ClickHouse oferece suporte a diferentes subconjuntos de funções para o uso de índices. Índices do tipo set podem ser usados por todas as funções. Os demais tipos de índice têm suporte conforme a seguir: Funções com um argumento constante menor que o tamanho do ngram não podem ser usadas por ngrambf_v1 para otimização de consultas. (*) Para que hasTokenCaseInsensitive e hasTokenCaseInsensitiveOrNull sejam eficazes, o índice tokenbf_v1 deve ser criado sobre dados convertidos para minúsculas, por exemplo INDEX idx (lower(str_col)) TYPE tokenbf_v1(512, 3, 0).
Filtros de Bloom podem gerar falsos positivos, portanto os índices ngrambf_v1, tokenbf_v1, sparse_grams e bloom_filter não podem ser usados para otimizar consultas em que se espera que o resultado de uma função seja falso.Por exemplo:
  • Pode ser otimizado:
    • s LIKE '%test%'
    • NOT s NOT LIKE '%test%'
    • s = 1
    • NOT s != 1
    • startsWith(s, 'test')
  • Não pode ser otimizado:
    • NOT s LIKE '%test%'
    • s NOT LIKE '%test%'
    • NOT s = 1
    • s != 1
    • NOT startsWith(s, 'test')

Projeções

Projeções são como visões materializadas, mas são definidas no nível das partes. Elas oferecem garantias de consistência e são usadas automaticamente nas consultas.
Ao implementar projeções, você também deve considerar a configuração force_optimize_projection.
Projeções não são compatíveis com instruções SELECT com o modificador FINAL.

Consulta de projeção

Uma consulta de projeção define uma projeção. Ela seleciona implicitamente dados da tabela pai. Sintaxe
As projeções podem ser modificadas ou removidas com a instrução ALTER.

Índices de projeção

Os índices de projeção ampliam o subsistema de projeções, oferecendo uma forma leve e explícita de definir índices no nível da projeção. Externamente, um índice de projeção ainda é uma projeção, mas com sintaxe simplificada e propósito mais claro: ele define uma expressão dedicada à filtragem, em vez de fornecer dados materializados. Internamente, um índice de projeção não materializa a tabela original em uma ordem de linhas permutada, como faz uma projeção comum. Em vez disso, a permutação é armazenada na forma de uma coluna numérica de permutação _part_offset, ou seja, SELECT _part_offset ORDER BY <index_expr>.

Sintaxe

Exemplo:

Tipos de índice

Atualmente, há suporte para:
  • básico: equivalente a um índice normal do MergeTree sobre a expressão.
O framework permite adicionar mais tipos de índice no futuro.

Armazenamento de projeções

As projeções são armazenadas dentro do diretório da parte. É semelhante a um índice, mas contém um subdiretório que armazena a parte de uma tabela MergeTree anônima. A tabela é derivada da consulta de definição da projeção. Se houver uma cláusula GROUP BY, o motor de armazenamento subjacente se torna AggregatingMergeTree, e todas as funções de agregação são convertidas em AggregateFunction. Se houver uma cláusula ORDER BY, a tabela MergeTree a usa como expressão de chave primária. Durante o processo de merge, a parte da projeção é mesclada pela rotina de merge do seu motor de armazenamento. O checksum da parte da tabela pai é combinado com a parte da projeção. Outras tarefas de manutenção são semelhantes às dos índices de salto.

Análise de consulta

  1. Verifique se a projeção pode ser usada para responder à consulta fornecida, ou seja, se ela gera a mesma resposta que consultar a tabela base.
  2. Selecione a melhor opção viável, que contenha o menor número de grânulos a serem lidos.
  3. O pipeline de consulta que usa projeções será diferente daquele que usa as partes originais. Se a projeção estiver ausente em algumas partes, podemos adicionar um pipeline para “projetá-la” dinamicamente.

Acesso simultâneo aos dados

Para acesso simultâneo à tabela, usamos multiversionamento. Em outras palavras, quando uma tabela é lida e atualizada ao mesmo tempo, os dados são lidos de um conjunto de partes vigente no momento da consulta. Não há bloqueios prolongados. As inserções não interferem nas operações de leitura. A leitura de uma tabela é paralelizada automaticamente.

TTL para colunas e tabelas

Determina o tempo de vida dos valores. A cláusula TTL pode ser definida para a tabela inteira e para cada coluna individual. O TTL no nível da tabela também pode especificar a lógica de movimentação automática de dados entre disks e volumes, ou a recompressão de partes cujos dados já expiraram. As expressões devem resultar em um tipo de dado Date, Date32, DateTime ou DateTime64.
Evite funções não determinísticas em expressões TTLO TTL é avaliado durante a mesclagem em segundo plano, e não no momento da inserção. Funções como rand(), now() ou now64() serão reavaliadas a cada merge, levando a um comportamento de exclusão imprevisível. O ClickHouse bloqueia expressões sem qualquer dependência de coluna, mas atualmente não rejeita funções não determinísticas misturadas com uma referência de coluna (por exemplo, ts + rand()). As expressões TTL devem se basear exclusivamente em valores determinísticos derivados de colunas para garantir resultados previsíveis.
Sintaxe Definir o time-to-live de uma coluna:
Para definir interval, use os operadores de intervalo de tempo, por exemplo:

TTL de coluna

Quando os valores de uma coluna expiram, o ClickHouse os substitui pelos valores padrão do tipo de dados da coluna. Se todos os valores da coluna em uma parte de dados expirarem, o ClickHouse exclui essa coluna da parte de dados no sistema de arquivos. A cláusula TTL não pode ser usada para colunas de chave. Exemplos

Criando uma tabela com TTL:

Adicionando TTL a uma coluna em uma tabela existente

Alterar o TTL da coluna

TTL da tabela

A tabela pode ter uma expressão para remover linhas expiradas e múltiplas expressões para mover partes automaticamente entre disks ou volumes. Quando as linhas da tabela expiram, o ClickHouse exclui todas as linhas correspondentes. Para movimentação de partes ou recompressão, todas as linhas de uma parte devem atender aos critérios da expressão TTL.
O tipo de regra TTL pode acompanhar cada expressão TTL. Ele determina a ação a ser executada quando a expressão for satisfeita (atingir o momento atual):
  • DELETE - exclui linhas expiradas (ação padrão);
  • RECOMPRESS codec_name - recomprime a parte de dados com o codec_name;
  • TO DISK 'aaa' - move a part para o disk aaa;
  • TO VOLUME 'bbb' - move a part para o disk bbb;
  • GROUP BY - agrega linhas expiradas.
A ação DELETE pode ser usada em conjunto com a cláusula WHERE para excluir apenas algumas das linhas expiradas com base em uma condição de filtragem:
A expressão GROUP BY deve ser um prefixo da chave primária da tabela. Se uma coluna não fizer parte da expressão GROUP BY e não for definida explicitamente na cláusula SET, ela conterá, na linha de resultado, um valor aleatório das linhas agrupadas (como se a função de agregação any fosse aplicada a ela). Exemplos

Criando uma tabela com TTL:

Alterando o TTL da tabela:

Criando uma tabela em que as linhas expiram após um mês. As linhas expiradas cujas datas caem em segundas-feiras são excluídas:

Criando uma tabela em que as linhas expiradas são recomprimidas:

Criando uma tabela em que as linhas expiradas são agregadas. Nas linhas de resultado, x contém o valor máximo entre as linhas agrupadas, y, o valor mínimo, e d, um valor qualquer das linhas agrupadas.

Remoção de dados expirados

Dados com TTL expirado são removidos quando o ClickHouse faz a mesclagem de partes de dados. Quando o ClickHouse detecta que os dados expiraram, ele realiza uma mesclagem fora do cronograma. Para controlar a frequência dessas mesclagens, você pode definir merge_with_ttl_timeout. Se o valor for muito baixo, muitas mesclagens fora do cronograma serão executadas, o que pode consumir muitos recursos. Se você executar a consulta SELECT entre as mesclagens, poderá obter dados expirados. Para evitar isso, use a consulta OPTIMIZE antes de SELECT. Veja também

Tipos de disco

Além de dispositivos de bloco locais, o ClickHouse oferece suporte a estes tipos de armazenamento:

Uso de vários dispositivos de bloco para armazenamento de dados

Introdução

Os motores de tabela da família MergeTree podem armazenar dados em vários dispositivos de bloco. Por exemplo, isso pode ser útil quando os dados de uma determinada tabela são implicitamente divididos em “quentes” e “frios”. Os dados mais recentes são consultados com frequência, mas exigem apenas uma pequena quantidade de espaço. Por outro lado, o grande volume de dados históricos é consultado raramente. Se houver vários disks disponíveis, os dados “quentes” podem ficar em disks rápidos (por exemplo, SSDs NVMe ou em memória), enquanto os dados “frios” ficam em disks relativamente lentos (por exemplo, HDDs). Isso se aplica a todos os tipos de disco, incluindo disks S3 e outros disks de armazenamento de objetos. Por exemplo, você pode distribuir dados entre vários buckets do S3 em um único volume ou criar políticas em camadas que movem dados de disks locais para o S3. Veja Usando disks S3 com vários volumes para mais detalhes. Uma parte de dados é a menor unidade móvel das tabelas com motor MergeTree. Os dados pertencentes a uma parte são armazenados em um único disco. As partes de dados podem ser movidas entre disks em segundo plano (de acordo com as configurações do usuário), bem como por meio das consultas ALTER.

Termos

  • Disco — Dispositivo de bloco montado no sistema de arquivos.
  • Disco padrão — Disco que armazena o caminho especificado na configuração de servidor path.
  • Volume — Conjunto ordenado de disks idênticos (semelhante a JBOD).
  • Política de armazenamento — Conjunto de volumes e das regras para mover dados entre eles.
Os nomes dados às entidades descritas podem ser encontrados nas tabelas de sistema system.storage_policies e system.disks. Para aplicar uma das políticas de armazenamento configuradas a uma tabela, use a configuração storage_policy das tabelas da família de engines MergeTree.

Configuração

Discos, volumes e políticas de armazenamento devem ser declarados dentro da tag <storage_configuration>, em um arquivo no diretório config.d.
Discos também podem ser declarados na seção SETTINGS de uma consulta. Isso é útil para análises ad hoc, para anexar temporariamente um disco hospedado, por exemplo, em uma URL. Consulte armazenamento dinâmico para mais detalhes.
Estrutura da configuração:
Tags:
  • <disk_name_N> — Nome do disco. Os nomes devem ser diferentes para todos os disks.
  • path — caminho no qual o servidor armazenará os dados (pastas data e shadow); deve terminar com ’/’.
  • keep_free_space_bytes — quantidade de espaço livre em disco a ser reservada.
A ordem da definição dos disks não é importante. Markup de configuração das políticas de armazenamento:
Tags:
  • policy_name_N — Nome da política. Os nomes das políticas devem ser únicos.
  • volume_name_N — Nome do volume. Os nomes dos volumes devem ser únicos.
  • disk — um disco dentro de um volume.
  • max_data_part_size_bytes — o tamanho máximo de uma parte que pode ser armazenada em qualquer um dos disks do volume. Se o tamanho estimado de uma parte de dados mesclada for maior que max_data_part_size_bytes, essa parte será gravada no próximo volume. Basicamente, esse recurso permite manter partes novas/pequenas em um volume quente (SSD) e movê-las para um volume frio (HDD) quando atingirem um tamanho grande. Não use essa configuração se sua política tiver apenas um volume.
  • move_factor — quando a quantidade de espaço disponível fica abaixo desse fator, os dados começam automaticamente a ser movidos para o próximo volume, se houver (por padrão, 0.1). O ClickHouse classifica as partes existentes por tamanho, da maior para a menor (em ordem decrescente), e seleciona partes cujo tamanho total seja suficiente para atender à condição de move_factor. Se o tamanho total de todas as partes for insuficiente, todas as partes serão movidas.
  • perform_ttl_move_on_insert — Desabilita o TTL move no INSERT de partes de dados. Por padrão (se estiver habilitado), se inserirmos uma parte de dados que já expirou pela regra de TTL move, ela irá imediatamente para um volume/disco declarado na regra de movimentação. Isso pode tornar o insert significativamente mais lento caso o volume/disco de destino seja lento (por exemplo, S3). Se estiver desabilitado, a parte de dados já expirada será gravada em um volume padrão e, logo em seguida, movida para o volume de TTL.
  • load_balancing - Política de balanceamento de disks, round_robin ou least_used.
  • least_used_ttl_ms - Configura o timeout (em milissegundos) para a atualização do espaço disponível em todos os disks (0 - sempre atualizar, -1 - nunca atualizar, o padrão é 60000). Observe que, se o disco puder ser usado apenas pelo ClickHouse e não estiver sujeito a redimensionamento/redução online do filesystem, você pode usar -1; em todos os outros casos, isso não é recomendado, pois eventualmente levará a uma distribuição incorreta do espaço.
  • prefer_not_to_merge — Você não deve usar essa configuração. Desabilita a mesclagem de partes de dados neste volume (isso é prejudicial e leva à degradação de desempenho). Quando essa configuração está habilitada (não faça isso), a mesclagem de dados neste volume não é permitida (o que é ruim). Isso permite (mas você não precisa disso) controlar (se você quiser controlar alguma coisa, está cometendo um erro) como o ClickHouse funciona com disks lentos (mas o ClickHouse sabe mais, então, por favor, não use essa configuração).
  • volume_priority — Define a prioridade (ordem) em que os volumes são preenchidos. Um valor menor significa prioridade maior. Os valores do parâmetro devem ser números naturais e, em conjunto, cobrir o intervalo de 1 a N (sendo N a menor prioridade atribuída) sem pular nenhum número.
    • Se todos os volumes estiverem marcados, eles serão priorizados na ordem fornecida.
    • Se apenas alguns volumes estiverem marcados, os que não tiverem a marcação terão a menor prioridade e serão priorizados na ordem em que são definidos na configuração.
    • Se nenhum volume estiver marcado, sua prioridade será definida de acordo com a ordem em que for declarado na configuração.
    • Dois volumes não podem ter o mesmo valor de prioridade.
Exemplos de configuração:
No exemplo fornecido, a política hdd_in_order implementa a abordagem de round-robin. Assim, essa política define apenas um volume (single), e as partes de dados são armazenadas em todos os seus disks em ordem circular. Essa política pode ser bastante útil se houver vários disks semelhantes montados no sistema, mas o RAID não estiver configurado. Tenha em mente que cada disco individual não é confiável, e talvez você queira compensar isso com um fator de replicação de 3 ou mais. Se houver diferentes tipos de disks disponíveis no sistema, a política moving_from_ssd_to_hdd pode ser usada no lugar. O volume hot consiste em um disco SSD (fast_ssd), e o tamanho máximo de uma parte que pode ser armazenada nesse volume é 1GB. Todas as partes com tamanho maior que 1GB serão armazenadas diretamente no volume cold, que contém um disco HDD disk1. Além disso, quando o disco fast_ssd ficar mais de 80% cheio, os dados serão transferidos para disk1 por um processo em segundo plano. A ordem de enumeração dos volumes dentro de uma política de armazenamento é importante caso pelo menos um dos volumes listados não tenha um parâmetro volume_priority explícito. Quando um volume fica cheio demais, os dados são movidos para o próximo. A ordem de enumeração dos disks também é importante, porque os dados são armazenados neles em rodízio. Ao criar uma tabela, é possível aplicar a ela uma das políticas de armazenamento configuradas:
A política de armazenamento default pressupõe o uso de apenas um volume, que consiste em apenas um disco definido em <path>. Você pode alterar a política de armazenamento após a criação da tabela com a consulta [ALTER TABLE … MODIFY SETTING], e a nova política deve incluir todos os disks e volumes antigos com os mesmos nomes. O número de threads que executam movimentações em segundo plano de partes de dados pode ser alterado pela configuração background_move_pool_size.

Detalhes

No caso das tabelas MergeTree, os dados chegam ao disco de diferentes maneiras:
  • Como resultado de um insert (consulta INSERT).
  • Durante mesclagens em segundo plano e mutações.
  • Ao serem baixados de outra réplica.
  • Como resultado do congelamento de uma partição ALTER TABLE … FREEZE PARTITION.
Em todos esses casos, exceto nas mutações e no congelamento de partição, uma parte é armazenada em um volume e em um disco de acordo com a política de armazenamento definida:
  1. É escolhido o primeiro volume (na ordem de definição) que tenha espaço em disco suficiente para armazenar uma parte (unreserved_space > current_part_size) e permita armazenar partes de determinado tamanho (max_data_part_size_bytes > current_part_size).
  2. Dentro desse volume, é escolhido o disco que vem após o usado para armazenar o fragmento anterior de dados e que tenha espaço livre maior que o tamanho da parte (unreserved_space - keep_free_space_bytes > current_part_size).
Internamente, mutações e o congelamento de partição usam hard links. Hard links entre disks diferentes não são suportados; portanto, nesses casos, as partes resultantes são armazenadas nos mesmos disks que as originais. Em segundo plano, as partes são movidas entre volumes com base na quantidade de espaço livre (parâmetro move_factor), de acordo com a ordem em que os volumes são declarados no arquivo de configuração. Os dados nunca são transferidos do último para o primeiro. É possível usar as tabelas de sistema system.part_log (campo type = MOVE_PART) e system.parts (campos path e disk) para monitorar movimentações em segundo plano. Além disso, informações detalhadas podem ser encontradas nos logs do servidor. O usuário pode forçar a movimentação de uma parte ou de uma partição de um volume para outro usando a consulta ALTER TABLE … MOVE PART|PARTITION … TO VOLUME|DISK …; todas as restrições das operações em segundo plano são levadas em consideração. A consulta inicia a movimentação por conta própria e não espera a conclusão das operações em segundo plano. O usuário receberá uma mensagem de erro se não houver espaço livre suficiente ou se alguma das condições exigidas não for atendida. A movimentação de dados não interfere na replicação. Portanto, diferentes políticas de armazenamento podem ser especificadas para a mesma tabela em réplicas diferentes. Após a conclusão das mesclagens em segundo plano e das mutações, as partes antigas só são removidas depois de um certo tempo (old_parts_lifetime). Durante esse período, elas não são movidas para outros volumes ou disks. Portanto, até que sejam finalmente removidas, ainda são consideradas no cálculo do espaço em disco ocupado. O usuário pode distribuir novas partes grandes entre diferentes disks de um volume JBOD de forma equilibrada usando a configuração min_bytes_to_rebalance_partition_over_jbod.

Usando armazenamento externo para dados

Os motores de tabela da família MergeTree podem armazenar dados em S3, AzureBlobStorage e HDFS usando, respectivamente, discos dos tipos s3, azure_blob_storage e hdfs. Consulte como configurar opções de armazenamento externo para mais detalhes. Exemplo de uso do S3 como armazenamento externo com um disco do tipo s3. Trecho de configuração:
Consulte também como configurar opções de armazenamento externo.

Usando disks do S3 com vários volumes

Disks do S3 (e de outros tipos de armazenamento de objetos) podem ser usados em políticas de armazenamento com vários disks e vários volumes da mesma forma que disks locais. Isso permite distribuir os dados entre vários buckets do S3 em um único volume (no estilo JBOD) ou configurar políticas de armazenamento em camadas com volumes do S3. Por exemplo, para distribuir os dados entre dois buckets do S3 em esquema round-robin:
Você também pode combinar volumes locais e do S3 em uma política em camadas, por exemplo, movendo dados de um SSD local para o S3 à medida que ficam mais antigos:
Ao usar use_environment_credentials para autenticação no S3, as credenciais de ambiente (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) são compartilhadas entre todos os disks S3. Não é possível usar credenciais de ambiente diferentes para disks diferentes. Se você precisar de credenciais diferentes para cada disk S3, use as configurações explícitas access_key_id e secret_access_key para cada disco.
É possível configurar tabelas MergeTree não replicadas em um cenário com um gravador e muitos leitores em armazenamento compartilhado. Isso é viabilizado pela atualização automática da lista de partes, que pode ser configurada nos leitores. Observe que isso requer metadados de sistema de arquivos compartilhados entre as réplicas (ou table_disk = true com um disco local da tabela). Consulte refresh_parts_interval and table_disk.
configuração do cacheAs versões 22.3 a 22.7 do ClickHouse usam uma configuração de cache diferente. Consulte using local cache se você estiver usando uma dessas versões.

Colunas virtuais

  • _part — Nome de uma parte.
  • _part_index — Índice sequencial da parte no resultado da consulta.
  • _part_starting_offset — Linha inicial cumulativa da parte no resultado da consulta.
  • _part_offset — Número da linha na parte.
  • _part_granule_offset — Número do grânulo na parte.
  • _partition_id — Nome de uma partição.
  • _part_uuid — Identificador exclusivo da parte (se a configuração do MergeTree assign_part_uuids estiver habilitada).
  • _part_data_version — Versão dos dados da parte (seja o número mínimo do bloco ou a versão da mutação).
  • _partition_value — Valores (uma tupla) de uma expressão partition by.
  • _sample_factor — Fator de amostragem (da consulta).
  • _block_number — Número original do bloco da linha que foi atribuído na inserção, persistido em mesclagens quando a configuração enable_block_number_column está habilitada.
  • _block_offset — Número original da linha no bloco que foi atribuído na inserção, persistido em mesclagens quando a configuração enable_block_offset_column está habilitada.
  • _disk_name — Nome do disco usado para o armazenamento.

Estatísticas de coluna

A declaração das estatísticas fica na seção de colunas da consulta CREATE para tabelas da família *MergeTree*:
Também é possível manipular estatísticas com instruções ALTER:
Essas estatísticas leves agregam informações sobre a distribuição de valores nas colunas. As estatísticas são armazenadas em cada parte e atualizadas a cada inserção. Elas só podem ser usadas para a otimização PREWHERE se habilitarmos set use_statistics = 1.

Poda de partes com estatísticas

Quando use_statistics_for_part_pruning está habilitado, as estatísticas podem ser usadas para a poda de partes. Atualmente, apenas as estatísticas basic (e as estatísticas minmax obsoletas) dão suporte à poda de partes. Quando essas estatísticas são definidas para uma coluna, o ClickHouse acompanha os valores mínimo e máximo dessa coluna em cada parte. A poda de partes permite evitar a leitura de partes de dados inteiras quando a condição de filtro da consulta não pode corresponder a nenhuma linha nessa parte. Exemplo:

Tipos disponíveis de estatísticas de coluna

  • basic Um conjunto compacto de resumos de valor único derivados de uma coluna. Dependendo do tipo da coluna, os seguintes elementos são preenchidos:
    • para qualquer coluna cujos valores sejam representados por um número (inteiros, floats, Decimal*, Date*, DateTime*, Enum*, IPv4, …): os valores mínimo e máximo, que permitem estimar a seletividade de filtros por intervalo e habilitam a poda de partes;
    • para colunas String e FixedString: o comprimento total em bytes dos valores não NULL (a partir do qual é possível derivar o comprimento médio das strings);
    • para colunas Nullable e LowCardinality(Nullable): a contagem de valores NULL, que o otimizador usa para descontar linhas NULL das estimativas de seletividade. Uma única estatística basic pode preencher vários desses elementos ao mesmo tempo — por exemplo, em uma coluna Nullable(UInt32), ela rastreia tanto o mínimo/máximo numérico quanto a contagem de nulos. Em comparação com minmax, basic também funciona em colunas String / FixedString e pode ser declarada em wrappers Nullable de tipos como UUID ou IPv6 apenas para rastrear a contagem de nulos.
  • minmax (obsoleto)
As estatísticas minmax estão obsoletas e não podem mais ser criadas (CREATE TABLE ... STATISTICS(minmax) e ALTER TABLE ... ADD/MODIFY STATISTICS ... TYPE minmax retornam um erro). Tabelas e partes existentes com estatísticas minmax continuam funcionando. Use estatísticas basic no lugar.
  • tdigest
Estatísticas do tipo tdigest têm alto custo de criação e podem desacelerar a ingestão de dados.
Sketches TDigest que permitem calcular percentis aproximados (por exemplo, o percentil 90) para colunas numéricas.
  • uniq Sketches BJKST que fornecem uma estimativa de quantos valores distintos uma coluna contém. Internamente, usa uniq.
  • uniq_v2 Semelhante a uniq, mas internamente usa uniqCombined(12) (uma variante de HyperLogLog). Consome menos memória que uniq e pode ser compilado mais rapidamente.
  • countmin
Estatísticas do tipo countmin têm alto custo de criação e podem desacelerar a ingestão de dados.
Sketches CountMin que fornecem uma contagem aproximada da frequência de cada valor em uma coluna.

Tipos de dados suportados

Todos os itens acima também aceitam wrappers Nullable e LowCardinality(Nullable) dos tipos listados. Basic também pode ser declarado em wrappers Nullable de tipos como UUID ou IPv6 exclusivamente para rastrear a contagem de valores nulos.

Operações suportadas

Para basic em colunas String / FixedString, a estatística registra apenas o comprimento total, em bytes, dos valores não NULL (usado para estimar o comprimento médio da string) e a contagem de nulos; filtros por intervalo e poda de partes não usam essa estatística.

Configurações em nível de coluna

Determinadas configurações do MergeTree podem ser sobrescritas em nível de coluna:
  • max_compress_block_size — Tamanho máximo dos blocos de dados não comprimidos antes de serem comprimidos para gravação em uma tabela.
  • min_compress_block_size — Tamanho mínimo dos blocos de dados não comprimidos necessário para que a compressão ocorra ao gravar a próxima marca.
Exemplo:
As configurações de nível de coluna podem ser alteradas ou removidas usando ALTER MODIFY COLUMN, por exemplo:
  • Remova SETTINGS da declaração da coluna:
  • Altere uma configuração:
  • Restaura uma ou mais configurações e também remove a declaração da configuração na expressão de coluna da consulta CREATE da tabela.
Última modificação em 24 de julho de 2026