How Does OpenTelemetry Interoperate with Prometheus Remote Write?

How Does OpenTelemetry Interoperate with Prometheus Remote Write?

A complete, step-by-step guide to how OpenTelemetry metrics travel into Prometheus-compatible storage: what gets translated, what gets lost, and how to run the bridge safely at scale.

Picture a busy hospital. One department records every patient’s heartbeat on paper charts, and a nurse collects the charts once every minute. Another department uses a modern system where each bed sends its readings to a central screen the moment they change. Both departments do the same job, but their forms, their vocabulary and their rhythm are different. Now the hospital director asks a simple question: can we show both on one screen without losing any meaning?

That question is exactly what engineers face when they connect OpenTelemetry to Prometheus Remote Write. This guide answers it slowly and clearly. You will learn what each side does, how the translation really works, where information survives, where it gets bent, and how to run the connection in production without nasty surprises.

1The Big Picture: Two Worlds That Must Talk

Before any technical detail, we need to see why these two worlds exist, why they keep meeting, and what the word “interoperate” honestly promises.

Two projects, two histories

Prometheus started at SoundCloud in 2012 as a system for watching the health of services. It joined the Cloud Native Computing Foundation in 2016 and became the default metrics tool of the Kubernetes era. Its big idea was simple. Every service shows a web page full of numbers. The Prometheus server visits that page on a timer, which is called a scrape. It stores what it finds and lets you ask questions using a query language named PromQL.

OpenTelemetry, usually shortened to OTel, arrived later. In 2019 two older projects, OpenTracing and OpenCensus, merged to form it. Its goal was bigger than metrics. It wants one vendor-neutral way to produce traces, metrics and logs from any programming language. It gives you code libraries called APIs and SDKs, a wire format called OTLP (the OpenTelemetry Protocol), and a program called the Collector that receives, changes and forwards telemetry. The metrics part of its specification reached stable status in 2023.

So one project is very strong at storing and querying metrics, and the other is very strong at producing and moving telemetry. Most real companies end up using both.

2012
Prometheus begins life at SoundCloud
2019
OpenTelemetry formed from OpenTracing and OpenCensus
2023
OpenTelemetry metrics specification declared stable

Why the two worlds must meet

Nobody wakes up wanting to translate between telemetry formats. The need appears because of everyday pressures inside a company. Here are the four that show up again and again.

Pressure 1

Existing investment

Teams already own hundreds of PromQL dashboards, alert rules and recording rules. Nobody wants to rewrite them just because a new instrumentation library arrived.

Pressure 2

New code uses OTel

New services and frameworks increasingly ship with OpenTelemetry built in, because one library can produce all three signals for any language.

Pressure 3

Scale and retention

A single Prometheus server cannot hold years of data for a whole company. Teams push data to a scalable storage system that speaks the Remote Write protocol.

Pressure 4

Freedom to switch

Standard formats on both sides mean you can change your storage backend or your instrumentation without rebuilding everything around it.

What “interoperate” really promises

When people say OpenTelemetry works with Prometheus Remote Write, they are bundling three different promises together. It helps to separate them, because each one has a different level of difficulty.

  • Delivery. The numbers arrive at the storage system. This is the easy part and it works reliably.
  • Meaning. A counter stays a counter, a histogram stays a histogram, and units are not mangled. This needs careful translation rules.
  • Queryability. You can write sensible PromQL against the data. This depends on naming, labels and a few habits that you control.
Analogy

Think of a travel adapter. A phone charger from one country does not fit the wall socket in another country. The adapter changes the shape of the plug so it fits. But a good adapter must also make sure the electricity inside is safe for the device. In our story, OTLP is the charger, Prometheus Remote Write is the foreign wall socket, and the OpenTelemetry Collector is the adapter. The shape change is easy. Keeping the “electricity”, which is the real meaning of each number, is where all the skill lives.

i
The short answer

OpenTelemetry applications send metrics using OTLP to a Collector. The Collector converts each OTLP data point into a Prometheus-style time series and sends batches of them to any backend that accepts Prometheus Remote Write. A newer path also exists where Prometheus-compatible backends accept OTLP directly and do the same conversion on their side.

2Core Concepts: How Prometheus Sees a Metric

To follow any translation, you first need to know the shape of the thing being translated into. Here is the Prometheus data model in plain words.

Time series, samples and labels

A metric is a number that tells you something about a system, such as how many requests arrived or how much memory is in use. Prometheus does not keep one number. It keeps a long list of numbers over time. Each entry in that list is a sample, which is just a pair of a timestamp and a value. A full list of samples that belong together is called a time series.

Now imagine you run a shop with ten cashiers. You would not want one single “sales” line, because you could not tell the cashiers apart. Prometheus solves this with labels. A label is a small name and value pair, like cashier equals “anita”. A metric name plus a unique set of labels defines exactly one time series. Change any label value and you get a different series.

Analogy

A time series is like the line on a heart monitor in a hospital. The metric name says “heart rate”. The labels are the name tag on the bed: which ward, which bed, which patient. Two beds mean two lines on the screen, even though both are called “heart rate”.

The four classic metric types

Prometheus describes a metric with a type. The type is mostly a hint for the person writing queries, but it also drives important rules about how numbers should behave over time.

Counter

Only goes up

Counts events such as requests served. It resets to zero only when a process restarts. You usually ask for its rate of change, not its raw value.

Gauge

Goes up and down

Shows a current level such as memory used, queue length or temperature. The raw value is meaningful on its own.

Histogram

Counts in buckets

Records how many observations fell below each limit, for example how many requests finished within 0.1 seconds, 0.5 seconds and so on. It lets you estimate percentiles.

Summary

Precomputed percentiles

Reports chosen percentiles calculated inside the application. These cannot be combined across many instances, which is why histograms are usually preferred.

A classic histogram is not stored as one series. It becomes several series: one per bucket limit, plus a running sum of all observed values and a running count of observations. The bucket series carry a special label named le, which means “less than or equal to”. Each bucket is cumulative, so a bucket for 0.5 seconds also includes everything that was faster than 0.1 seconds.

Native histograms

The classic bucket layout has a cost. Every bucket is its own series, so a histogram with many buckets and many label combinations creates a huge number of series. Newer Prometheus versions therefore support native histograms. A native histogram is stored as one series whose every sample carries a compact structure of buckets with automatically sized boundaries. This idea matters a great deal for us later, because it is the natural landing place for OpenTelemetry’s exponential histograms.

Cardinality: the quiet danger

The number of unique label combinations for a metric is called its cardinality. Each combination is a separate series, and each series uses memory and disk. A label such as “user id” or “request id” can create millions of series and bring a server to its knees. Remember this word. It will return in almost every production warning in this guide.

!
Watch out

Prometheus labels are cheap to add and expensive to keep. Anything with unlimited possible values, such as user ids, email addresses, full URLs or trace ids, does not belong in a label.

3Core Concepts: How OpenTelemetry Sees a Metric

OpenTelemetry describes metrics with a richer vocabulary than Prometheus. Knowing these extra ideas explains almost every translation decision that comes later.

API, SDK and instruments

In your code you use the OpenTelemetry API to say what you want to measure. You create an instrument, which is a named tool for recording numbers. The SDK sits behind the API and does the real work of adding the numbers up and sending them out. This split lets library authors use only the API, while the application owner chooses the SDK and where the data goes.

The main instruments are a counter that only goes up, an up-down counter that can go up and down, a gauge for a current reading, and a histogram for distributions. Several of them also come in an “observable” form, where the SDK calls your code on a timer to read the value instead of you pushing each change.

Aggregation and temporality

The SDK does not send every single measurement. It aggregates them: it adds up counts and fills histogram buckets in memory, then exports the result on a timer. By default this export happens about once every sixty seconds.

Here comes a concept that Prometheus does not have as an option: temporality. When a counter is exported with cumulative temporality, each export says “the total since the start”. When it is exported with delta temporality, each export says “the change since the last export”. Prometheus only understands the cumulative style. This difference creates real work for the Collector, as you will see in Chapter 11.

Analogy

Think of a car. Cumulative temporality is the odometer: it always shows the total distance since the car was built. Delta temporality is the trip meter that you reset after each stop: it shows only the distance since the last reset. Both tell the truth, but a system built to read odometers will get confused if you hand it trip meter readings.

Resource, scope and attributes

OpenTelemetry keeps three layers of descriptive information, and Prometheus flattens them into one flat set of labels.

  • Resource. Describes the thing that produced the data: the service name, its version, the host, the container or the cloud region. It is attached once per batch, not per data point.
  • Instrumentation scope. Names the library that created the metric, for example a web framework’s instrumentation package, along with its version.
  • Attributes. Describe one single data point, such as the HTTP method or the status code. These are the closest cousins of Prometheus labels.
