Quando usar dicionários vs JOINs
Os dicionários funcionam melhor quando um dos lados de um JOIN é uma tabela de consulta que cabe em memória. Em um JOIN padrão, o ClickHouse cria uma tabela hash a partir do lado direito antes de consultá-la com o lado esquerdo — mesmo que a maioria das linhas depois seja descartada pelos filtros deWHERE. Embora as versões mais recentes (24.12+) apliquem filtros antes de JOINs em muitos casos, isso nem sempre elimina essa sobrecarga. Com um dicionário, você chama dictGet inline, então as consultas só acontecem nas linhas que já passaram pela filtragem.
No entanto, dictGet nem sempre é a melhor escolha. Se você precisar chamar dictGet em uma grande porcentagem das linhas de uma tabela — por exemplo, em uma condição WHERE como dictGet('dict', 'elevation', id) > 1800 — pode ser melhor usar uma coluna comum com índices nativos. O ClickHouse pode usar PREWHERE para ignorar grânulos em uma coluna comum, mas dictGet é avaliado linha por linha, sem suporte a índice.
Como regra geral:
- Use dicionários para substituir JOINs com tabelas de dimensão pequenas, em que a chave de consulta já está disponível.
- Use colunas comuns e índices ao filtrar pelo valor consultado em muitas linhas.
Escolhendo um layout
A cláusulaLAYOUT controla a estrutura de dados interna do dicionário. Todos os layouts disponíveis estão documentados na referência de layouts.
Ao escolher um layout, use as seguintes diretrizes:
flat— o layout mais rápido (consulta simples por deslocamento em array), mas as chaves devem serUInt64e, por padrão, são limitadas a 500.000 (max_array_size). É o melhor para chaves inteiras com crescimento monotônico em tabelas de pequeno a médio porte. Distribuições esparsas de chaves (por exemplo, valores de chave 1 e 500.000) desperdiçam memória, já que o array é dimensionado com base na maior chave. Se você estiver esbarrando no limite de 500 mil, isso é um sinal para mudar parahashed_array.hashed_array— o padrão recomendado para a maioria dos casos de uso. Armazena atributos em arrays com uma tabela hash que mapeia chaves para índices no array. É quase tão rápido quantohashed, mas usa memória de forma mais eficiente, especialmente quando há muitos atributos.hashed— armazena o dicionário completo em uma tabela hash. Pode ser mais rápido quehashed_arrayquando há pouquíssimos atributos, mas consome mais memória à medida que o número de atributos cresce.complex_key_hashed/complex_key_hashed_array— use-os quando as chaves não puderem ser convertidas paraUInt64(por exemplo, chavesString). Eles seguem os mesmos trade-offs de desempenho que as versões sem chave complexa.sparse_hashed— troca CPU por menor uso de memória em comparação comhashed. Raramente é a melhor escolha — só é eficiente quando há um único atributo. Na maioria dos casos,hashed_arrayé mais adequado.cache/ssd_cache— armazenam em cache apenas as chaves acessadas com frequência. São úteis quando o conjunto de dados completo não cabe na memória, mas as consultas podem consultar a fonte em caso de cache miss. Não são recomendados para workloads sensíveis à latência nem para enriquecimento confiável no momento do insert; consulte Dicionários cache em caminhos de ingestão.direct— consulta a fonte a cada consulta, sem armazenamento em memória. Use quando os dados mudam com frequência demais para serem armazenados em cache ou quando o dicionário é grande demais para caber na memória.
Dicionários de cache em caminhos de ingestão
Os layouts baseados em cache —cache, complex_key_cache, ssd_cache e complex_key_ssd_cache — mantêm as entradas aquecidas apenas no servidor local. Cada servidor aquece seu cache de forma independente, e, após uma reinicialização ou substituição, o servidor começa com o cache vazio. As consultas com cache frio dependem da fonte do dicionário, o que adiciona latência e pode bloquear ou fazer falhar os inserts caso a fonte não consiga atender à consulta. No ClickHouse Cloud, o autoscaling e a substituição gerenciada de nós tornam essa situação mais provável.
Evite esses layouts para enriquecimento no momento da inserção em views materializadas incrementais ou em colunas MATERIALIZED e DEFAULT quando a previsibilidade da latência e a disponibilidade da fonte forem importantes. Em vez disso, use um layout que carregue o dicionário completo em cada servidor: normalmente hashed ou hashed_array para chaves UInt64, e complex_key_hashed ou complex_key_hashed_array para chaves do tipo string ou compostas. flat e outros layouts totalmente carregados também podem ser adequados. Se o dicionário completo não couber na memória, enriqueça os dados antes da inserção ou no momento da consulta, em vez de depender de um cache aquecido no caminho de ingestão.
Monitoramento do uso de dicionários
Acompanhe o consumo de memória e a saúde por meio da tabelasystem.dictionaries:
bytes_allocated— memória consumida pelo dicionário. Os dicionários armazenam dados sem compressão, portanto esse valor pode ser significativamente maior que o tamanho da tabela comprimida.hit_rateefound_rate— úteis para avaliar a eficácia do layoutcache.last_exception— verifique este campo quando um dicionário não conseguir ser carregado ou atualizado.