LinkMesh

Search docs, blog and changelog

ENDE

Snippets · Destinations

Store telemetry in ClickHouse

ClickHouse is a popular self-hosted store for high-volume telemetry. With create_schema the clickhouse exporter creates the database and its tables on start, and ttl sets how long rows live. In our test against ClickHouse 24.8 it created otel.otel_logs and wrote three log records into it.

Signals
LogsMetricsTraces
Source
OTLP
Components
otlpbatchmemory_limiter
Destination
ClickHouse

OpenTelemetry Collector

OpenTelemetry Collector YAML
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 25
  batch:
    timeout: 5s

exporters:
  clickhouse:
    endpoint: tcp://clickhouse.example.internal:9000?dial_timeout=10s
    database: otel
    username: ${env:CLICKHOUSE_USER}
    password: ${env:CLICKHOUSE_PASSWORD}
    # Creates the database and the otel_logs / otel_traces / otel_metrics_*
    # tables on start.
    create_schema: true
    ttl: 720h
    logs_table_name: otel_logs
    traces_table_name: otel_traces
    sending_queue:
      enabled: true
      queue_size: 5000
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s

service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [clickhouse]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [clickhouse]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [clickhouse]

LinkMesh

LinkMesh has no built-in template for this job. Add it as a custom destination in raw mode, with clickhouse as the component type and this body - the same lines as under clickhouse in the collector config:

Custom destination body (clickhouse)
endpoint: tcp://clickhouse.example.internal:9000?dial_timeout=10s
database: otel
username: ${env:CLICKHOUSE_USER}
password: ${env:CLICKHOUSE_PASSWORD}
create_schema: true
ttl: 720h
logs_table_name: otel_logs
traces_table_name: otel_traces
sending_queue:
  enabled: true
  queue_size: 5000
retry_on_failure:
  enabled: true
  initial_interval: 5s
  max_interval: 30s
  max_elapsed_time: 300s

How custom components are validated and rolled out: create a custom destination.

Download config.yamlValidated with otelcol-contrib v0.161.0 on 2026-10-26

Complete config files, each checked with the collector's own validate command (otelcol-contrib v0.161.0).

The gotcha

The ttl becomes part of the table definition. In our test ttl: 720h turned into "TTL toDateTime(Timestamp) + toIntervalDay(30)" in the CREATE TABLE statement the exporter issued, so retention is part of the table definition from the first start. Set ttl before the collector first runs against an empty database.

Manage this across a fleet

LinkMesh manages collector configuration across a fleet from one self-hosted control plane, so a change like this is made once rather than host by host. The first 25 Collectors are free after a no-card registration in the OpenSight Customer Portal (5 without one).

Doing this in LinkMesh: Create a custom source or destination