Analogy

Imagine shipping boxes. The sender’s address printed on the outside of the whole shipment is the resource. The name of the packing team who filled the box is the scope. The little sticker on each item inside, saying “fragile” or “this side up”, is the attribute. Prometheus has only one place for stickers, so the sender’s address and the packing team have to be turned into stickers too, or left behind.

The instrument to wire-type map

When the SDK exports, each instrument turns into one of five OTLP data types. This table is the foundation for the mapping rules in Chapter 8.

InstrumentDefault OTLP data typeWhat it represents
CounterSum, monotonicA total that only increases
Up-down counterSum, not monotonicA total that can rise and fall
GaugeGaugeThe latest value of something
HistogramHistogram with explicit bucketsCounts per bucket, plus sum and count
Histogram, if configuredExponential histogramCounts in automatically scaled buckets

A fifth OTLP type called Summary exists mainly so that data scraped from older Prometheus summaries can be carried through. Normal OpenTelemetry instruments do not produce it.

One catch with default buckets

The default bucket limits of an explicit-bucket histogram were designed for values measured in milliseconds, running from zero up to ten thousand. If your instrument measures seconds, almost every observation will land in the very first bucket, and your percentile estimates will be useless. Always set bucket limits that match the real range of your values, or switch to exponential histograms, which size their own buckets.

i
Good habit

Pick the unit first, then pick the bucket limits to match. A request-duration histogram in seconds usually needs limits between a few milliseconds and several seconds, written as fractions of a second.

4Pull Versus Push: Where Remote Write Fits

The two projects grew up with opposite habits about who starts the conversation. Understanding that difference removes a lot of confusion.

The pull habit

In classic Prometheus, the server decides when to collect. It keeps a list of targets, visits each one every fifteen to sixty seconds, and reads the numbers. If a target is dead, the visit fails, and Prometheus knows something is wrong immediately. That is a lovely side effect: the act of collecting also acts as a health check.

The push habit

In OpenTelemetry, the application decides when to send. The SDK pushes its aggregated results on a timer to a receiving endpoint, over gRPC or HTTP. This suits short-lived jobs, serverless functions and places where the server cannot reach into the network to scrape.

Analogy

Pull is a milk delivery person who walks to each house and checks the empty bottles. Push is each house leaving a note on the doorstep whenever the fridge is running low. The milk person who walks the street also notices a house that is empty. The notes never tell you about a house that quietly went silent.

So where does Remote Write sit?

Here is a point many beginners miss. Prometheus Remote Write is itself a push protocol. It was created so that a Prometheus server can copy every sample it collects to a second system, usually one built for long-term storage and huge scale. Over time it became a general-purpose door that many storage systems offer for incoming time series data, even when no Prometheus server is involved at all.

That is what makes the interop possible. Remote Write is a push door that is already open on many backends. OpenTelemetry also pushes. So the Collector can take OTLP data, reshape it into the format that door expects, and push it through.

QuestionScrape (classic Prometheus)OTLP pushRemote Write
Who starts the transfer?Prometheus serverThe application SDKThe sender, such as Prometheus or a Collector
Data formatText expositionProtocol Buffers, rich modelProtocol Buffers, Prometheus model
Typical useCollecting from long-running servicesSending telemetry out of appsCopying series into scalable storage
Health check as a side effectYes, through a synthetic “up” seriesNoNo
TemporalityCumulative onlyCumulative or deltaCumulative only
!
Lost health check

When you move from scraping to push, you lose the automatic “up” signal that scraping produces. A silent application and a healthy but idle application can look the same. Plan separate alerts for missing data, for example by alerting when an expected series stops arriving.

5Inside the Remote Write Protocol

Remote Write looks mysterious from the outside, yet it is a small and clear contract. Learning what is inside a request explains why the translation rules exist.

What a request looks like

A Remote Write request is an ordinary HTTP POST. The body is a message written in Protocol Buffers, which is a compact binary format made by Google. That body is then compressed with the snappy method, which is fast rather than very small. A few headers tell the receiver what to expect, including the content encoding and a version string for the protocol.

Inside the message there is a list of time series. Each time series carries its own list of labels, and then one or more samples, where each sample is a timestamp in milliseconds and a number stored as a 64-bit float. A series can also carry exemplars, which are example trace links attached to a measurement, and in newer versions it can carry native histogram samples. The metric name itself is simply a label called __name__. Labels must be sorted by name, which lets receivers compare and hash them quickly.

Analogy

A Remote Write request is like a parcel sent through a courier. The parcel has a strict shape: a sorted address label on each item, a time stamp on each item, and the whole parcel is vacuum-packed to save space. The courier does not read your items. The receiving warehouse unpacks the parcel and sorts each item onto the right shelf.

How a sender works inside Prometheus

The most famous sender is the Prometheus server itself. It does not send straight from memory. As samples arrive, Prometheus first writes them into a write-ahead log, which is an on-disk journal used for crash recovery. A component called the WAL watcher reads new records from that journal and hands them to a set of in-memory queues. Each queue is served by a worker called a shard. Each shard collects samples into batches and sends them as separate HTTP requests in parallel.

All samples of one series are always sent by the same shard. This keeps the order of samples for that series correct, which matters because storage engines dislike samples that arrive out of order. Prometheus also watches how far behind the senders are and adds or removes shards automatically. If the remote side is slow, it adds shards. If it is fast, it removes them.

flowchart LR
  A["Scraped or ingested samples"] --> B["TSDB head in memory"]
  B --> C["Write ahead log on disk"]
  C --> D["WAL watcher"]
  D --> E["Queue per shard"]
  E --> F["Shard workers send in parallel"]
  F --> G["HTTPS POST with snappy protobuf body"]
  G --> H["Remote Write receiver"]
  H -->|"2xx OK"| I["Samples accepted"]
  H -->|"5xx or 429"| J["Retry with backoff"]
  H -->|"other 4xx"| K["Batch dropped"]
  J --> F
Figure 1: The life of a sample inside a classic Remote Write sender

Read the diagram from left to right. The journal on disk is what makes the sender robust: if the remote system is down for a while, the data is still waiting in the journal. The three response paths at the end are crucial. A success removes the batch from the queue. A temporary failure triggers a retry. A permanent client error means the batch is thrown away, because retrying a request that is wrong will never fix it.

The rules for retrying

The protocol gives clear guidance. Servers should answer with a 2xx code when they accept data. A 5xx code means “my fault, try again”. A 429 code means “you are sending too fast, slow down and try again”. Other 4xx codes mean “your request is bad”, and senders should not retry them. This simple rule matters in production, because one malformed or too-old sample can cause a whole batch to be rejected without any retry.

Remote Write 2.0

The original version of the protocol, often called 1.0, worked well but repeated a lot of text. Every series repeated every label name and value in every request. It also had no good home for metadata such as the help text or unit of a metric.

Version 2.0 of the specification fixes these gaps. It uses a shared symbol table so each unique string appears once per request. It lets each series carry metadata like the metric type, help text and unit. It supports a created timestamp, which records when a counter started counting. It treats native histograms as first-class citizens. And it adds response headers so a receiver can say how many samples, histograms and exemplars it actually wrote, which helps with partial failures. Support for 2.0 is growing in recent Prometheus 3.x releases and in the OpenTelemetry Collector, but support differs by version and by backend, so check the release notes of every component in your path before relying on it.

i
Why this matters for OpenTelemetry

OpenTelemetry carries units, descriptions and start times for every metric. Remote Write 1.0 has almost nowhere to put them, so they are either folded into the metric name or lost. Remote Write 2.0 gives them a proper place, which makes the translation cleaner.

6Architecture and Components of the Bridge

A working interop setup is a chain of small parts. Each part has one job, and knowing the jobs helps you find problems quickly.

The whole chain at a glance

The path starts with application code that uses the OpenTelemetry SDK. The SDK sends OTLP data to a Collector running close by. That first Collector often forwards to a second layer of Collectors that act as a central gateway. The gateway converts the data and sends it out using Remote Write to a metrics backend. Finally, a dashboard tool such as Grafana asks the backend questions using PromQL.

flowchart LR
  subgraph APPS["Application layer"]
    A1["Service A with OTel SDK"]
    A2["Service B with OTel SDK"]
    A3["Legacy service with metrics page"]
  end
  subgraph AGENT["Agent tier"]
    C1["Agent Collector one per node"]
  end
  LB["Load balancer"]
  subgraph GATEWAY["Gateway tier"]
    G1["Gateway Collector replica 1"]
    G2["Gateway Collector replica 2"]
  end
  subgraph STORE["Storage and query"]
    R["Remote Write endpoint"]
    S["Long term metrics store"]
    Q["Query layer with PromQL"]
  end
  D["Dashboards and alert rules"]
  A1 -->|"OTLP"| C1
  A2 -->|"OTLP"| C1
  A3 -->|"scraped by Prometheus receiver"| C1
  C1 -->|"OTLP"| LB
  LB --> G1
  LB --> G2
  G1 -->|"Remote Write"| R
  G2 -->|"Remote Write"| R
  R --> S
  S --> Q
  Q --> D
