Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Trabalhando com o tipo Map no ClickHouse

All quickstarts
ObservabilidadeOSS

Pré-requisitos

O que você vai criar

No OpenTelemetry, cada span de trace carrega um conjunto de atributos de recurso — metadados de chave-valor que descrevem a entidade que produziu a telemetria (nome do serviço, host, região de nuvem, pod do Kubernetes etc.). O conjunto de chaves varia entre serviços e ambientes, o que torna o tipo Map do ClickHouse uma escolha natural: as chaves são dinâmicas e específicas da aplicação, mas cada linha normalmente tem apenas algumas delas.

Neste guia de início rápido, você usará o clickhouse-local para carregar dados reais de traces do OTel de um arquivo CSV em uma tabela com colunas Map(LowCardinality(String), String) e aprenderá a consultar, filtrar, agregar e otimizar dados em map.

Baixe os dados de exemplo

O conjunto de dados contém 6.120 trace spans do OTel exportados de um aplicativo de demonstração com microsserviços. Cada linha inclui as colunas ResourceAttributes e SpanAttributes, que contêm pares chave-valor dinâmicos em maps JSON. Salve o arquivo em um diretório fácil de referenciar, por exemplo ~/data/data-otel-traces.csv.

Baixar data-otel-traces.csv (2.9 MB)

Veja como é uma única linha:

Timestamp:          2025-12-26 00:00:45.759467000
TraceId:            0da128e6e3c01bc38b6b43a33e5fa522
SpanId:             3774f759424e4006
ParentSpanId:       2fdd1e5b66605098
SpanName:           orders receive
SpanKind:           SPAN_KIND_CONSUMER
ServiceName:        accountingservice
Duration:           5361
StatusCode:         STATUS_CODE_UNSET
ResourceAttributes: {"host.name":"f19476836e47","os.type":"linux","process.pid":"1","process.command_args":"[\"./accountingservice\"]","process.executable.path":"...
SpanAttributes:     {"network.transport":"tcp","messaging.destination.name":"orders","messaging.kafka.message.offset":"232260","messaging.message.body.size":"216"...

Crie a tabela e carregue os dados

Inicie o clickhouse-local e crie a tabela a seguir com um esquema correspondente ao CSV. A coluna-chave é ResourceAttributes Map(LowCardinality(String), String) - usando LowCardinality no tipo da chave porque as chaves de atributo do OTel vêm de um conjunto relativamente pequeno e recorrente.

CREATE TABLE otel_traces
(
    Timestamp          DateTime64(9),
    TraceId            String,
    SpanId             String,
    ParentSpanId       String,
    SpanName           LowCardinality(String),
    SpanKind           LowCardinality(String),
    ServiceName        LowCardinality(String),
    Duration           UInt64,
    StatusCode         LowCardinality(String),
    ResourceAttributes Map(LowCardinality(String), String),
    SpanAttributes     Map(LowCardinality(String), String)
)
ENGINE = MergeTree()
ORDER BY (ServiceName, SpanName, toUnixTimestamp(Timestamp));

Agora, carregue o CSV usando o engine de tabela file. Ajuste o caminho para o local em que você salvou o arquivo:

INSERT INTO otel_traces
SELECT * FROM file('~/data/data-otel-traces.csv', CSVWithNames);

Confirme se os dados foram carregados:

SELECT count() FROM otel_traces;

Você verá 6.120 linhas.

Consulte os dados

Acesse uma chave específica — use a sintaxe de colchetes para obter um valor do map. Se a chave não existir em uma determinada linha, você receberá o valor padrão do tipo do valor (string vazia para String):

SELECT
    ServiceName,
    SpanName,
    ResourceAttributes['host.name']             AS host,
    ResourceAttributes['k8s.pod.name']          AS pod,
    ResourceAttributes['deployment.environment'] AS env
FROM otel_traces
LIMIT 10;

Filtre por um valor de map — encontre todos os spans com um nome de serviço específico:

SELECT
    Timestamp,
    SpanName,
    Duration / 1e6 AS duration_ms
FROM otel_traces
WHERE ResourceAttributes['service.name'] = 'cartservice'
ORDER BY Timestamp
LIMIT 10;

Verifique se uma chave está presente — nem todo span tem metadados do Kubernetes. Use mapContains para descobrir quais têm:

SELECT
    ServiceName,
    SpanName,
    mapContains(ResourceAttributes, 'k8s.node.name') AS has_node_info
FROM otel_traces
LIMIT 10;

Inspecione todas as chaves presentes no conjunto de dados — útil para entender o que a instrumentação está produzindo:

SELECT DISTINCT arrayJoin(mapKeys(ResourceAttributes)) AS key
FROM otel_traces
ORDER BY key;

Desdobre um map em linhas com ARRAY JOIN — transforme cada par chave-valor em uma linha própria, útil para criar inventários de atributos ou alimentar dashboards:

SELECT
    ServiceName,
    key,
    value
FROM otel_traces
ARRAY JOIN
    mapKeys(ResourceAttributes)  AS key,
    mapValues(ResourceAttributes) AS value
WHERE ServiceName = 'cartservice'
LIMIT 20;

Filtre maps com mapFilter — extraia apenas os atributos do Kubernetes de cada span:

SELECT
    ServiceName,
    mapFilter((k, v) -> k LIKE 'k8s.%', ResourceAttributes) AS k8s_attrs
FROM otel_traces
WHERE mapContains(ResourceAttributes, 'k8s.pod.name')
LIMIT 10;

Encontre spans com erro e seu contexto de recurso — combine filtros de coluna comuns com acesso a map:

SELECT
    Timestamp,
    ServiceName,
    SpanName,
    ResourceAttributes['host.name']    AS host,
    ResourceAttributes['k8s.pod.name'] AS pod,
    SpanAttributes['error.type']       AS error_type,
    SpanAttributes['error.message']    AS error_message
FROM otel_traces
WHERE StatusCode = 'STATUS_CODE_ERROR';

Agregação em maps com o combinador -Map

O combinador de agregação -Map do ClickHouse permite aplicar qualquer função de agregação a uma coluna Map e fazer com que ela opere em cada chave de forma independente. O resultado também é um Map — uma entrada por chave, com o valor agregado. Isso é especialmente útil para métricas OTel, em que counters ou gauges são armazenados como valores de map.

Para demonstrar, crie uma pequena tabela de métricas em que cada linha registra contagens de códigos de status HTTP como um Map(String, UInt64):

CREATE TABLE otel_http_status_counts
(
    Timestamp    DateTime,
    ServiceName  LowCardinality(String),
    StatusCounts Map(String, UInt64)
)
ENGINE = MergeTree()
ORDER BY (ServiceName, Timestamp);

INSERT INTO otel_http_status_counts VALUES
    ('2025-12-26 10:00:00', 'cart-service',      {'2xx': 150, '4xx': 12, '5xx': 3}),
    ('2025-12-26 10:01:00', 'cart-service',      {'2xx': 200, '4xx': 8,  '5xx': 1}),
    ('2025-12-26 10:00:00', 'inventory-service', {'2xx': 90,  '4xx': 5}),
    ('2025-12-26 10:01:00', 'inventory-service', {'2xx': 110, '4xx': 3,  '5xx': 2}),
    ('2025-12-26 10:00:00', 'payment-service',   {'2xx': 50,  '5xx': 10}),
    ('2025-12-26 10:01:00', 'payment-service',   {'2xx': 45,  '4xx': 2,  '5xx': 15});

Agora use sumMap para somar as contagens por código de status de cada serviço:

SELECT
    ServiceName,
    sumMap(StatusCounts) AS total_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;

O sufixo -Map funciona com qualquer função de agregação, então você pode usar minMap, maxMap ou avgMap com a mesma facilidade:

SELECT
    ServiceName,
    avgMap(StatusCounts) AS avg_by_status,
    maxMap(StatusCounts) AS peak_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;

Você também pode combiná-lo com outros combinadores. Por exemplo, sumMapIf permite fazer agregações condicionais — aqui, somando apenas as janelas de um minuto em que o serviço já apresentava erros:

SELECT
    ServiceName,
    sumMapIf(StatusCounts, StatusCounts['5xx'] > 0) AS totals_in_error_windows
FROM otel_http_status_counts
GROUP BY ServiceName;

Por que isso é importante para OTel: Quando seu OTel Collector grava, no ClickHouse, a discriminação por minuto dos códigos de status, sumMap permite consolidá-la em totais por hora ou por dia em uma única consulta — sem ARRAY JOIN, sem fazer unpivot, sem precisar conhecer de antemão o conjunto completo de chaves. Qualquer chave que apareça em alguma linha é incluída automaticamente no resultado.

Otimize para chaves usadas com frequência em consultas

Se você perceber que está sempre filtrando pela mesma chave do map — host.name é um exemplo comum —, pode extraí-la para uma coluna materializada. Assim, você evita a varredura linear no map a cada consulta:

ALTER TABLE otel_traces
    ADD COLUMN HostName String
    MATERIALIZED ResourceAttributes['host.name'];

Para os dados existentes, faça o backfill da coluna:

ALTER TABLE otel_traces MATERIALIZE COLUMN HostName;

Agora, WHERE HostName = 'prod-cart-01' lê uma única coluna dedicada, em vez do map inteiro. Esse é o padrão recomendado no schema do ClickHouse para OTel para qualquer atributo consultado com frequência.

Principais conclusões

  • Map(LowCardinality(String), String) é o tipo idiomático para atributos do OTel — flexível o bastante para lidar com conjuntos de chaves variáveis, e LowCardinality mantém o armazenamento dessas chaves eficiente.
  • A sintaxe com colchetes (map['key']) é a forma mais comum de acessar valores, mas lembre-se de que ela faz uma varredura linear — funciona bem para maps com dezenas de chaves, mas não é ideal para centenas.
  • Colunas materializadas são a saída: quando uma chave do map se torna um alvo frequente de filtro, promova-a a uma coluna real para ter acesso indexado e colunar.
  • mapContains, mapKeys, mapValues, mapFilter e ARRAY JOIN oferecem um conjunto poderoso de ferramentas para explorar e transformar dados de map sem sair do SQL.
  • O combinador de agregação -Map (sumMap, avgMap, maxMap, etc.) agrega cada chave de forma independente em todas as linhas — ideal para consolidar contadores de métricas do OTel sem precisar conhecer o conjunto de chaves com antecedência. Ele também se combina com outros combinadores (por exemplo, sumMapIf).

Próximos passos

Confira estes guias de início rápido:

Ou aprofunde-se com a documentação de referência:

ClickHouse Academy — Master ClickHouse with expert-designed training for every skill level
Check out the ClickHouse academy for on-demand and live training
Navigation