Skip to main content
All quickstarts
ObservabilityOSS

Prerequisites

What you’ll build

In OpenTelemetry, every trace span carries a set of resource attributes — key-value metadata describing the entity that produced the telemetry (service name, host, cloud region, Kubernetes pod, etc.). The set of keys varies between services and environments, making this a natural fit for ClickHouse’s Map type: the keys are dynamic and application-specific, but any given row typically has only a handful of them. In this quickstart you’ll use clickhouse-local to load real OTel trace data from a CSV file into a table with Map(LowCardinality(String), String) columns, and learn how to query, filter, aggregate, and optimise map data.
1

Download the sample data

The dataset contains 6,120 OTel trace spans exported from a demo microservices application. Each row includes a ResourceAttributes and SpanAttributes column containing dynamic key-value pairs as JSON maps. Save the file to a directory you can easily reference, for example ~/data/data-otel-traces.csv.Download data-otel-traces.csv (2.9 MB)Here’s what a single row looks like:
2

Create the table and load the data

Launch clickhouse-local and create the following table with a schema matching the CSV. The key column is ResourceAttributes Map(LowCardinality(String), String) - using LowCardinality on the key type because OTel attribute keys are drawn from a relatively small, repeating set.
Now load the CSV using the file table engine. Adjust the path to where you saved the file:
Confirm the data was loaded:
You should see 6,120 rows.
3

Query the data

Access a specific key — use bracket syntax to pull a value out of the map. If the key doesn’t exist on a given row, you get the default for the value type (empty string for String):
Filter by a map value — find all spans from a specific service name:
Check whether a key exists — not every span has Kubernetes metadata. Use mapContains to find which ones do:
Inspect all keys present across the dataset — useful for understanding what instrumentation is producing:
Explode a map into rows with ARRAY JOIN — turn each key-value pair into its own row, handy for building attribute inventories or feeding dashboards:
Filter maps with mapFilter — extract only the Kubernetes-related attributes from each span:
Find error spans and their resource context — combine regular column filters with map access:
4

Aggregate across maps with the -Map combinator

ClickHouse’s -Map aggregate combinator lets you apply any aggregate function to a Map column and have it operate on each key independently. The result is itself a Map — one entry per key, with the aggregated value. This is especially powerful for OTel metrics, where counters or gauges are stored as map values.To demonstrate, create a small metrics table where each row records HTTP status code counts as a Map(String, UInt64):
Now use sumMap to total the counts per status code for each service:
The -Map suffix works with any aggregate function, so you can use minMap, maxMap, or avgMap just as easily:
You can also combine it with other combinators. For example, sumMapIf lets you conditionally aggregate — here, only summing the minute windows where the service already had errors:
Why this matters for OTel: When your OTel Collector writes per-minute status code breakdowns into ClickHouse, sumMap lets you roll them up to hourly or daily totals in a single query — no ARRAY JOIN, no unpivoting, no knowing the full set of keys in advance. Any key that appears in any row is automatically included in the result.
5

Optimise for frequently queried keys

If you find yourself constantly filtering on the same map key — host.name is a common one — you can extract it into a materialized column. This avoids the linear scan through the map on every query:
For existing data, backfill the column:
Now WHERE HostName = 'prod-cart-01' reads a single, dedicated column instead of the entire map. This is the recommended pattern in the OTel ClickHouse schema for any attribute you query frequently.

Key takeaways

  • Map(LowCardinality(String), String) is the idiomatic type for OTel attributes — flexible enough to handle varying key sets, and LowCardinality keeps the key storage efficient.
  • Bracket syntax (map['key']) is the most common way to access values, but remember it scans linearly — fine for maps with tens of keys, not ideal for hundreds.
  • Materialized columns are the escape hatch: when a map key becomes a hot filter target, promote it to a real column for indexed, columnar access.
  • mapContains, mapKeys, mapValues, mapFilter and ARRAY JOIN give you a rich toolkit for exploring and transforming map data without leaving SQL.
  • The -Map aggregate combinator (sumMap, avgMap, maxMap, etc.) aggregates each key independently across rows — ideal for rolling up OTel metric counters without needing to know the key set in advance. It composes with other combinators too (e.g. sumMapIf).

Next steps

Check out the following quickstarts next: Or go deeper with the reference documentation:
ClickHouse Academy — Master ClickHouse with expert-designed training for every skill level

Check out the ClickHouse academy for on-demand and live training

Last modified on September 1, 2026