Figure 2: A typical two-tier OpenTelemetry to Remote Write architecture

In this picture, the agent tier is small and sits next to the applications. It absorbs bursts, adds basic labels and protects the application from slow networks. The gateway tier is where the heavier work happens: filtering, renaming, converting and finally exporting. The load balancer spreads incoming OTLP traffic across gateway replicas so that no single replica becomes a bottleneck or a single point of failure.

The parts inside a Collector

A Collector is built from four kinds of parts that you connect into a pipeline. Data enters through a receiver, passes through processors, and leaves through an exporter. Extensions run beside the pipeline and add helpers such as authentication.

Receiver

OTLP and Prometheus

The OTLP receiver listens for pushes over gRPC or HTTP. The Prometheus receiver does the opposite: it scrapes classic metrics pages using familiar scrape settings.

Processor

Shape the data

Memory limiter, batcher, attribute editors, filters, a general transform processor, and a delta-to-cumulative converter all live here.

Exporter

Remote Write exporter

Converts OTLP metrics into the Prometheus model and sends them out. It lives in the contrib distribution of the Collector.

Extension

Auth and health

Provides things like basic auth, bearer tokens, cloud request signing, health checks and profiling endpoints.

!
Pick the right build

The Remote Write exporter is not part of the smallest “core” Collector build. You need the contrib build, a vendor distribution that includes it, or a custom build made with the OpenTelemetry Collector Builder tool. Many failed first attempts come from running a build that simply does not contain the exporter.

The jobs of each processor you will meet

ProcessorJobWhy it matters for Remote Write
Memory limiterRefuses new data when memory is nearly fullPrevents the Collector from crashing during traffic spikes
BatchGroups data before exportCreates larger, more efficient Remote Write requests
Delta to cumulativeTurns change-since-last values into running totalsPrometheus only accepts cumulative values
Attributes and resourceAdd, rename or remove attributesControls which labels and which cardinality reach storage
FilterDrops unwanted metrics or pointsSaves money and protects the backend
TransformRewrites names and fields with a small expression languageFixes naming problems before they become permanent
Remote Write is the socket in the wall. OTLP is the plug from another country. The Collector is the adapter in between.

7Internal Working: How One OTLP Data Point Becomes a Time Series

This is the heart of the guide. We follow one data point through the Remote Write exporter and watch every decision it makes.

The translation step by step

When a batch of OTLP metrics reaches the exporter, it is organized like a tree: resources at the top, then scopes, then metrics, then data points. The exporter walks down the tree and flattens each data point into one or more Prometheus time series. Here is the path it takes, in order.

1

Read the metric type

The exporter checks whether the metric is a gauge, a sum, a histogram, an exponential histogram or a summary, and whether a sum is monotonic and cumulative or delta.

2

Decide if the point can be exported

Delta sums and delta histograms cannot be represented, so they are dropped with a warning unless an earlier processor converted them to cumulative.

3

Build the metric name

Dots and other illegal characters become underscores. A unit suffix such as seconds or bytes may be added. A counter may receive a total suffix.

4

Build the labels

Each data point attribute becomes a label after its name is cleaned. Resource information is turned into the job and instance labels, and a separate target information series.

5

Convert the timestamp

OpenTelemetry uses nanoseconds. Remote Write uses milliseconds. The value is divided, so sub-millisecond detail is lost.

6

Create the series

A histogram creates many series, one for each bucket plus a sum and a count. A gauge creates one. The exporter groups samples by a signature made from the full label set.

7

Batch, compress and send

Series are packed into requests below a size limit, compressed with snappy and posted. The queue and retry rules take over from here.

flowchart TD
  A["OTLP metric arrives at exporter"] --> B{"Which type?"}
  B -->|"Gauge"| C["One gauge series"]
  B -->|"Sum"| D{"Cumulative?"}
  B -->|"Histogram"| E{"Cumulative?"}
  B -->|"Exponential histogram"| F["Native histogram series"]
  B -->|"Summary"| G["Quantile series plus sum and count"]
  D -->|"No, delta"| H["Dropped with warning"]
  D -->|"Yes"| I{"Monotonic?"}
  I -->|"Yes"| J["Counter series"]
  I -->|"No"| K["Gauge series"]
  E -->|"No, delta"| H
  E -->|"Yes"| L["Bucket series plus sum and count"]
  C --> M["Build name and labels"]
  J --> M
  K --> M
  L --> M
  F --> M
  G --> M
  M --> N["Add job, instance and target info"]
  N --> O["Convert time to milliseconds"]
  O --> P["Batch and send"]
Figure 3: The decision tree followed for every OTLP metric

The diagram shows an important truth: the first fork depends on the type, and the second fork depends on temporality. Everything after the fork is mostly about names and labels. If you keep this picture in your head, almost every translation surprise becomes easy to explain.

A worked example in words

Imagine a web service named “checkout” that records a histogram called http.server.request.duration with a unit of seconds. One data point has the attribute “http.request.method” equal to “GET”. After translation, you would find a family of series with names like http_server_request_duration_seconds_bucket, along with _sum and _count companions. The attribute becomes a label called http_request_method. The service name becomes the job label. The instance identifier becomes the instance label. Every bucket series also gets the le label.

Analogy

Picture a post office sorting room where parcels from abroad arrive with foreign labels. A clerk reads each foreign label, rewrites it into local format, converts weights from pounds to kilograms, stamps the local date format, and splits any parcel that is too big into several smaller ones. Some parcels, such as those marked with a customs code the local system does not accept, are returned. That clerk is the exporter.

i
Debugging tip

When a metric looks wrong in PromQL, do not guess. Add a debug exporter next to the Remote Write exporter in the same pipeline. Compare what enters the pipeline with the names and labels you see in storage. The gap between the two is the translation, and it is almost always explained by one of the seven steps above.

8Mapping Each Metric Type in Detail

Each OpenTelemetry metric type has its own rule for how it lands in the Prometheus model. Some rules are perfect matches. Others are careful compromises.

OTLP typeBecomes in PrometheusFidelity
GaugeGauge seriesExact
Sum, monotonic, cumulativeCounter series, usually with a total suffixExact
Sum, not monotonic, cumulativeGauge seriesValue kept, counter meaning lost
Any delta typeNot supported directlyNeeds conversion first
Histogram with explicit bucketsBucket, sum and count seriesGood, minimum and maximum dropped
Exponential histogramNative histogramGood, with scale limits
SummaryQuantile, sum and count seriesExact

Gauges

This is the easiest case. A gauge is a number that can be read at any moment, and Prometheus gauges mean exactly the same thing. The name and labels are cleaned, and the value is passed along.

Monotonic cumulative sums

An OpenTelemetry counter that only increases becomes a Prometheus counter. By convention the translator adds a _total suffix, so a counter named “orders.processed” ends up as orders_processed_total. Counters work well with the PromQL rate function, because Prometheus can detect when the value drops and treat that as a restart.

Non-monotonic sums

An up-down counter can fall as well as rise, for example the number of items in a queue. Prometheus counters are not allowed to fall, so these are exported as gauges. The value is correct, but you should not use rate on it, and no total suffix is added.

Explicit-bucket histograms

OpenTelemetry stores the count for each bucket separately. Prometheus buckets are cumulative, so the exporter adds the counts up as it goes and finishes with a bucket whose limit is infinity. It then adds the sum and the count. OpenTelemetry histograms may also carry a recorded minimum and maximum. Prometheus classic histograms have no place for them, so they are dropped.

Analogy

Imagine a school measuring students’ heights. OpenTelemetry writes “ten kids in the 120 to 130 cm group, fifteen in the 130 to 140 group”. Prometheus writes “ten kids are under 130 cm, twenty-five kids are under 140 cm”. It is the same data in two accounting styles. The exporter does the running addition for you.

Exponential histograms and native histograms

Exponential histograms size their buckets automatically using a scale number, so they cover a very wide range with small relative error. Prometheus native histograms use a very similar idea, so the conversion is natural. There are limits, though. Prometheus only supports a certain range of scales, so very fine scales are reduced in resolution, and extremely coarse ones may not fit. Both the sender and the receiver need native histogram support switched on, and older Collector versions may drop exponential histograms completely. Test this path with your exact versions before depending on it.

Summaries

Summaries map one to one. Each reported quantile becomes a series with a quantile label, and the sum and count become their own series. OpenTelemetry instruments rarely create summaries, so you mostly meet them when scraping older Prometheus clients through the Collector.

