Jaeger 2.18 ClickHouse Backend Setup: 8.6x Compression
Set up Jaeger 2.18's new native ClickHouse tracing backend in minutes: the config file, the run command, and the storage savings to expect.
The short version
Jaeger 2.18 ships a native ClickHouse storage backend, and you turn it on with a single storage stanza in your Jaeger v2 config file. No sidecar plugin, no gRPC storage process. You point Jaeger at a ClickHouse instance, set create_schema: true, and it builds the tables on startup. On the project’s own 10 million span benchmark it hit 8.6x compression on the spans table while sustaining more than 50k spans per second of ingestion.
One caveat before you wire it into anything important: the ClickHouse backend is alpha in 2.18. Pin the exact version, keep it out of your primary production path for now, and treat it as a serious evaluation rather than a drop-in replacement for Elasticsearch or Cassandra.
Prerequisites
- A reachable ClickHouse server (a local
clickhouse-servercontainer is fine for testing). - The Jaeger v2 binary or the
jaegertracing/jaeger:2.18.0image. Jaeger v2 is built on the OpenTelemetry Collector, so its config uses the collector’s extension and pipeline model, not the old v1 flags. - An app already exporting OTLP traces, or anything that can send OTLP to port 4317/4318.
The config
Jaeger v2 declares storage as an extension and wires it into the traces pipeline through an exporter. Save this as config-clickhouse.yaml:
extensions:
jaeger_storage:
backends:
clickhouse-storage:
clickhouse:
addresses:
- localhost:9000
database: jaeger
auth:
basic:
username: default
password: password
create_schema: true
jaeger_query:
storage:
traces: clickhouse-storage
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
jaeger_storage_exporter:
trace_storage: clickhouse-storage
service:
extensions: [jaeger_storage, jaeger_query]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger_storage_exporter]
Three fields do the real work. addresses is the ClickHouse native protocol endpoint (port 9000, not the 8123 HTTP port). database is the target database, which should already exist. create_schema: true tells Jaeger to create the span and index tables inside that database on first start, so you do not hand-write DDL.
Run it
Start ClickHouse, then start Jaeger against the config. Both run as Docker containers, so a laptop is enough to kick the tires:
$ docker run -d --name clickhouse -p 9000:9000 clickhouse/clickhouse-server:latest
$ docker exec clickhouse clickhouse-client --query "CREATE DATABASE IF NOT EXISTS jaeger"
$ docker run --rm --name jaeger --network host -v "$(pwd)/config-clickhouse.yaml:/etc/jaeger/config.yaml" jaegertracing/jaeger:2.18.0 --config /etc/jaeger/config.yaml
The UI comes up on http://localhost:16686. Send a few OTLP spans, then confirm they landed in ClickHouse rather than trusting the UI alone:
$ docker exec clickhouse clickhouse-client --query "SELECT count() FROM jaeger.spans"
If that count climbs as traffic flows, the pipeline is healthy end to end.
What the compression actually buys you
The headline number comes from the Jaeger team’s published benchmark on a 10 million span dataset. ClickHouse stores spans in columnar form and compresses each column independently, which is why trace data (highly repetitive service names, operation names, and attribute keys) shrinks so well.
| Metric | Reported result |
|---|---|
| Spans table compression | 8.6x |
| Sustained ingestion | 50k+ spans/sec |
| Trace-by-ID retrieval | ~100 ms |
| Typical search query | under 50 ms |
Those are the project’s figures on their hardware, not a promise for your cluster. Your ratio moves with attribute cardinality and how many unique tag values you carry per span. Still, an 8.6x reduction on the largest table is the kind of change that turns a storage line item you dread into one you stop thinking about, which is the same pressure driving teams toward real-time cost observability elsewhere in the stack.
Two things worth knowing
Service Performance Monitoring works without a second datastore. In v2.18 Jaeger can compute latency, call rate, and error rate directly from the spans stored in ClickHouse, so you do not need a separate Prometheus-backed metrics path just to light up the Monitor tab.
Set retention with ClickHouse TTL, not a Jaeger flag. Because the tables are plain ClickHouse tables, you control retention with a TTL clause on the spans table (for example TTL timestamp + INTERVAL 30 DAY). That keeps trace retention a database concern instead of an application one.
If you are running the collector tier that feeds this backend, the same OpenTelemetry foundations apply on the ingest side. See managing OTel collectors at scale for the fleet story, and setting up observability with OpenTelemetry if you are still standing up the tracing pipeline itself.
Start it on a staging Jaeger, point one service’s traces at it, and watch the compression ratio on your own data before you commit. Alpha or not, the storage math is hard to argue with.
Related articles
Get the next article in your inbox
Practical DevOps tips, tutorials, and guides. No spam, unsubscribe anytime.