Map(K, V) armazena pares chave-valor.
Ao contrário de outros bancos de dados, os maps no ClickHouse não exigem chaves únicas, ou seja, um map pode conter dois elementos com a mesma chave.
(O motivo é que os maps são implementados internamente como Array(Tuple(K, V)).)
Você pode usar a sintaxe m[k] para obter o valor da chave k no map m.
Além disso, m[k] faz uma varredura no map, ou seja, o runtime da operação é linear em relação ao tamanho do map.
Parâmetros
K— O tipo das chaves do Map. Qualquer tipo, exceto Nullable e LowCardinality aninhado com tipos Nullable.V— O tipo dos valores do Map. Qualquer tipo.
Query
key2:
Query
Response
k não estiver presente no map, m[k] retornará o default value do tipo de valor, por exemplo, 0 para tipos inteiros e '' para tipos String.
Para verificar se uma chave existe em um map, você pode usar a função mapContains.
Query
Response
Convertendo Tuple para Map
Tuple() podem ser convertidos para o tipo Map() usando a função CAST:
Exemplo
Query
Response
Leitura das subcolunas de Map
keys e values.
Exemplo
Query
Response
Serialização em buckets de Map no MergeTree
Map no MergeTree é armazenada como um único fluxo Array(Tuple(K, V)).
Ler uma única chave com m['key'] exige varrer a coluna inteira — cada par chave-valor de cada linha — mesmo quando apenas uma chave é necessária.
Em maps com muitas chaves distintas, isso se torna um gargalo.
A serialização em buckets (with_buckets) divide os pares chave-valor em vários subfluxos independentes (buckets) com base no hash da chave.
Quando uma consulta acessa m['key'], apenas o bucket que contém essa chave é lido do disco, ignorando todos os outros buckets.
Habilitando a serialização em buckets
basic para as partes de nível zero (criadas durante INSERT) e usar with_buckets apenas para as partes mescladas:
Como funciona
with_buckets:
- O número médio de chaves por linha é calculado com base nas estatísticas do bloco.
- O número de buckets é determinado pela estratégia configurada (consulte Configurações).
- Cada par chave-valor é atribuído a um bucket por meio do hash da chave:
bucket = hash(key) % num_buckets. - Cada bucket é armazenado como um subfluxo independente com suas próprias chaves, valores e offsets.
- Um fluxo de metadados
buckets_inforegistra a contagem de buckets e as estatísticas.
m['key']), o otimizador reescreve a expressão para uma subcoluna de chave (m.key_<serialized_key>).
A camada de serialização calcula a qual bucket a chave solicitada pertence e lê apenas esse bucket do disco.
Quando o map completo é lido (por exemplo, SELECT m), todos os buckets são lidos e remontados no map original. Isso é mais lento do que a serialização basic devido à sobrecarga de ler e mesclar vários subfluxos.
A ordem das chaves dentro de um valor de map pode diferir da ordem de inserção original ao usar a serialização
with_buckets. As chaves são distribuídas entre buckets por hash e remontadas na ordem dos buckets, não na ordem de inserção. Com a serialização basic, a ordem das chaves dos maps inseridos é preservada.basic e with_buckets podem coexistir na mesma tabela e são mescladas de forma transparente.
Configurações
| Configuração | Padrão | Descrição |
|---|---|---|
map_serialization_version | basic | Formato de serialização para colunas Map. basic armazena os dados como um único fluxo de array. with_buckets divide as chaves em buckets para leituras mais rápidas de chaves individuais. |
map_serialization_version_for_zero_level_parts | basic | Formato de serialização para partes de nível zero (criadas por INSERT). Permite manter basic para operações de INSERT a fim de evitar sobrecarga de gravação, enquanto as partes mescladas usam with_buckets. |
max_buckets_in_map | 32 | Limite superior para o número de buckets. A quantidade real depende de map_buckets_strategy. O valor máximo permitido é 256. |
map_buckets_strategy | sqrt | Estratégia para calcular a quantidade de buckets com base no tamanho médio do map: constant — sempre usa max_buckets_in_map; sqrt — usa round(coefficient * sqrt(avg_size)); linear — usa round(coefficient * avg_size). O resultado é limitado ao intervalo [1, max_buckets_in_map]. |
map_buckets_coefficient | 1.0 | Multiplicador para as estratégias sqrt e linear. Ignorado quando a estratégia é constant. |
map_buckets_min_avg_size | 32 | Número médio mínimo de chaves por linha para habilitar o uso de buckets. Se a média ficar abaixo desse limite, um único bucket será usado independentemente das outras configurações. Defina como 0 para desabilitar esse limite. |
Trade-offs de desempenho
with_buckets em comparação com a serialização basic em diferentes tamanhos de map (de 10 a 10.000 chaves por linha). A quantidade de buckets foi determinada pela estratégia sqrt, limitada a 32. Os números exatos dependem dos tipos de chave/valor, da distribuição dos dados e do hardware.
| Operação | 10 chaves | 100 chaves | 1.000 chaves | 10.000 chaves | Observações |
|---|---|---|---|---|---|
Busca de uma única chave (m['key']) | 1.6–3.2x mais rápido | 4.5–7.7x mais rápido | 16–39x mais rápido | 21–49x mais rápido | Lê apenas um bucket em vez da coluna inteira. |
| Busca de 5 chaves | ~1x | 1.5–3.1x mais rápido | 2.9–8.3x mais rápido | 4.5–6.7x mais rápido | Cada chave lê seu próprio bucket; alguns buckets podem se sobrepor. |
PREWHERE (SELECT m WHERE m['key'] = ...) | 1.5–3.0x mais rápido | 2.9–7.3x mais rápido | 5.3–31x mais rápido | 20–45x mais rápido | O filtro PREWHERE lê apenas um bucket; a leitura completa do map ocorre apenas para as linhas correspondentes. O ganho de desempenho depende da seletividade — menos grânulos correspondentes significam menos E/S do map completo. |
Varredura completa do map (SELECT m) | ~2x mais lento | ~2x mais lento | ~2x mais lento | ~2x mais lento | É necessário ler e remontar todos os buckets. |
| INSERT | 1.5–2.5x mais lento | 1.5–2.5x mais lento | 1.5–2.5x mais lento | 1.5–2.5x mais lento | Sobrecarga de aplicar hash às chaves e gravar em vários subfluxos. |
Recomendações
- Maps pequenas (< 32 chaves em média): Mantenha a serialização
basic. A sobrecarga da divisão em buckets não se justifica para maps pequenas. O valor padrãomap_buckets_min_avg_size = 32garante isso automaticamente. - Maps médias (32–100 chaves): Use
with_bucketscom a estratégiasqrtse as consultas acessarem chaves individuais com frequência. O ganho de velocidade é de 4–8x para buscas de uma única chave. - Maps grandes (100+ chaves): Use
with_buckets. Buscas de uma única chave são 16–49x mais rápidos. Consideremap_serialization_version_for_zero_level_parts = 'basic'para manter a velocidade de inserção próxima à referência. - Varreduras completas do map dominam o workload: Mantenha
basic. A serialização em buckets adiciona ~2x de sobrecarga para varreduras completas. - Workload misto (alguns buscas de chave, algumas varreduras completas): Use
with_bucketscom as partes de nível zero definidas comobasic. A otimizaçãoPREWHERElê apenas o bucket relevante para o filtro e depois lê o map completo somente para as linhas correspondentes, proporcionando um ganho líquido de velocidade significativo.
Abordagens alternativas
Map em buckets não se adequar ao seu caso de uso, há duas abordagens alternativas para melhorar o desempenho do acesso por chave:
Usando o tipo de dados JSON
max_dynamic_paths vão para uma estrutura de dados compartilhada, que pode usar a serialização advanced para otimizar leituras de um único caminho. Consulte o artigo no blog para ter uma visão geral detalhada da serialização advanced.
| Aspecto | Map com buckets | JSON |
|---|---|---|
| Leitura de uma única chave | Lê um bucket (que pode conter outras chaves). Todos os pares chave-valor no bucket são desserializados. | Caminhos frequentes são lidos diretamente das subcolunas dinâmicas. Caminhos pouco frequentes vão para dados compartilhados; com a serialização advanced, apenas os dados do caminho exato são lidos. |
| Tipos de valor | Todos os valores compartilham o mesmo tipo V | Cada caminho pode ter seu próprio tipo. Caminhos sem dica de tipo usam Dynamic. |
| Suporte a índice de salto | Funciona com alguns tipos de índice criados em mapKeys/mapValues | Índices de salto só podem ser criados em subcolunas de caminhos específicos, não em todos os caminhos/valores de uma só vez. |
| Leitura da coluna completa | ~2x mais lenta que basic devido à remontagem dos buckets | Sobrecarga da codificação do tipo Dynamic e da reconstrução dos caminhos. |
| Sobrecarga de armazenamento | Metadados adicionais mínimos | Maior devido à codificação do tipo Dynamic, ao armazenamento dos nomes dos caminhos e aos metadados adicionais na serialização advanced. |
| Flexibilidade de esquema | Tipos fixos de chave e valor na criação da tabela | Totalmente dinâmica — chaves e tipos de valor podem variar por linha. Dicas de caminhos tipados podem ser declaradas para caminhos conhecidos, permitindo acesso direto por subcoluna. |
JSON quando chaves diferentes precisarem de tipos de valor diferentes, quando o conjunto de chaves variar significativamente entre as linhas ou quando chaves acessadas com frequência forem conhecidas de antemão e puderem ser declaradas como caminhos tipados para acesso direto por subcoluna.
Sharding manual em várias colunas do tipo Map
Map em várias colunas com base no hash da chave, no nível da aplicação:
m{hash(key) % 4}. Nas consultas, leia da coluna específica: m{hash('target_key') % 4}['target_key'].
| Aspecto | Map com buckets | Sharding manual |
|---|---|---|
| Facilidade de uso | Transparente — gerenciado pelo mecanismo de armazenamento | Requer lógica de roteamento no nível da aplicação para inserções e consultas |
| Mesclagem vertical | Não suportada — todos os buckets pertencem a uma coluna | Suportada — cada coluna Map é uma coluna independente e pode passar por mesclagem vertical |
| Alterações de schema | A quantidade de buckets se adapta automaticamente por parte | Alterar o número de shards exige reescrever os dados ou adicionar novas colunas |
| Sintaxe de consulta | m['key'] funciona diretamente | É preciso calcular a coluna correta: m0['key'], m1['key'] etc. |
| Granularidade do bucket | Por parte, adapta-se às estatísticas dos dados | Fixa na criação da tabela |
- função map()
- função CAST()
- combinador -Map para o tipo de dado Map