Exemplars

OpenTelemetry measurements can carry exemplars, which are sample values linked to a trace and span. These can travel through Remote Write too, so a dashboard can show a dot on a latency graph that jumps straight to the trace. The receiving backend must be set up to store exemplars, and the exemplar’s trace link must be allowed to pass through any processors you configured.

!
Hidden loss

Three kinds of information commonly vanish in the translation: the minimum and maximum of a histogram, the exact start time of a counter in Remote Write 1.0, and the human-readable description of a metric. If your team relies on any of them, test early and decide whether Remote Write 2.0 or native OTLP ingestion is a better fit.

9Names, Units and Labels

Most “my dashboard shows nothing” problems are really naming problems. This chapter explains exactly how names and labels are rewritten, so you can predict the result before you deploy.

Why names need rewriting

OpenTelemetry names often contain dots, such as http.server.request.duration. Classic Prometheus names may contain only letters, digits, underscores and colons. So the translator replaces every illegal character with an underscore. Newer Prometheus versions can accept a much wider range of characters, including dots, but that needs matching support in every tool that reads the data, so many teams still choose the underscore style.

Suffixes and units

OpenTelemetry keeps the unit of a metric in a separate field, written in a standard notation. Prometheus has no such field in the original Remote Write format, so by convention the unit is placed at the end of the name. A duration measured in seconds ends up with a _seconds suffix. A size in bytes gets _bytes. A gauge with the plain unit “1”, which usually means a ratio between zero and one, may get a _ratio suffix. Units written in curly braces, such as a count of requests, are only annotations and are dropped. Compound units with a slash, like meters per second, are spelled out using the word “per”.

Counters get a _total suffix. This follows a long Prometheus habit that makes counters easy to recognize at a glance.

Analogy

Think of labeling jars in a kitchen. The OpenTelemetry style writes “sugar” on the lid and writes “grams” on a separate tiny sticker. The Prometheus style has no room for the second sticker, so it writes “sugar in grams” right on the lid. You lose nothing as long as everyone follows the same lid-writing rules.

Translation strategies

Because needs differ, modern tools let you choose how aggressive the renaming is. The exact option names depend on the tool and its version, but the choices look like this.

Strategy ideaDots become underscoresUnit and total suffixes addedBest used when
Escape and add suffixesYesYesYou want classic Prometheus-looking names. This is the usual default.
Escape without suffixesYesNoYou want names close to the original OpenTelemetry names but safe for old tools
Keep full text with suffixesNoYesYour whole toolchain supports full UTF-8 names
No translationNoNoYou want the original OpenTelemetry names to survive unchanged
!
Choose once, early

Changing the naming strategy later creates new series with new names. Old dashboards and alert rules stop matching. Decide on a strategy before you build dashboards, and write it in your team’s guidelines.

Rewriting label names

Attribute names go through a similar cleaning. Illegal characters become underscores, so http.request.method becomes http_request_method. A name that starts with a digit receives a prefix so it remains valid. Names that start with a single underscore may also receive a prefix, because double underscores at the start are reserved for Prometheus internal use.

Sometimes two different attribute names clean up to the same label name. For example, “a.b” and “a_b” both become “a_b”. The translator then joins their values into one label separated by a semicolon, rather than letting one overwrite the other. If you ever see a label value with a semicolon in it, this is usually the reason.

Rewriting label values

OpenTelemetry attributes can be text, whole numbers, decimals, true or false values, and even lists. Prometheus label values are only text, so every attribute is converted to its text form. A numeric status code of 200 becomes the text “200”. Lists are converted to a text representation of the list. Because of this, avoid using list or object attributes on metrics.

Habit 1

Use base units

Record time in seconds and sizes in bytes. This keeps names tidy and avoids confusing mixes such as milliseconds in one service and seconds in another.

Habit 2

Keep names stable

Treat metric names like a public API. Renaming a metric breaks every query built on it.

Habit 3

Bound your labels

Only use attributes that have a small, known set of values. Route templates are fine. Raw URLs are not.

Habit 4

Avoid reserved names

Do not create attributes whose names collide with labels like job, instance or le, because translation will mix them up.

10Resources, Target Info, Job and Instance

OpenTelemetry’s resource idea has no direct twin in Prometheus. The way the two are bridged is clever, and it is also the place where cardinality mistakes are most often made.

Job and instance

Every Prometheus series normally has two labels that say where it came from. The job label names the logical service, and the instance label names the particular running copy. When Prometheus scrapes, it fills these in itself. With push, nobody scrapes, so the translation must fill them in from the resource.

The rule is straightforward. The job label comes from the service name, and if a service namespace exists, the job becomes the namespace, a slash, and the service name. The instance label comes from the service instance identifier. If a resource has no service name, a placeholder name for an unknown service is used, which is a clear sign that someone forgot to configure the SDK.

!
Instance must be unique

If two running copies of a service share the same instance identifier, their series collide. Storage will see two writers sending different values for the same series and the same timestamp, and it will reject many of them as duplicates. Make sure every process has its own identifier, for example one built from the pod name in Kubernetes.

The target info series

A resource may hold thirty or more attributes: cloud provider, region, operating system, container image, version and more. Copying all of them onto every series would multiply storage cost. So the exporter writes them only once, into a special series named target_info. This series always has the value one, and its labels are the resource attributes. It also carries the job and instance labels, so it can be linked to other series.

When you want to filter or group a normal metric by something from the resource, such as the software version, you use a PromQL join. In words: take the request-rate series, match it to target_info using the shared job and instance labels, and copy the version label across. It is one extra step in a query, in exchange for a huge saving in storage.

Analogy

Think of a school. Each student has a library card number, which is short. The full details about the student, such as home address, parents’ names and class, are kept in one register. When the librarian needs those details, she looks up the card number in the register. She does not write the full address inside every borrowed book record. The register is target_info, and the card number is the job and instance pair.

Promoting selected attributes

Joins are powerful but slightly harder to write. A middle path is to promote a small number of important resource attributes into real labels on every series. The cluster name, the namespace and the environment are classic candidates, because you will filter by them in almost every dashboard.

The Remote Write exporter has an all-or-nothing switch that copies every resource attribute onto every series. That switch is convenient for small tests, but it can multiply cardinality in production. A safer pattern is to use a processor that copies only the few attributes you choose. The native OTLP receiver in recent Prometheus versions has a similar setting where you list exactly which resource attributes to promote.

Resource attributePromote to a label?Reason
Service name and namespaceAlready become jobNeeded by almost every query
Service instance identifierAlready becomes instanceSeparates running copies
Cluster nameYesFew values, used in nearly every filter
Kubernetes namespaceUsually yesBounded and widely used
Deployment environmentYesSeparates production from testing
Software versionUsually no, use a joinChanges often and multiplies series on every release
Container or pod unique idNoVery high cardinality and short-lived

What happens to scope information

The instrumentation scope, meaning the library name and version that produced a metric, is the piece most likely to be left out. Depending on the tool and its settings, it may become extra labels, it may become part of a separate info series, or it may be dropped. If you care about knowing which library produced a metric, check how your chosen path handles it and test with a real sample.

11Time, Temporality, Resets and Staleness

Numbers are only half of a metric. The other half is when they were measured and how a system knows a series has started, restarted or ended. Here the two worlds differ most.

Delta metrics need a converter

Some OpenTelemetry setups prefer delta temporality, where each export reports only the change since the last export. Teams like it because the SDK can forget old values after each export, which saves memory when there are many label combinations. Serverless functions also like it for the same reason.

Prometheus, however, expects running totals. To bridge this, the Collector offers a processor that turns deltas into cumulative values by keeping a running sum for every series in memory. This is powerful but it makes the processor stateful. That has three consequences you must plan for.

  • Every data point of one series must reach the same Collector replica, otherwise each replica builds a different running total. A load-balancing exporter that routes by series identity solves this.
  • If the replica restarts, the running total starts again from zero. Prometheus will read that as a counter reset, which is handled gracefully by rate calculations.
  • Series that stop reporting must eventually be forgotten, so the processor has settings for how long to remember a silent series and how many series to track at most.
Analogy

A cashier hands you receipts that each show only today’s sales. To report the year’s total to the bank, an accountant must keep a running ledger. If the accountant leaves and a new one starts a fresh ledger, the bank sees the total drop back to zero. The bank can cope if it knows that happened, but it must be the same single accountant all year, not three accountants each holding a third of the receipts.

Counter resets and start time

OpenTelemetry attaches a start time to every cumulative point. It says exactly when counting began, so a new start time clearly signals a restart. Prometheus does not have this field in the original protocol. It detects a reset only when a value goes down compared with the previous sample.

