Prerrequisitos
- clickhouse-local instalado en su equipo. Consulte la guía de configuración de clickhouse-local para empezar.
Lo que crearás
En OpenTelemetry, cada span de traza lleva un conjunto de atributos de recurso: metadatos clave-valor que describen la entidad que produjo la telemetría (nombre del servicio, host, región de Cloud, pod de Kubernetes, etc.). El conjunto de claves varía entre servicios y entornos, por lo que encaja de forma natural con el tipo Map de ClickHouse: las claves son dinámicas y específicas de la aplicación, pero una fila cualquiera normalmente solo tiene unas pocas.
En esta guía de inicio rápido usarás clickhouse-local para cargar datos reales de trazas de OTel desde un archivo CSV a una tabla con columnas Map(LowCardinality(String), String), y aprenderás a consultar, filtrar, agregar y optimizar datos de tipo Map.
Descarga los datos de muestra
El conjunto de datos contiene 6.120 spans de trazas de OTel exportados de una aplicación de microservicios de demostración. Cada fila incluye las columnas ResourceAttributes y SpanAttributes, que contienen pares clave-valor dinámicos en forma de mapas JSON.
Guarda el archivo en un directorio que puedas localizar fácilmente, por ejemplo ~/data/data-otel-traces.csv.
Descargar data-otel-traces.csv (2.9 MB)
Este es el aspecto de una sola fila:
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"...Cree la tabla y cargue los datos
Inicie clickhouse-local y cree la siguiente tabla con un esquema que coincida con el del CSV.
La columna clave es ResourceAttributes Map(LowCardinality(String), String); se usa LowCardinality en el tipo de clave porque las claves de los atributos de OTel provienen de un conjunto relativamente pequeño y repetitivo.
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));Ahora carga el CSV con el motor de tabla file. Ajusta la ruta a la ubicación donde guardaste el archivo:
INSERT INTO otel_traces
SELECT * FROM file('~/data/data-otel-traces.csv', CSVWithNames);Confirma que se hayan cargado los datos:
SELECT count() FROM otel_traces;Deberías ver 6.120 filas.
Consultar los datos
Acceder a una clave específica — usa la sintaxis de corchetes para obtener un valor del mapa. Si esa clave no existe en una fila concreta, obtendrás el valor predeterminado del tipo de valor (cadena vacía 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;Filtrar por un valor de Map — encuentra todos los spans de un nombre de servicio específico:
SELECT
Timestamp,
SpanName,
Duration / 1e6 AS duration_ms
FROM otel_traces
WHERE ResourceAttributes['service.name'] = 'cartservice'
ORDER BY Timestamp
LIMIT 10;Comprueba si existe una clave — no todos los spans incluyen metadatos de Kubernetes. Usa mapContains para identificar cuáles sí los incluyen:
SELECT
ServiceName,
SpanName,
mapContains(ResourceAttributes, 'k8s.node.name') AS has_node_info
FROM otel_traces
LIMIT 10;Inspecciona todas las claves presentes en el conjunto de datos — útil para entender qué está generando la instrumentación:
SELECT DISTINCT arrayJoin(mapKeys(ResourceAttributes)) AS key
FROM otel_traces
ORDER BY key;Desglosa un Map en filas con ARRAY JOIN â convierte cada par clave-valor en una fila independiente, lo que resulta útil para crear inventarios de atributos o usarlos en paneles:
SELECT
ServiceName,
key,
value
FROM otel_traces
ARRAY JOIN
mapKeys(ResourceAttributes) AS key,
mapValues(ResourceAttributes) AS value
WHERE ServiceName = 'cartservice'
LIMIT 20;Filtrar maps con mapFilter — extrae solo los atributos de 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;Encuentra spans con errores y su contexto de recurso — combina filtros de columna comunes con acceso 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';Agregar en mapas con el combinador -Map
El combinador de agregación -Map de ClickHouse permite aplicar cualquier aggregate function a una columna Map y hacer que opere sobre cada clave de forma independiente. El resultado es también un Map: una entrada por clave, con el valor agregado. Esto resulta especialmente útil para las metrics de OTel, donde los counters o gauges se almacenan como valores de mapas.
Para demostrarlo, crea una pequeña table de metrics en la que cada fila registre los recuentos de códigos de estado HTTP como un 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});Ahora usa sumMap para sumar el recuento por código de estado de cada servicio:
SELECT
ServiceName,
sumMap(StatusCounts) AS total_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;El sufijo -Map funciona con cualquier función de agregación, por lo que puedes usar minMap, maxMap o avgMap igual de fácilmente:
SELECT
ServiceName,
avgMap(StatusCounts) AS avg_by_status,
maxMap(StatusCounts) AS peak_by_status
FROM otel_http_status_counts
GROUP BY ServiceName;También puedes combinarlo con otros combinadores. Por ejemplo, sumMapIf te permite agregar de forma condicional: aquí, solo se suman las ventanas de un minuto en las que el servicio ya tenía errores:
SELECT
ServiceName,
sumMapIf(StatusCounts, StatusCounts['5xx'] > 0) AS totals_in_error_windows
FROM otel_http_status_counts
GROUP BY ServiceName;Por qué esto es importante para OTel: Cuando tu OTel Collector escribe en ClickHouse desgloses por minuto de códigos de estado, sumMap te permite consolidarlos en totales por hora o por día en una sola consulta — sin ARRAY JOIN, sin despivotar y sin necesidad de conocer de antemano el conjunto completo de claves. Cualquier clave que aparezca en cualquier fila se incluye automáticamente en el resultado.
Optimiza para las claves que se consultan con frecuencia
Si ves que filtras constantemente por la misma clave del map — host.name es una de las más comunes — puedes extraerla como una columna materializada. Esto evita el escaneo lineal por el map en cada consulta:
ALTER TABLE otel_traces
ADD COLUMN HostName String
MATERIALIZED ResourceAttributes['host.name'];Para los datos existentes, rellene la columna retroactivamente:
ALTER TABLE otel_traces MATERIALIZE COLUMN HostName;Ahora WHERE HostName = 'prod-cart-01' lee una única columna dedicada en lugar del mapa completo. Este es el patrón recomendado en el esquema de OTel de ClickHouse para cualquier atributo que consultes con frecuencia.
Puntos clave
Map(LowCardinality(String), String)es el tipo más idiomático para los atributos de OTel: ofrece la flexibilidad necesaria para manejar conjuntos de claves variables, yLowCardinalitymantiene eficiente el almacenamiento de las claves.- La sintaxis con corchetes (
map['key']) es la forma más habitual de acceder a los valores, pero recuerda que hace un escaneo lineal: funciona bien para mapas con decenas de claves, pero no es lo ideal para cientos. - Las columnas materializadas son la vía de escape: cuando una clave de mapa se convierte en un filtro frecuente, promuévela a una columna real para obtener acceso indexado y columnar.
mapContains,mapKeys,mapValues,mapFilteryARRAY JOINte proporcionan un completo conjunto de herramientas para explorar y transformar datos de tipo Map sin salir de SQL.- El combinador de agregación
-Map(sumMap,avgMap,maxMap, etc.) agrega cada clave de forma independiente en todas las filas: es ideal para consolidar contadores de métricas de OTel sin necesidad de conocer de antemano el conjunto de claves. También se puede combinar con otros combinadores (por ejemplo,sumMapIf).
Próximos pasos
A continuación, consulta estas guías de inicio rápido:
O profundiza con la documentación de referencia:
