Pular para o conteúdo principal
ClickHouse é feito para desempenho. Ele executa consultas de forma altamente paralela, usando todos os núcleos de CPU disponíveis, distribuindo os dados entre lane de processamento e, muitas vezes, levando o hardware quase ao limite. Este guia mostra como o paralelismo de consultas funciona no ClickHouse e como você pode ajustá-lo ou monitorá-lo para melhorar o desempenho em workloads maiores. Usamos uma consulta de agregação no conjunto de dados uk_price_paid_simple para ilustrar os principais conceitos.

Passo a passo: como o ClickHouse paraleliza uma consulta de agregação

Quando o ClickHouse ① executa uma consulta de agregação com um filtro na chave primária da tabela, ele ② carrega o índice primário em memória para ③ identificar quais grânulos precisam ser processados e quais podem ser ignorados com segurança:

Distribuindo o trabalho entre lanes de processamento

Os dados selecionados são então distribuídos dinamicamente entre n lanes de processamento paralelas, que transmitem e processam os dados bloco a bloco até o resultado final:

O número de n lanes de processamento paralelas é controlado pela configuração max_threads, que, por padrão, corresponde ao número de núcleos (threads) de uma única CPU disponíveis ao ClickHouse no servidor. No exemplo acima, assumimos 4 núcleos. Em uma máquina com 8 núcleos, a taxa de processamento de consultas praticamente dobraria (mas o uso de memória também aumentaria na mesma proporção), já que mais lanes processam dados em paralelo:

Uma distribuição eficiente das lanes é fundamental para maximizar a utilização da CPU e reduzir o tempo total da consulta.

Processamento de consultas em tabelas com shards

Quando os dados da tabela são distribuídos entre vários servidores em shards, cada servidor processa seu shard em paralelo. Em cada servidor, os dados locais são processados por lane de processamento paralelas, exatamente como descrito acima:

O servidor que recebe a consulta inicialmente coleta todos os resultados parciais dos shards e os combina no resultado global final. Distribuir a carga das consultas entre shards permite ampliar horizontalmente o paralelismo, especialmente em ambientes de alto throughput.
ClickHouse Cloud usa réplicas paralelas em vez de shardsNo ClickHouse Cloud, esse mesmo paralelismo é obtido por meio de réplicas paralelas, que funcionam de forma semelhante aos shards em clusters shared-nothing. Cada réplica do ClickHouse Cloud — um nó de computação sem estado — processa uma parte dos dados em paralelo e contribui para o resultado final, exatamente como faria um shard independente.

Monitoramento do paralelismo de consultas

Use estas ferramentas para verificar se sua consulta está aproveitando totalmente os recursos de CPU disponíveis e para diagnosticar quando isso não acontece. Estamos executando este teste em um servidor com 59 núcleos de CPU, o que permite ao ClickHouse demonstrar plenamente seu paralelismo de consultas. Para observar como a consulta de exemplo é executada, podemos instruir o servidor ClickHouse a retornar todas as entradas de log no nível de trace durante a consulta de agregação. Para esta demonstração, removemos o predicado da consulta — caso contrário, apenas 3 grânulos seriam processados, o que não fornece dados suficientes para que o ClickHouse use mais do que algumas lanes paralelas de processamento:
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
SETTINGS send_logs_level='trace';
① <Debug> ...: 3609 marks to read from 3 ranges
② <Trace> ...: Spreading mark ranges among streams
② <Debug> ...: Reading approx. 29564928 rows with 59 streams
Podemos ver que
  • ① o ClickHouse precisa ler 3.609 grânulos (indicados como marcas nos logs no nível trace) em 3 faixas de dados.
  • ② Com 59 núcleos de CPU, ele distribui esse trabalho em 59 fluxos paralelos de processamento — um por lane.
Como alternativa, podemos usar a cláusula EXPLAIN para inspecionar o plano físico de operadores — também conhecido como “pipeline de consulta” — da consulta de agregação:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple;
    ┌─explain───────────────────────────────────────────────────────────────────────────┐
 1. │ (Expression)                                                                      │
 2. │ ExpressionTransform × 59                                                          │
 3. │   (Aggregating)                                                                   │
 4. │   Resize 59 → 59                                                                  │
 5. │     AggregatingTransform × 59                                                     │
 6. │       StrictResize 59 → 59                                                        │
 7. │         (Expression)                                                              │
 8. │         ExpressionTransform × 59                                                  │
 9. │           (ReadFromMergeTree)                                                     │
10. │           MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59 0 → 1 │
    └───────────────────────────────────────────────────────────────────────────────────┘
Observação: leia o plano de operadores acima de baixo para cima. Cada linha representa uma etapa do plano de execução físico, começando com a leitura dos dados do armazenamento na parte inferior e terminando com as etapas finais de processamento na parte superior. Os operadores marcados com × 59 são executados de forma concorrente em regiões de dados não sobrepostas, em 59 lanes de processamento paralelas. Isso reflete o valor de max_threads e ilustra como cada etapa da consulta é paralelizada entre os núcleos de CPU. A interface web embutida do ClickHouse (disponível no endpoint /play) pode exibir o plano físico acima como uma visualização gráfica. Neste exemplo, definimos max_threads como 4 para manter a visualização compacta, mostrando apenas 4 lanes de processamento paralelas: Observação: leia a visualização da esquerda para a direita. Cada linha representa uma lane de processamento paralela que transmite dados bloco por bloco, aplicando transformações como filtragem, agregação e estágios finais de processamento. Neste exemplo, é possível ver quatro lanes paralelas correspondentes à configuração max_threads = 4.