This leads to a rare but real blind spot. If a process restarts and its counter climbs past its old value before the next sample is taken, Prometheus never sees a drop and underestimates the growth. The created timestamp in Remote Write 2.0 is designed to close this gap. Some exporters can also write an extra series that carries the start time, which helps storage engines that understand it.

Staleness: how a series ends

When Prometheus scrapes a target and a series disappears from the page, it writes a special “stale” marker. Queries then stop showing that series right away. With push, nobody notices that a series went silent. So instead, queries keep showing the last value until a lookback window passes, which is five minutes by default.

OpenTelemetry does have a way to say “no value recorded” on a data point. A well-behaved exporter translates that flag into the Prometheus stale marker, so a series can end cleanly. However, most SDKs simply stop sending, and so most series fade out slowly after the lookback window.

Choosing time windows that work

The rate function in PromQL needs at least two samples inside its window. If your SDKs export every sixty seconds, a window of one minute may contain only one sample and return nothing. A good rule of thumb is to make the window at least four times the export interval. For sixty-second exports, use windows of five minutes or more.

TopicScrape worldPush world through Remote Write
Who sets the timestamp?Prometheus, at scrape timeThe SDK, at export time
How is a restart detected?Value decreasesValue decreases, or created timestamp in newer versions
How does a series end?Stale marker written automaticallyFades after lookback unless a no-value flag is sent
Sample spacingRegular, set by scrape intervalSet by SDK export interval, can drift
Two writers for one seriesRare, avoided by designCommon mistake, causes duplicate and out-of-order errors
i
Clock hygiene

Samples carry the timestamp the sender chose. If a machine’s clock is wrong by several minutes, its samples may be rejected as too old or too far in the future. Keep clocks synchronized with a time service on every node that runs an SDK or a Collector.

12Data Flow and Lifecycle: From Code to Chart

Let us now follow a single measurement on its whole journey, from the moment a line of code runs to the moment a person sees it on a graph.

sequenceDiagram
  participant App as Application code
  participant SDK as OTel SDK
  participant Col as Collector
  participant Que as Export queue
  participant Rec as Remote Write receiver
  participant DB as Time series store
  participant Gra as Dashboard
  App->>SDK: Record a measurement
  Note over SDK: Add to in memory aggregate
  SDK->>Col: Export aggregate over OTLP on timer
  Col->>Col: Memory check and batch
  Col->>Col: Convert to Prometheus time series
  Col->>Que: Place request in queue
  Que->>Rec: POST snappy protobuf
  Rec->>DB: Validate and write samples
  DB-->>Rec: Accepted
  Rec-->>Que: 2xx response
  Gra->>DB: PromQL query
  DB-->>Gra: Series and samples
Figure 4: A measurement travelling from code to dashboard

The journey in seven stops

1

Record

Your code adds to a counter or records a value in a histogram. This takes a tiny fraction of a millisecond, because nothing leaves the process yet.

2

Aggregate

The SDK keeps running totals and bucket counts in memory for each unique set of attributes.

3

Export on a timer

By default about once a minute, the SDK packs its aggregates into an OTLP message and pushes it to the Collector.

4

Process and convert

The Collector checks memory, groups points into batches and converts them into Prometheus series, following the rules from Chapters 7 to 10.

5

Queue and send

Requests wait in a queue and are sent by several parallel workers, with retry on temporary failures.

6

Ingest

The receiver checks each sample, for example for label limits and timestamp age, and writes it to storage.

7

Query

A dashboard or alert rule asks a PromQL question and the store returns the matching series.

60 s
Default SDK metric export interval
200 ms
Typical default batch timeout in the Collector
5 min
Default PromQL lookback for the last sample

These three numbers tell you where the time goes. The Collector adds a fraction of a second. The network and storage add a few seconds at worst. The big delay is the SDK export interval, so a measurement may take up to a minute or two to show up on a graph. If an alert needs faster reaction, shorten the SDK interval, but remember that more frequent samples also mean more storage.

Analogy

Imagine a postal system where each village collects letters in a box, and a van picks up the box once an hour. The van, the sorting office and the delivery to the final house take only minutes. Most of the waiting time is the letter sitting in the village box until the van arrives. The SDK export timer is that hourly van.

13Other Ways to Connect the Two Worlds

The Collector’s Remote Write exporter is the best-known path, but it is not the only one. The right choice depends on where your data starts and what your storage understands.

flowchart LR
  subgraph P1["Pattern A: Remote Write exporter"]
    A1["OTel SDK"] -->|"OTLP"| A2["Collector"]
    A2 -->|"Remote Write"| A3["Prometheus compatible store"]
  end
  subgraph P2["Pattern B: Native OTLP ingestion"]
    B1["OTel SDK"] -->|"OTLP"| B2["Collector or direct"]
    B2 -->|"OTLP"| B3["Store with OTLP endpoint"]
  end
  subgraph P3["Pattern C: Pull from SDK"]
    C1["OTel SDK with Prometheus exporter"] -->|"scraped"| C2["Prometheus server"]
    C2 -->|"Remote Write"| C3["Long term store"]
  end
  subgraph P4["Pattern D: Prometheus into OTel"]
    D1["Prometheus server"] -->|"Remote Write"| D2["Collector Remote Write receiver"]
    D2 -->|"OTLP"| D3["OTLP backend"]
  end
Figure 5: Four common integration patterns

Pattern A: Collector with the Remote Write exporter

This is the pattern the rest of this guide describes. It works with a very wide range of storage products because Remote Write is almost a universal door. You also get full control over processing inside the Collector.

Pattern B: Native OTLP ingestion

A growing number of storage systems accept OTLP directly. Recent Prometheus versions can be started with a flag that opens an OTLP receiving endpoint. Grafana Mimir, and several other systems built on the Prometheus model, expose OTLP endpoints too. In this pattern the translation to the Prometheus model happens inside the storage system rather than in your Collector, using the same rules you have just learned. The benefit is one fewer moving part to run. The drawbacks are less flexibility and a dependence on the storage system’s version for features such as delta handling or exponential histograms.

Pattern C: Let Prometheus scrape the SDK

Many OpenTelemetry SDKs include a Prometheus exporter that exposes a classic metrics page inside the application. A normal Prometheus server scrapes it, and that server can then use Remote Write to copy data to long-term storage. This keeps the pull habit and its free health check, and it is a gentle migration path for teams with a lot of existing Prometheus setup. It does not carry every OpenTelemetry concept, and the maturity of this exporter varies between languages.

Pattern D: From Prometheus into OpenTelemetry

The reverse direction is also useful. The Collector’s Prometheus receiver can scrape classic targets using standard scrape configuration, so a Collector can replace a Prometheus server as the thing that scrapes. In Kubernetes, the OpenTelemetry Operator includes a component called the Target Allocator, which discovers scrape targets from the same service monitor definitions many teams already use, and shares them fairly among several Collector replicas. There is also a Remote Write receiver for the Collector, which lets an existing Prometheus server send its Remote Write stream straight to a Collector that then forwards it as OTLP. At the time of writing, that receiver is newer and less settled than the exporter, so confirm its maturity for your version.

i
Scrape limits

The Prometheus receiver accepts the scraping part of a Prometheus configuration. It does not accept parts that make no sense inside a Collector, such as alerting rules or its own Remote Write settings.

Collector exporter strengths

  • Works with almost any Remote Write backend
  • Full control over filtering, renaming and routing
  • Can fan out to several destinations at once
  • Same Collector can also handle traces and logs

Collector exporter costs

  • One more component to run, size and monitor
  • Delta data needs an extra stateful processor
  • Translation behavior changes between Collector versions
  • Exemplars and native histograms need careful setup

14The Backend Landscape: Who Accepts Remote Write?

A Remote Write door is open on many products. The door looks the same on all of them, but the rooms behind it differ. Knowing those differences saves you from painful surprises.

The common backends

Self-managed

Prometheus itself

Can accept Remote Write when started with a receiver flag. It is fine for small and medium setups, but it is a single server with one disk, so it is not meant to be a giant shared sink.

Scalable

Grafana Mimir and Cortex

Horizontally scalable, multi-tenant systems built for huge series counts. They accept Remote Write and, in the case of Mimir, OTLP directly as well.

Scalable

Thanos Receive

The receiving side of the Thanos project. It accepts Remote Write, stores data and ships blocks to object storage for long retention.

Scalable

VictoriaMetrics and M3

Independent storage engines that understand the Prometheus protocol. Both accept Remote Write and offer their own scaling and compression approaches.

Managed

Amazon Managed Service for Prometheus

A fully managed workspace that accepts Remote Write on a workspace-specific address. Requests must be signed with AWS credentials, which the Collector can do through an authentication extension.

Managed

Google Managed Service for Prometheus

Google offers a dedicated Collector exporter that talks to its service natively, so for that target you do not use the general Remote Write exporter at all.

Questions to ask before you choose

