cloudparts

Self-hosted observability on the LGTM stack

Loki for logs, Grafana for dashboards, Tempo for traces and Mimir for metrics, fed by one OpenTelemetry pipeline and storing everything in object storage.

Fits: A platform team running observability for tens to hundreds of services, usually on Kubernetes6 technologies
Live model
What you’re watchingServices emit logs, metrics and spans (three colours) to a collector, which routes each signal to Loki, Mimir or Tempo; all three write their data to object storage. Engineers’ dashboards and searches go through Grafana, which sends LogQL, PromQL or TraceQL to the matching store, and the answers come back from recent data and from the bucket.

Click any box to open its page. Open it in the playground to rewire it, change the traffic or take parts down.

Grafana Labs calls its four open-source projects the LGTM stack, after the code-review shorthand “looks good to me”: Loki for logs, Grafana to look at them, Tempo for traces and Mimir for metrics. The three backends share one design: a stateless distributor takes writes, recent data is kept close at hand for queries, and everything older is written as files to an object storage bucket, which is the only place data is kept for the long term. Services are instrumented once with OpenTelemetry, and a collector, either the OpenTelemetry Collector or Grafana Alloy, sends each signal to its store. Grafana then queries each store in its own language, PromQL, LogQL and TraceQL, and links them through shared labels and trace IDs. It suits teams that want metrics, logs and traces in one place but would rather run the backends themselves and pay mainly for object storage.

How it flows

  1. 1

    Instrument once, collect in one place

    Services use OpenTelemetry SDKs and send OTLP to a collector: the OpenTelemetry Collector, or Grafana Alloy, Grafana Labs’ distribution of it with built-in Prometheus pipelines that also scrapes /metrics endpoints. The collector sends each signal to its backend: logs to Loki’s /otlp endpoint, metrics to Mimir’s /otlp endpoint or by Prometheus remote write, and spans to Tempo over OTLP.

  2. 2

    Loki indexes labels and stores log lines as chunks

    A few resource attributes, such as service.name and k8s.namespace.name, become Loki labels; other attributes are kept as structured metadata. The distributor hashes each stream to ingesters, which compress lines into chunks and flush them, with a small TSDB index of the labels, to the bucket. Searching a word means picking streams by label and scanning their chunks.

  3. 3

    Mimir writes through Kafka and keeps Prometheus blocks

    In Mimir 3’s ingest storage architecture, distributors validate samples and shard series across Kafka partitions; a write is acknowledged once Kafka has replicated it. Each ingester consumes one partition into a per-tenant Prometheus TSDB, cuts a block every two hours and uploads it. The compactor merges and deduplicates blocks, and store-gateways serve them to queries.

  4. 4

    Tempo turns spans into Parquet blocks

    In Tempo 3’s microservices mode, distributors write spans, sharded by trace ID, to Kafka. Block-builders consume them into Apache Parquet blocks in the bucket, and live-stores serve the most recent spans. An optional metrics-generator derives rate, error and duration metrics and a service graph from the spans and remote-writes them to Mimir, so every traced service gets request-rate, error and latency metrics.

  5. 5

    Grafana queries each store and links them

    Grafana has built-in Loki, Tempo and Prometheus data sources; Mimir is added as a Prometheus data source. A panel sends PromQL to Mimir, LogQL to Loki or TraceQL to Tempo, and each backend splits the query in its query frontend and reads recent data plus files from the bucket. Because all three use the same labels, Grafana can move from a latency spike to the traces and logs of the same service and time. Alert rules run in Grafana Alerting or in the Mimir and Loki rulers.

Why this shape works

  • Object storage holds all data at rest, so long retention costs bucket prices rather than replicated SSDs, and none of the three needs a separate database
  • One OpenTelemetry pipeline feeds all three stores, and shared labels such as service.name make jumping between metrics, traces and logs work without lookup tables
  • Each signal scales on its own: log volume, active series and span rate grow at different speeds, and each backend’s write and read paths scale separately
  • All four are open source (Grafana, Loki, Tempo and Mimir under AGPLv3) and are also sold as Grafana Cloud, so a team can move between self-hosted and hosted without changing queries or dashboards

Where it strains

  • Three distributed databases to run, each with its own components, hash rings and caches; Mimir 3 and Tempo 3 also want a Kafka-compatible cluster for production, so this is a platform team’s job, not a side task
  • Labels are the index in both Loki and Mimir: a label with unbounded values (user ID, request ID, pod name in some setups) multiplies streams and series and the memory and cost behind them
  • Loki has no full-text index, so a broad search over days of logs scans a lot of chunks; teams used to Elasticsearch need to narrow by label first
  • AGPLv3 obliges anyone who modifies these projects and offers them over a network to publish the changes, and some companies forbid AGPL software outright
  • Correlation only works if the labels really match: the same service name and environment on logs, metrics and spans, and trace IDs written into log lines

Common swaps

Built from

Architectures with parts in common

Sources

Improve this page on GitHub