Balanceamento de carga entre lanes de processamento

Observe que os operadores Resize no plano físico acima reparticionam e redistribuem fluxos de blocos de dados entre as lanes de processamento para mantê-las uniformemente ocupadas. Esse rebalanceamento é especialmente importante quando os intervalos de dados variam na quantidade de linhas que correspondem aos predicados da consulta; caso contrário, algumas lanes podem ficar sobrecarregadas enquanto outras permanecem ociosas. Ao redistribuir o trabalho, as lanes mais rápidas efetivamente ajudam as mais lentas, otimizando o tempo geral de execução da consulta.

Por que max_threads nem sempre é respeitado

Como mencionado acima, o número de n lane de processamento paralelas é controlado pela configuração max_threads, que, por padrão, corresponde ao número de núcleos de CPU disponíveis para o ClickHouse no servidor:
SELECT getSetting('max_threads');
   ┌─getSetting('max_threads')─┐
1. │                        59 │
   └───────────────────────────┘
No entanto, o valor de max_threads pode ser ignorado, dependendo do volume de dados selecionado para processamento:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 30
Como mostrado no trecho do plano de execução acima, embora max_threads esteja definido como 59, o ClickHouse usa apenas 30 fluxos concorrentes para varrer os dados. Agora vamos executar a consulta:
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
   ┌─max(price)─┐
1. │  594300000 │ -- 594.30 million
   └────────────┘
   
1 row in set. Elapsed: 0.013 sec. Processed 2.31 million rows, 13.66 MB (173.12 million rows/s., 1.02 GB/s.)
Peak memory usage: 27.24 MiB.   
Como mostrado na saída acima, a consulta processou 2,31 milhões de linhas e leu 13,66 MB de dados. Isso ocorre porque, durante a fase de análise do índice, ClickHouse selecionou 282 grânulos para processamento, cada um contendo 8.192 linhas, totalizando aproximadamente 2,31 milhões de linhas:
EXPLAIN indexes = 1
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
    ┌─explain───────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))             │
 2. │   Aggregating                                         │
 3. │     Expression (Before GROUP BY)                      │
 4. │       Expression                                      │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)   │
 6. │         Indexes:                                      │
 7. │           PrimaryKey                                  │
 8. │             Keys:                                     │
 9. │               town                                    │
10. │             Condition: (town in ['LONDON', 'LONDON']) │
11. │             Parts: 3/3                                │
12. │             Granules: 282/3609                        │
    └───────────────────────────────────────────────────────┘  
Independentemente do valor configurado de max_threads, o ClickHouse só aloca lane de processamento adicionais quando há dados suficientes para justificá-las. O “max” em max_threads refere-se a um limite superior, não a um número garantido de threads em uso. O que significa “dados suficientes” é determinado principalmente por duas configurações, que definem o número mínimo de linhas (163.840 por padrão) e o número mínimo de bytes (2.097.152 por padrão) que cada lane de processamento deve processar: Para clusters shared-nothing: Para clusters com armazenamento compartilhado (por exemplo, ClickHouse Cloud): Além disso, há um limite inferior rígido para o tamanho da tarefa de leitura, controlado por:
Não modifique essas configuraçõesNão recomendamos modificar essas configurações em produção. Elas são mostradas aqui apenas para ilustrar por que max_threads nem sempre determina o nível real de paralelismo.
Para fins de demonstração, vamos inspecionar o plano físico com essas configurações sobrescritas para forçar a concorrência máxima:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON'
SETTINGS
  max_threads = 59,
  merge_tree_min_read_task_size = 0,
  merge_tree_min_rows_for_concurrent_read_for_remote_filesystem = 0, 
  merge_tree_min_bytes_for_concurrent_read_for_remote_filesystem = 0;
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59
Agora o ClickHouse usa 59 fluxos simultâneos para varrer os dados, respeitando totalmente o max_threads configurado. Isso demonstra que, para consultas em conjuntos de dados pequenos, o ClickHouse limitará intencionalmente a concorrência. Use substituições de configuração apenas para testes — nunca em produção —, pois elas podem levar a uma execução ineficiente ou à contenção de recursos.

Principais pontos

  • O ClickHouse paraleliza consultas usando lanes de processamento vinculadas a max_threads.
  • O número real de lanes depende do volume de dados selecionados para processamento.
  • Use EXPLAIN PIPELINE e logs em nível trace para analisar o uso das lanes.

Onde encontrar mais informações

Se você quiser se aprofundar em como o ClickHouse executa consultas em paralelo e como alcança alto desempenho em escala, explore os seguintes recursos:
Última modificação em 10 de junho de 2026