Because Remote Write is a loose standard, two backends can both say “we accept Remote Write” and still behave differently. Before connecting your fleet, run through the following checklist with each backend.

QuestionWhy it matters
Does it accept native histograms?Without this, exponential histograms from OpenTelemetry cannot be stored as native histograms.
Does it store exemplars?Needed to jump from a latency graph to a trace.
Does it understand Remote Write 2.0?Determines whether units, help text and created timestamps survive.
How does it treat old and out-of-order samples?Push pipelines produce more late data than scraping does.
What are the label limits?Many systems cap the number of labels per series and the length of names and values. Resource promotion can hit these caps.
How does it handle multiple tenants?Shared systems often need a tenant header on each request.
What are the ingestion rate limits?A sudden rise in series count may cause throttling with 429 responses.
Is there a maximum request size?Your Collector batch size must stay under it.
Analogy

Many restaurants accept the same credit card. But one has a minimum spend, one will not accept the card for tips, and one only accepts it before nine in the evening. The card is the standard. The house rules are the part you have to read.

i
Where Alloy fits

Grafana also distributes a Collector-compatible agent named Alloy. It can receive OTLP, convert it to the Prometheus model and send it on with its own Remote Write component. If your team already uses Grafana tooling, it is another valid way to build the same bridge.

15Security: Protecting the Pipe and the Data

Metrics look harmless, yet they can reveal how your systems are built, and an open endpoint can be abused to flood your storage. Security should be designed in from the start.

Encrypt everything in transit

Every hop in the chain should use TLS: from the application to the Collector, between Collector tiers, and from the Collector to the storage endpoint. For machine-to-machine links inside your own network, many teams go one step further and use mutual TLS, where both sides present certificates, so each side knows exactly who it is talking to.

Prove who is sending

Remote Write backends do not accept anonymous strangers in production. The Collector supports several ways to prove identity through extensions.

Simple

Basic auth and bearer tokens

A username and password or a long secret token sent in a request header. Easy, but the secret must be protected and rotated.

Standard

OAuth2 client credentials

The Collector fetches short-lived tokens from an identity provider, so no long-lived secret sits in a file.

AWS

SigV4 request signing

Each request is signed with temporary AWS credentials. Access is controlled by a policy that can allow only the Remote Write action on one workspace.

Isolation

Tenant header

Shared systems such as Mimir and Cortex read a header to decide which tenant owns the data. It separates teams, but it is not a substitute for authentication.

Protect the front doors

The OTLP receiver listens on well-known ports, commonly 4317 for gRPC and 4318 for HTTP. If these are reachable from the open internet, anyone can send you junk metrics or try to overwhelm you. Bind receivers to private addresses, place them behind internal load balancers, require authentication when traffic crosses trust boundaries, and use network rules to allow only known senders. Recent Collector versions are moving toward listening on the local machine by default, which is a safer starting point.

Keep secrets out of files

Do not write tokens and passwords into configuration files that live in version control. Feed them through environment variables or a secret store, and rotate them on a schedule. For cloud-signed requests, prefer role-based identity attached to the workload so there are no stored keys at all.

Watch what labels reveal

Labels are data. If a developer adds an attribute holding an email address, a customer name or an internal host name, it will be copied into every series, stored for a long time and shown on shared dashboards. Use processors at the gateway to delete or hash sensitive attributes before export, and use review rules so new attributes are checked before they ship.

ThreatExampleDefence
EavesdroppingMetrics read on the networkTLS, and mutual TLS between tiers
ImpersonationA rogue sender pretends to be a serviceAuthentication extension, signed requests
FloodingMillions of fake series sent inNetwork limits, backend rate limits, cardinality caps
Data leakagePersonal data stored in labelsAttribute deletion or hashing at the gateway
Secret exposureToken committed to a repositorySecret stores, short-lived credentials, rotation
Over-broad accessCollector can read and delete dataLeast privilege: write-only permission
Analogy

Think of a post office loading dock. A locked van keeps parcels private on the road (TLS). A guard checks each driver’s badge before opening the gate (authentication). The dock only lets drivers drop parcels off; they cannot walk into the vault (least privilege). And a sign says “no parcels over a certain size”, which stops anyone from blocking the dock with a giant crate (rate and size limits).

16Performance, Scalability and High Availability

A bridge that works for ten services must also work for ten thousand. Here is how to size it, scale it and keep it alive when parts fail.

Size the load first

The load on Remote Write is easy to estimate. Count your active series and multiply by how often each series gets a new sample. One million active series sampled once a minute produce about sixteen thousand seven hundred samples per second. That number, not the number of services, decides how many Collector replicas and how many backend ingesters you need.

1 million
Example count of active series
60 s
Example sample interval per series
16,700
Samples per second the pipeline must carry

Batching and request size

Many small requests waste time on connection and header overhead. Larger batches are far more efficient. The batch processor groups data by count and by time, and the exporter splits very large payloads so each request stays under a maximum size, which is around a few megabytes by default. Always check that this limit is below the request size limit of your backend.

Queues, workers and backpressure

The exporter holds data in an in-memory queue and sends it with several parallel workers. The queue absorbs short slowdowns. If the backend stays slow longer than the queue can hold, the queue fills up and new data is rejected, which means metrics are lost. The names of these settings differ between Collector versions, so read the documentation for your version, but the ideas are always the same: how many items can wait, how many workers send, and how often to retry.

A memory limiter placed first in the pipeline is your safety valve. When memory use climbs too high it refuses new data early, which pushes pressure back to the senders instead of letting the Collector crash and lose everything at once. For stronger durability, the Collector can keep its queue on disk so that waiting data survives a restart, at the cost of some speed.

Scaling the Collector tier

Stateless pipelines scale by adding replicas behind a load balancer. Any replica can handle any request. Stateful pipelines, such as the delta-to-cumulative converter, need every point of a series to land on the same replica. In that case place a load-balancing exporter in an earlier tier that routes by a stable identity, such as the series, so each series always lands on the same replica in the next tier.

High availability and the single-writer rule

The golden rule of Remote Write is that each series should have exactly one writer at any time. Two senders writing different values at the same timestamp to one series cause conflicts. For pushed OpenTelemetry data, high availability usually comes from a load balancer that spreads and fails over traffic between healthy gateway replicas, so there is still only one writer per series at a time.

When you scrape targets with the Collector, high availability is often built by running two identical scrapers. In that case both replicas send the same series. Systems such as Mimir and Cortex offer a deduplication feature that elects one replica as the leader for each cluster, using two special labels, one naming the cluster and one naming the replica, and drops the other copy. Thanos solves a similar need by deduplicating at query time using a replica label.

flowchart LR
  T["Scrape targets"] --> R1["Collector replica A"]
  T --> R2["Collector replica B"]
  R1 -->|"Remote Write with replica label A"| D["Distributor with HA tracker"]
  R2 -->|"Remote Write with replica label B"| D
  D -->|"Leader copy kept"| I["Ingesters"]
  D -.->|"Duplicate dropped"| X["Discarded"]
  I --> S["Object storage and queriers"]
Figure 6: Two scraping replicas and a deduplicating receiver

In Figure 6, both Collectors scrape the same targets and send identical data. The receiver looks at the replica label, picks one leader and quietly drops the copy. If the leader dies, the tracker waits a short time and promotes the other replica, so there is only a brief gap rather than a long outage.

!
Do not duplicate by accident

Sending the same pushed data from two paths to the same backend without a deduplication feature is a very common cause of duplicate-sample errors. If you must send to two backends, use one pipeline that fans out to two exporters, and keep each backend with a single writer per series.

Old and out-of-order data

Scrape-based setups send samples in neat time order. Push setups may deliver samples late, for example after a network pause or when a queue drains after an outage. By default, storage engines reject samples that arrive older than the newest sample of that series. Most modern backends have a setting for an out-of-order time window that accepts slightly late data. Turn it on if your pipeline can deliver late data, and size it to your realistic delays.

Analogy

Picture a diary where each day gets its own page. A scrape setup writes pages in order every day. A push setup sometimes delivers yesterday’s note this morning. A strict diary refuses to add anything to a page already closed. A forgiving diary keeps the last few pages open for late notes. The out-of-order window is how many pages stay open.

17Monitoring, Failure Scenarios and Recovery

A pipeline that carries your monitoring data needs monitoring of its own. This chapter shows what to watch and how to recover when things break.

Watch the bridge itself

The Collector reports its own health as metrics. A few of them tell you almost everything. Watch the number of metric points successfully sent and the number that failed. Watch the queue size compared with the queue capacity, because a queue that is always nearly full means the backend cannot keep up. Watch the Collector’s memory use and the count of data points refused by the memory limiter. In recent versions many of these names carry a total suffix, so check the exact names in your version.

