- As tabelas mudam com pouca frequência ou quando atualizações em lote são aceitáveis.
- Os relacionamentos não são muitos-para-muitos nem têm cardinalidade excessivamente alta.
- Apenas um subconjunto limitado das colunas será consultado, ou seja, certas colunas podem ser excluídas da desnormalização.
- Você tem a capacidade de deslocar o processamento para fora do ClickHouse, para sistemas upstream como Flink, onde o enriquecimento ou o achatamento em tempo real podem ser gerenciados.
Quando junções são necessários
- Evite produtos cartesianos: Se um valor no lado esquerdo corresponder a vários valores no lado direito, o JOIN retornará várias linhas — o chamado produto cartesiano. Se o seu caso de uso não precisar de todas as correspondências do lado direito, mas apenas de uma única correspondência qualquer, você pode usar junções
ANY(por exemplo,LEFT ANY JOIN). Eles são mais rápidos e usam menos memória do que junções regulares. - Reduza o tamanho das tabelas envolvidas no JOIN: O tempo de execução e o consumo de memória de junções crescem proporcionalmente ao tamanho das tabelas à esquerda e à direita. Para reduzir a quantidade de dados processados pelo JOIN, adicione condições de filtro extras nas cláusulas
WHEREouJOIN ONda consulta. O ClickHouse faz o pushdown das condições de filtro o mais profundamente possível no plano de consulta, geralmente antes dos junções. Se os filtros não forem aplicados automaticamente (por qualquer motivo), reescreva um dos lados do JOIN como uma subconsulta para forçar o pushdown. - Use direct joins via dicionários, se apropriado: junções padrão no ClickHouse são executados em duas fases: uma fase de compilação, que percorre o lado direito para construir uma tabela hash, seguida por uma fase de sondagem, que percorre o lado esquerdo para encontrar parceiros correspondentes por meio de lookups na tabela hash. Se o lado direito for um Dicionário ou outro table engine com características de chave-valor (por exemplo, EmbeddedRocksDB ou o Join table engine), o ClickHouse poderá usar o algoritmo de join “direct”, que elimina, na prática, a necessidade de construir uma tabela hash, acelerando o processamento da consulta. Isso funciona para junções
INNEReLEFT OUTERe é a opção preferida para workloads analíticas em tempo real. - Aproveite a ordenação da tabela em junções: Cada tabela no ClickHouse é ordenada pelas colunas da chave primária. É possível aproveitar essa ordenação usando os chamados algoritmos de join sort-merge, como
full_sorting_mergeepartial_merge. Diferentemente dos algoritmos de JOIN padrão baseados em tabelas hash (veja abaixo:parallel_hash,hash,grace_hash), os algoritmos de join sort-merge primeiro ordenam e depois mesclam as duas tabelas. Se a consulta fizer JOIN entre as duas tabelas por suas respectivas colunas de chave primária, o sort-merge terá uma otimização que elimina a etapa de ordenação, economizando tempo de processamento e overhead. - Evite junções com spilling em disco: Estados intermediários de junções (por exemplo, tabelas hash) podem ficar tão grandes que deixam de caber na memória principal. Nessa situação, o ClickHouse retornará, por padrão, um erro de falta de memória. Alguns algoritmos de join (veja abaixo), por exemplo
grace_hash,partial_mergeefull_sorting_merge, conseguem gravar estados intermediários em disco e continuar a execução da consulta. Ainda assim, esses algoritmos de join devem ser usados com cuidado, pois o acesso ao disco pode desacelerar significativamente o processamento do JOIN. Em vez disso, recomendamos otimizar a consulta JOIN de outras formas para reduzir o tamanho dos estados intermediários. - Valores padrão como marcadores de ausência de correspondência em junções externos: Junções externas à esquerda/à direita/completas incluem todos os valores da tabela esquerda/direita/de ambas as tabelas. Se nenhum parceiro correspondente for encontrado na outra tabela para algum valor, o ClickHouse substitui esse parceiro por um marcador especial. O padrão SQL determina que os bancos de dados usem NULL como esse marcador. No ClickHouse, isso exige encapsular a coluna de resultado em Nullable, criando uma sobrecarga adicional de memória e desempenho. Como alternativa, você pode configurar a setting
join_use_nulls = 0e usar o valor padrão do tipo de dado da coluna de resultado como marcador.
Use dicionários com cuidadoAo usar dicionários para junções no ClickHouse, é importante entender que, por natureza, eles não permitem chaves duplicadas. Durante o carregamento de dados, chaves duplicadas são deduplicadas silenciosamente — apenas o último valor carregado para uma determinada chave é mantido. Esse comportamento torna os dicionários ideais para relacionamentos um-para-um ou muitos-para-um, em que apenas o valor mais recente ou autoritativo é necessário. No entanto, usar um dicionário em um relacionamento um-para-muitos ou muitos-para-muitos (por exemplo, ao associar papéis a atores quando um ator pode ter vários papéis) resultará em perda silenciosa de dados, já que todas as linhas correspondentes, exceto uma, serão descartadas. Por isso, dicionários não são adequados para cenários que exigem fidelidade relacional completa em casos com múltiplas correspondências. Para saber mais sobre quando os dicionários ajudam (e quando não ajudam), consulte Boas práticas de dicionários.
Escolhendo o algoritmo de JOIN correto
- Parallel Hash JOIN (padrão): Rápido para tabelas do lado direito de pequeno a médio porte que caibam na memória.
- Direct JOIN: Ideal ao usar dicionários (ou outros motores de tabela com características de chave-valor) com
INNERouLEFT ANY JOIN— o método mais rápido para buscas pontuais, pois elimina a necessidade de criar uma tabela hash. - Full Sorting Merge JOIN: Eficiente quando ambas as tabelas estão ordenadas pela chave de junção.
- Partial Merge JOIN: Minimiza o uso de memória, mas é mais lento — melhor para fazer JOIN entre tabelas grandes com memória limitada.
- Grace Hash JOIN: Flexível e ajustável em termos de memória, bom para grandes conjuntos de dados com características de desempenho ajustáveis.
Cada algoritmo oferece suporte diferente aos tipos de JOIN. Uma lista completa dos tipos de JOIN compatíveis com cada algoritmo pode ser encontrada aqui.
join_algorithm = 'auto' (o padrão) ou controlá-lo explicitamente com base na sua carga de trabalho. Se precisar selecionar um algoritmo de JOIN para otimizar o desempenho ou o uso de memória, recomendamos este guia.
Para um desempenho ideal:
- Mantenha as junções ao mínimo em cargas de trabalho de alto desempenho.
- Evite mais de 3–4 junções por consulta.
- Faça benchmark de diferentes algoritmos com dados reais — o desempenho varia conforme a distribuição da chave de junção e o volume de dados.