DEV Community

Gulshan Yadav
Gulshan Yadav

Posted on Originally published at misar.blog

OpenTelemetry vs Prometheus: Why You Almost Certainly Need Both

This gets framed as a choice, and it mostly isn't one. They occupy different layers, and a normal production setup runs both. Understanding why saves a lot of wasted migration effort.

What each one actually is

Prometheus is a metrics database with a query language. It scrapes numeric time series from your services, stores them, and lets you query them with PromQL. It is a backend: a place metrics live and are asked questions.

OpenTelemetry is a specification and a set of SDKs for producing telemetry. It defines how you instrument code to emit traces, metrics and logs, and it ships a Collector that receives, processes and forwards them. It is a pipeline: how telemetry gets from your code to wherever it is stored.

OTel does not store anything long-term. Prometheus does not instrument your code. Asking which to use is a little like asking whether to use a courier or a warehouse.

Where the overlap actually is

There is one real point of contention: metrics instrumentation.

Historically you added a Prometheus client library to your service, defined counters and histograms, and exposed /metrics for Prometheus to scrape. That works and is still widely deployed.

OpenTelemetry offers its own metrics SDK doing the same job, but vendor-neutrally — the same instrumentation can be exported to Prometheus, or to a commercial backend, or to several at once, without touching application code.

So the genuine question is not "Prometheus or OTel" but "Prometheus client libraries or OTel SDKs, in front of Prometheus?"

The answer for most teams

Instrument with OpenTelemetry. Store metrics in Prometheus.

The reason is not that OTel's metrics API is nicer — arguably the Prometheus client is simpler for pure metrics. It is that instrumentation is the expensive, sticky part. It is spread across every service, written by everyone, and rewriting it later is a large coordinated change nobody wants to schedule.

Backends are comparatively easy to change. If your instrumentation is vendor-neutral, swapping or adding a backend is a Collector config change. If your instrumentation is Prometheus-specific, it is a code change in every service.

Instrument once against a standard; keep the storage decision reversible.

The other reason: traces

Prometheus stores metrics. Metrics tell you that p99 latency rose. They cannot tell you which call in the request path caused it, because a metric is an aggregate — the individual requests were summed away.

Traces keep the individual request: every span, in order, with timing. When latency rises, a trace shows you it was a specific downstream call, on a specific code path, for a specific class of request.

Prometheus has no trace story. OpenTelemetry treats traces as first-class and links them to metrics through exemplars — so a spike in a histogram can carry a pointer to an actual slow request. In practice that link is the single most useful thing in the stack, and you only get it if traces and metrics come from the same instrumentation.

If you have only ever had metrics, this is the upgrade worth making. Most debugging time is spent going from "something is slow" to "this is what is slow", and that is precisely the gap traces close.

What a real setup looks like

Application code instrumented with OTel SDKs, emitting traces and metrics. An OTel Collector deployed per host or as a service, receiving that telemetry. From the Collector: metrics to Prometheus, traces to Tempo or Jaeger, logs to Loki. Grafana on top, querying all three.

The Collector is more useful than it first appears. Because everything passes through it, you can drop high-cardinality attributes, sample traces, redact fields that should never reach a vendor, and add resource attributes centrally. Doing any of that without a collection layer means redeploying every service.

The trap worth naming

Cardinality. Prometheus stores a separate time series for every unique combination of label values. Add a label with unbounded values — user ID, request ID, full URL path — and you generate millions of series. Prometheus will slow down and then fall over, and the failure looks like a Prometheus problem rather than an instrumentation problem.

OpenTelemetry makes this easier to do accidentally, because trace attributes are naturally high-cardinality and it is tempting to promote them to metric labels. Traces handle high cardinality fine. Metrics do not. Keep the distinction: identifying details belong on spans, and metric labels stay bounded and low.

When you do not need OTel

If you run a handful of services, you already have Prometheus client libraries working, and nobody is asking questions your metrics cannot answer — there is no urgent reason to migrate. It is real work for benefit you may not currently need.

The trigger to adopt it is usually one of: you want distributed tracing; you are tired of debugging across service boundaries with only aggregates; or you want to stop being locked to one backend's instrumentation format.

Summary

Not competitors, and not really a choice. OpenTelemetry is how telemetry is produced and moved; Prometheus is where metrics land and get queried. Instrument with OTel because instrumentation is the part you cannot cheaply redo, keep Prometheus as the metrics backend because it is excellent at that job, and add tracing because it answers the questions metrics structurally cannot.


Originally published at https://www.misar.blog/@mrgulshanyadav/articles/opentelemetry-vs-prometheus-why-you-almost-certainly-need-both

Top comments (0)