The receiving side also tells its own story. Storage systems count the samples they reject and give the reason: too old, out of order, duplicate, too many labels, label too long, or series limit reached. Send these counters to a dashboard and alert on any sustained rise. Many silent data gaps come from rejections nobody was watching.

Symptoms, causes and fixes

What you seeLikely causeWhat to do
Metric never appearsDelta temporality dropped by the exporterAdd a delta-to-cumulative step, or set cumulative temporality in the SDK
Name looks different from expectedDots changed and suffixes addedLook for the underscore name with unit and total suffixes
Job shows an unknown service nameService name not set in the SDKSet the service name resource attribute
Percentiles look wrongDefault bucket limits do not match the unitSet bucket limits suited to the value range, or use exponential histograms
Rate returns nothingWindow shorter than the export intervalUse a window at least four times the interval
Duplicate sample errorsTwo writers for one series, or non-unique instance idsGive every process a unique instance id and keep one writer per series
Too old sample errorsClock skew or long queue delaysFix time sync and consider an out-of-order window
Throttled with 429Ingestion limits reachedReduce series, raise limits, or slow senders
Rejected with a series or label limit errorToo many labels or too much cardinalityTrim attributes and promote fewer resource attributes
Graph lags behind realityLong SDK export intervalShorten the interval for important metrics

Failure scenarios and recovery

A

The backend is down

Requests fail with server errors and are retried. The queue fills. If the outage is short, nothing is lost. If it is long, the queue overflows and the oldest or newest data is dropped. A disk-backed queue and a larger queue size lengthen the time you can survive.

B

A Collector replica crashes

Data in its memory queue is lost. The load balancer routes new traffic to healthy replicas. Delta converters on that replica lose their running totals and restart from zero, which Prometheus reads as a counter reset.

C

The network is split

The Collector cannot reach the backend. Behavior is the same as a backend outage. When the link returns, the queue drains, and late samples may arrive out of order, so an out-of-order window helps.

D

Bad data poisons a batch

A batch containing a rejected sample may be refused entirely with a client error, which is not retried. Look at the receiver’s reason counters and fix the cause, such as the label that is too long.

E

A cardinality explosion

A new deploy adds an unbounded attribute. Series counts shoot up, the backend throttles or rejects. Roll back, delete the attribute with a processor at the gateway, and add a series limit alert so you hear about it earlier next time.

i
Practice the failure

Do not wait for a real outage to learn how your pipeline behaves. In a test environment, block the backend for ten minutes and watch queue size, drops and recovery. The numbers you learn will tell you how large your queue should be.

18Design Patterns, Anti-patterns and Best Practices

After you know how the pieces work, the remaining skill is arranging them well. These patterns and warnings come from the mistakes that teams repeat most often.

Patterns that work

Pattern

Agent plus gateway

A light Collector near each application and a central gateway tier for conversion and export. It isolates apps from backend slowness and centralizes rules.

Pattern

Filter at the edge

Drop unneeded metrics and unsafe attributes as early as possible, so you do not pay to move and store them.

Pattern

Fan-out to many destinations

One pipeline can export to a main store and to a cheaper archive or a second environment, with one writer per series per destination.

Pattern

Tenant routing

Route metrics of different teams or customers to different tenants of a shared backend using headers set in the gateway.

Pattern

Gradual migration

Run old scraping and new OTLP side by side with different metric names or labels, compare, then retire the old path.

Pattern

Naming guide as code

Keep one written naming and label standard, and check it in code review so every service follows it.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What it looks like

Turning on the setting that copies every resource attribute onto every series, “just to be safe”.

Why it hurts

Every series now carries dozens of extra labels. Storage grows, queries slow down, and attributes such as pod identifiers change often and create endless new series.

Better approach

Keep resource detail in the target info series, and promote only a short, chosen list of stable attributes.

ANTI-PATTERN 02Avoid
What it looks like

Running several Collector replicas that each hold part of a delta-to-cumulative state, behind a plain round-robin load balancer.

Why it hurts

Each replica builds a different running total for the same series. The result is jumping values and false counter resets.

Better approach

Route by series identity in a front tier, or ask the SDKs to use cumulative temporality when the destination is Prometheus-compatible.

ANTI-PATTERN 03Avoid
What it looks like

Leaving the default histogram bucket limits in place for a metric measured in seconds.

Why it hurts

Nearly every observation lands in the first bucket. Percentile estimates become flat and meaningless, and alerts on latency never fire.

Better approach

Set bucket limits to fit the real range of the values, or use exponential histograms when your whole path supports them.

ANTI-PATTERN 04Avoid
What it looks like

Putting user ids, session ids or raw URLs into metric attributes.

Why it hurts

Each unique value creates a new series. Series counts explode, the backend rejects data, and the bill grows rapidly.

Better approach

Use metrics for bounded groupings only, and send per-user detail to traces or logs where high detail belongs.

Best practices in one place

  • Standardize resource settings. Make sure every service sets a service name and a unique instance identifier.
  • Agree on one naming strategy before building dashboards, and document it.
  • Prefer cumulative temporality for Prometheus-compatible destinations unless you have a strong reason to use delta.
  • Put the memory limiter first and the batch processor near the end of the pipeline.
  • Limit cardinality at the gateway with filters, attribute deletion and backend limits.
  • Test version upgrades of the Collector and the backend on real samples, because translation behavior and defaults do evolve.
  • Monitor the monitoring. Alert on failed exports, a full queue, and rising rejection counters on the backend.
  • Keep one writer per series and keep clocks synchronized.

Common mistakes beginners make

Do this

  • Check the actual names that arrive in storage before writing queries
  • Use windows longer than the export interval in rate queries
  • Join with target info for rare resource details
  • Test the full path with a small sample first

Not this

  • Assuming names stay exactly as written in the code
  • Expecting a missing series to disappear instantly
  • Copying every attribute everywhere
  • Switching on delta temporality without a plan to convert it

19Advanced Topics: Native Histograms, Version 2.0 and Choosing a Path

Once the basics are running, three bigger questions decide how smooth your next two years will be: how you store distributions, how rich your metadata should be, and which integration path you commit to.

Why distributions deserve special care

Counters and gauges are cheap. A histogram is the opposite, because it is really a small table of numbers sent again and again. With classic buckets, each bucket is a separate series. If a service has fifteen buckets and a hundred label combinations, one histogram creates about sixteen hundred series, counting the sum and the count. Multiply that by hundreds of services and the bill becomes serious. This is the pressure that drives teams toward native histograms.

A native histogram keeps all buckets inside one sample. The bucket boundaries follow a mathematical pattern controlled by a single number called the schema, so the boundaries do not need to be written out each time. Because buckets are created only when observations land in them, empty ranges cost nothing. The result is a big drop in series count and, usually, better accuracy for percentile estimates.

Analogy

Imagine tracking the heights of everyone in a city. The classic way is to hang fifteen separate notice boards, one for each height range, and update each board every minute. The native way is a single notebook with one page per minute, where each page lists only the height ranges that actually had people in them. The notebook is smaller, and it can add more detail when needed without adding more boards.

Where exponential histograms meet native histograms

OpenTelemetry exponential histograms and Prometheus native histograms were designed with the same mathematical idea, so they line up closely. Each bucket is a fixed percentage wider than the one before it, so a single set of buckets can describe values from microseconds to hours with the same relative accuracy. When the schemas differ, the exporter reduces the resolution to the nearest supported level, merging neighbouring buckets. This loses a little detail but never invents data.

To use this path end to end, four things must all be true. The SDK must be set to produce exponential histograms. The Collector version must convert them rather than drop them. The backend must be started with native histogram support. And your dashboard tool must know how to query them. If any link is missing, the data either disappears or falls back to something less useful, so test the whole chain with a small service first.

!
Check the whole chain

A native histogram sent to a backend that does not expect it may be rejected or silently ignored. A missing percentile graph is often a missing feature switch on the receiving side, not a bug in the application.

What Remote Write 2.0 changes in practice

Think of version 2.0 as a better envelope. It does not change how you design metrics, but it removes several old compromises. Strings are sent once and referred to by number, so requests shrink noticeably when many series share long label names. Each series can say what type it is, what unit it uses and what it means in plain words, so you may no longer need to rely on suffixes alone. A created timestamp lets counters declare when they began, which closes the restart blind spot from Chapter 11. And response headers tell the sender exactly how many items were stored, so partial failures can be understood instead of guessed.

The cost is coordination. Sender and receiver must both speak version 2.0, and some products in between, such as gateways or proxies that inspect requests, may not understand it yet. Many teams therefore run version 1.0 for now, watch the release notes of their Collector and storage vendor, and switch when both ends are ready. Because the receiver tells the sender what it supports in many implementations, a careful rollout can often be done one destination at a time.

CapabilityVersion 1.0Version 2.0
Repeated label stringsSent in full every timeShared symbol table
Unit and descriptionFolded into names, mostly lostCarried as metadata per series
Counter start timeNot carriedCreated timestamp
Native histogramsSupported as an additionFirst-class
Partial failure feedbackLimitedCounts returned in response headers
Ecosystem supportVery wideGrowing, varies by version

Choosing a path with confidence

Because there are several ways to connect the two worlds, it helps to decide using a few plain questions rather than habit. Ask yourself where your data starts, what your storage understands and who will operate the pieces.

Choose when

Collector with Remote Write

You use several backends, need heavy filtering or renaming, or your storage only accepts Remote Write. It is also the safest choice if you must keep control of the translation rules yourself.

Choose when

Direct OTLP to the store

Your storage has a mature OTLP endpoint, your team is small, and you would rather run fewer components. Accept that translation behavior now follows the storage vendor’s releases.

Choose when

Scrape the SDK

You already run Prometheus well, your services are long-lived, and you want the free health signal that scraping gives. It is a gentle first step, not a final destination.

Choose when

Prometheus into a Collector

Your analysis or vendor platform wants OTLP, but your applications still expose classic pages. The Collector acts as the translator in the opposite direction.

Pick the path that you can monitor, upgrade and explain to a new teammate in ten minutes.

Cost thinking for architects

Every design choice in this guide has a price tag, even if it is hidden. More series means more memory in ingesters and more disk in object storage. More frequent samples means more network traffic and more compute for queries. Longer retention multiplies storage. Extra labels multiply series. For this reason, mature teams track cost per thousand active series and review it each quarter, just as they review server costs. They also use recording rules, which pre-calculate expensive queries, so dashboards stay quick even when raw data is large.

A useful habit is to estimate the cost of a new metric before adding it. Multiply the number of label combinations by the number of services and by the sample interval, and compare the result with your budget. A metric that is cheap in one service can be expensive when copied into a thousand. Treat this estimate as part of normal design review, in the same way you would review a new database table.

i
Keep a metric catalogue

Keep a simple shared list of important metrics with their owner, unit, labels and purpose. When someone wants to rename or remove a metric, the catalogue shows who will be affected. It also makes onboarding new engineers much faster.

20Real-World and Industry Examples

The ideas in this guide were not invented in a lab. They grew out of real scaling pain at real companies, and they show up again in the typical situations below.

How the ecosystem grew around Remote Write

Uber and M3

Uber needed to store and query metrics at a size that single Prometheus servers could not handle. It built a metrics platform called M3 and later open-sourced it. M3 accepts data using the Prometheus Remote Write protocol, which shows how the protocol became a doorway into purpose-built storage.

Improbable and Thanos

The engineers behind Thanos wanted long retention and a single global view across many Prometheus servers. Their project added a receiving component that accepts Remote Write and keeps data in cheap object storage.

Weaveworks, Cortex and Grafana Mimir

Cortex began as a horizontally scalable, multi-tenant home for Prometheus data. Grafana Labs later built Mimir from that foundation, with high ingestion rates and direct support for both Remote Write and OTLP. The deduplication feature for replicated scrapers described in Chapter 16 comes from this family.

Cloud providers

Amazon offers a managed Prometheus-compatible service that takes Remote Write with signed requests, and pairs naturally with an AWS-supported distribution of the OpenTelemetry Collector. Google offers a managed Prometheus service and a dedicated Collector exporter. These offerings show that big platforms treat the Prometheus model as a shared language, even when the producing side uses OpenTelemetry.

Everyday situations where the bridge appears

Scenario

Kubernetes platform team

Hundreds of services, mixed instrumentation. The team deploys agent Collectors per node and a gateway tier. Old services keep exposing metrics pages that the Collector scrapes, and new services push OTLP. Everything leaves through a single Remote Write exporter.

Scenario

Serverless functions

Short-lived functions cannot be scraped because they may be gone in seconds. They push OTLP to a Collector, which converts delta values to cumulative ones and writes to a managed Prometheus service.

Scenario

Migration from a vendor agent

A company wants freedom from a single vendor. It instruments with OpenTelemetry, sends to a Prometheus-compatible store, and keeps its dashboards in a tool that speaks PromQL.

Scenario

Multi-tenant software product

A provider hosts many customers. The gateway sets a tenant header per customer and routes each customer’s metrics to a separate tenant in a shared backend, with strict series limits.

A sensible rollout plan

1

Pilot one service

Instrument a single non-critical service, send it through a test Collector, and read the actual names and labels that arrive.

2

Write the standards

Record your naming strategy, required resource attributes, allowed label list and temporality choice.

3

Build the gateway tier

Add the memory limiter, batcher, filters and the Remote Write exporter with authentication, then load test it.

4

Watch the pipeline

Create dashboards and alerts for failures, queues and backend rejections before sending real production volume.

5

Expand gradually

Onboard services in waves, compare results with the old path, and retire the old path only when numbers agree.

21Frequently Asked Questions

Quick answers to the questions that come up most when teams first connect these two worlds.

Q1Can I keep using PromQL, dashboards and alert rules with OpenTelemetry metrics?
Yes. Once the data lands in a Prometheus-compatible store, PromQL works on it like any other series. The main work is learning how the names and labels look after translation, and then updating queries to match.
Q2Does Remote Write replace scraping?
No. They solve different problems. Scraping is how data is collected from a target. Remote Write is how already-collected data is sent onward. Many setups use both, and the Collector can scrape and then export with Remote Write.
Q3Why does my metric name look different in the store?
The translator changes dots to underscores, adds a unit suffix and adds a total suffix for counters. Look for the rewritten name, and decide on a naming strategy so the result is predictable.
Q4Are delta metrics supported?
Not directly through Remote Write, because Prometheus expects running totals. Use a delta-to-cumulative processor in the Collector with sticky routing, or configure your SDKs to use cumulative temporality.
Q5Do traces and logs travel through Remote Write too?
No. Remote Write is only for metrics. Traces and logs use their own exporters and their own backends.
Q6Should I use the Remote Write exporter or send OTLP directly to the backend?
If your backend accepts OTLP and you do not need complex processing, direct OTLP means fewer parts to run. If you need wide backend compatibility, fan-out, or heavy filtering in your own hands, the Collector with the Remote Write exporter is the more flexible choice.
Q7Can a Prometheus server receive Remote Write directly?
Yes, it has a receiver you can switch on. It is suitable for modest volumes. For large shared ingestion, scalable systems built for the job are a better fit.
Q8Do exemplars and trace links survive the trip?
They can. The exporter can carry them, but the backend must be configured to store exemplars, and your processors must not strip them out.
Q9What happens to metric descriptions and units?
With Remote Write 1.0, units are folded into the name and descriptions are mostly lost. Remote Write 2.0 can carry them as metadata when both sides support it.
Q10How do I stop cardinality from exploding?
Only use bounded attributes, promote very few resource attributes, remove risky attributes with a processor at the gateway, and set series limits plus alerts in the backend.
Q11Do I need Remote Write 2.0?
Not to get started. Version 1.0 works widely today. Version 2.0 improves efficiency and metadata, and is worth adopting once your Collector and backend versions both support it well.
Q12How do I send to Amazon Managed Service for Prometheus?
Point the exporter at the workspace’s Remote Write address and attach the request-signing authentication extension so each request is signed with AWS credentials. Give the identity only the permission to write remotely.
Q13Will data be lost if the backend is down?
Short outages are absorbed by retries and the queue. Long outages can overflow the queue and lose data. A larger queue, a disk-backed queue and good alerting reduce the risk, but no queue is infinite.

22Summary and Key Takeaways

You started with two systems that speak different languages. You now know how a small, careful translator lets them share one picture of your software.

Key Takeaways

  • Remote Write is a push door, OTLP is a different plug. The OpenTelemetry Collector’s exporter reshapes OTLP data into Prometheus time series and sends it through that door.
  • Each metric type has a clear rule. Gauges and monotonic cumulative sums map cleanly, histograms become bucket, sum and count series, exponential histograms become native histograms, and delta data must be converted first.
  • Names, units and labels are rewritten. Dots become underscores, units and totals become suffixes, and resource information becomes job, instance and a separate target info series.
  • Cardinality is your biggest risk. Promote only a few stable resource attributes and never use unbounded values as labels.
  • Time works differently in push. Use rate windows several times longer than the export interval, keep clocks in sync, expect slow fading instead of instant staleness, and keep one writer per series.
  • Production needs engineering. Use TLS and authentication, a memory limiter and batching, sensible queues, deduplication for replicated scrapers, and alerts on failed exports and backend rejections.
  • Choose the path that fits. The Collector exporter gives the widest compatibility and control, while native OTLP ingestion removes a moving part when your backend supports it. Always test versions together, because this area keeps evolving.