Skip to content

RW 2.0-rc.5: allow exemplar-only time series - #3085

Open
thc1006 wants to merge 3 commits into
prometheus:rw2-spec-rc5from
thc1006:rw2-exemplar-only-timeseries
Open

RW 2.0-rc.5: allow exemplar-only time series#3085
thc1006 wants to merge 3 commits into
prometheus:rw2-spec-rc5from
thc1006:rw2-exemplar-only-timeseries

Conversation

@thc1006

@thc1006 thc1006 commented Aug 26, 2026

Copy link
Copy Markdown

This changes the 2.0 specification so that a TimeSeries carrying only exemplars is described rather than forbidden.

Why the current wording does not cover what a sender produces

Prometheus stores exemplars per series, not per sample. populateV2TimeSeries in storage/remote/queue_manager.go writes one output TimeSeries per queue item, and for an exemplar item it appends only an exemplar, having already reset that entry's samples and histograms. So an exemplar can leave the sender in a TimeSeries of its own.

The specification currently says:

At least one element in samples or in histograms MUST be provided. A TimeSeries MUST NOT include both samples and histograms.

That shape therefore has no wording behind it, and a receiver has nothing to rely on when it arrives.

What this changes

Two normative sentences, plus the proto copy this document carries:

  • the "at least one element" rule accepts exemplars as well, and says what a TimeSeries carrying only exemplars means;
  • the #### Exemplars section says the same thing from the exemplar's side;
  • the copied proto comments say the same as the rules above them, so the two do not disagree inside one document.

No field, field number or wire layout changes, and nothing is asked of a sender that was not asked before.

What the first version also had

It added a SHOULD that exemplars travel in the same request as the samples or histograms of their series. That is a rule about how a sender batches rather than a statement about what the wire format allows, and it is the part of this change review is stuck on, so it is out as of 81c879a.

Co-location is still worth writing down somewhere. QueueManager.Append and AppendExemplars both enqueue on the series ref, and shards.enqueue picks the shard with ref % len(queues), so a series' samples and its exemplars go into the same shard queue in order. They are usually in one request and a full batch is what splits them. That belongs wherever the receiver profile question lands, not in this rule.

What this leaves alone

The receiver tests in prometheus/compliance only cover exemplars sent alongside a sample. Covering a TimeSeries that carries only exemplars is a separate change. The canonical proto comments are in prometheus/prometheus#19530, which carries the source version bump to rc.5 as well.

On versioning

Retitled with the version, the way rc.4 was done in #2762, so the title is the record of what went into the release. #3080 still carries the single bump and #3081 is the other change in this one.

Related to prometheus/prometheus#17857 and prometheus/prometheus#16944.

@bwplotka

bwplotka commented Aug 26, 2026

Copy link
Copy Markdown
Member

This looks what wanted as per https://docs.google.com/document/d/1ZcyKVF87Xf6FhaGkb33LgVLb27FH5NIlRXWEGO2Cc4Q/edit?tab=t.0#bookmark=id.nllt8bbnl0i decision. However we need strict versioning.

I initially planned to pack this and async change under rc.5 for an easy reference -- we don't use git tags so it's a bit challenging to pack two separate changes under same version (:

@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from 06d0a88 to 73870cf Compare August 26, 2026 13:40
@thc1006 thc1006 changed the title docs: allow exemplar-only TimeSeries in PRW 2.0 RW 2.0-rc.5: allow exemplar-only time series Aug 26, 2026
@thc1006
thc1006 marked this pull request as ready for review August 26, 2026 13:40
@thc1006

thc1006 commented Aug 26, 2026

Copy link
Copy Markdown
Author

Retitled with the version, the way rc.4 was done in #2762, so the title carries the record that git tags would otherwise hold. #3080 keeps the single bump and #3081 is the other change in rc.5, so nothing has to be packed together beyond the two titles saying the same version.

Also added the second half of the decision, which I had left out: exemplars SHOULD be sent in the same request as the samples or histograms of their series, including when they travel in a TimeSeries of their own. Without it the wording read as a licence to detach them freely, which is not what the sender does today.

Out of draft now.

@bwplotka
bwplotka changed the base branch from main to rw2-spec-rc5 August 26, 2026 14:07
@bwplotka bwplotka mentioned this pull request Aug 26, 2026
@bwplotka

Copy link
Copy Markdown
Member

I changed base to #3085 so we can squash it all as a rc.5, hope that's ok

@bwplotka

Copy link
Copy Markdown
Member

Ups I think you need to manually rebase, sorry @thc1006 - do you mind doing this?

Comment thread docs/specs/prw/remote_write_spec_2_0.md Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be updated once the proto is updated then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's update it at one go and prep a PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both done: the copy here in this PR, the original in prometheus/prometheus#19530.

@krajorama krajorama left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, except the question about the copied protobuf

Prometheus stores exemplars per series, not per sample. populateV2TimeSeries
writes one output TimeSeries per queue item, and for an exemplar item it
appends only an exemplar, so an exemplar can leave the sender in a TimeSeries
of its own. The specification requires every TimeSeries to carry a sample or a
histogram, so that shape is not covered.

Describe it instead of forbidding it, and keep the expectation that the
exemplars travel in the same request as the series they belong to.

Related to prometheus/prometheus#17857 and prometheus/prometheus#16944.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006

thc1006 commented Aug 26, 2026

Copy link
Copy Markdown
Author

Rebased onto rw2-spec-rc5, so this is one commit against that branch again. No trouble at all, and moving the base was the right call for keeping rc.5 in one place.

@krajorama good catch on the copied protobuf. The comment you pointed at turned out to be one of three saying the same thing: that one says a TimeSeries specifies samples or histograms, the exemplars field says they belong to the series' samples, and the Exemplar message says it too. All three are in this PR now.

The originals live in prompb/io/prometheus/write/v2/types.proto, so those go in prometheus/prometheus#19530. That one does not depend on this PR: populateV2TimeSeries clears Samples and Histograms and appends only an exemplar when the queue item is a tExemplar, so the comment already disagrees with the sender sitting next to it.

One thing I would rather ask than guess. In prometheus/prometheus#17857 back in January you proposed two halves, "Add SHOULD for per sample, but allow per series by spec". This PR has the second one. The SHOULD I added after your first comment here is about sending exemplars in the same request, not about preferring per sample association. Should the per sample SHOULD go in as well, or does the August decision replace it? Your 26 August comment on that issue reads as moving away from the per sample model for now, which is why I left it out.

Last thing, unrelated to the diff: prometheus/prometheus#18014 has been open since February taking the other route, dropping exemplars that arrive without a matching sample. @AftAb-25 wrote it against the spec as it read then. If this lands, they deserve to hear that the direction moved. The bot has already marked it stale.

thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 26, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from f5268d0 to 6a5c22f Compare August 26, 2026 15:21
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 26, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from 6a5c22f to 35ee174 Compare August 26, 2026 17:15
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 26, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 requested a review from krajorama August 27, 2026 11:01
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from 35ee174 to 487086d Compare August 27, 2026 11:03
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 27, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
thc1006 added a commit to thc1006/prometheus that referenced this pull request Aug 28, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from 487086d to 46b865c Compare August 28, 2026 01:33

@bwplotka bwplotka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General thought when I look a the changes: It feels this change is a step back when it comes to compatibility with systems oriented around OpenTelemetry model. As-in, you can use PRW2 to have a reliable transport against those, but it requires being strict on various elements beyond what protocol requires from sender e.g.:

  • Avoid unknown metadata.
  • Use NHCB instead of classic histograms.

Now with this change it also technically needs:

  • Exemplar per sample (or at least within same request).

In the same time this relaxation of rules for exemplars allows (slightly) simpler protocol for existing Prometheus systems, and it's unlikely the exemplar part would change for those given the current APIs.

I wonder how to tackle this redundancy of implementing PRW2 around those intentions to battle ossification/proliferation and being too lean on sending so "I implement PRW2 sending" is not ambiguous.

Perhaps what we need is to introduce some "mode" or consider OTLP export again given advances we have in the storage.

Comment thread docs/specs/prw/remote_write_spec_2_0.md Outdated
-->
* MAY contain labels e.g. referencing trace or request ID. If the exemplar references a trace it SHOULD use the `trace_id` label name, as a best practice.
* MUST contain a timestamp. While exemplar timestamps are optional in Prometheus/Open Metrics exposition formats, the assumption is that a timestamp is assigned at scrape time in the same way a timestamp is assigned to the scrape sample. Receivers require exemplar timestamps to reliably handle (e.g. deduplicate) incoming exemplars.
* MAY be sent in a `TimeSeries` that carries neither samples nor histograms. In that form the exemplars are associated with the series identified by `labels_refs`, rather than with a particular sample or histogram.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to think about other elements like metadata. I don't think we want to allow them hanging without samples.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The at-least-one rule above names samples, histograms and exemplars, so metadata alone does not satisfy it and a metadata-only TimeSeries stays invalid. If this line reads as an opening for the other fields, I can say so where the rule is rather than leaving it to be inferred.

The copy of io.prometheus.write.v2 in this document still says a TimeSeries
specifies samples or histograms, and that exemplars belong to the series'
samples. Neither holds once a TimeSeries can carry exemplars on its own.

krajorama spotted this in review. The same three comments live in
prompb/io/prometheus/write/v2/types.proto, which is the source of truth, so
they are changed there too.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006
thc1006 force-pushed the rw2-exemplar-only-timeseries branch from 46b865c to 8f9d1fa Compare September 4, 2026 14:42
thc1006 added a commit to thc1006/prometheus that referenced this pull request Sep 4, 2026
populateV2TimeSeries builds one TimeSeries per queue item, so a tExemplar item
produces a message holding labels and one exemplar and nothing else. The
comments on TimeSeries say that a message specifies samples or histograms, and
that its exemplars belong to the series' samples, so they do not describe what
the sender does.

The specification is being changed to match in prometheus/docs#3085. krajorama
asked there whether the copy of this file inside the specification document
would follow the real one, so the two change together.

types.pb.go carries the same three comments and is updated with them. The
embedded file descriptors are left alone because they do not encode comments,
which two generator runs, one with the change and one without, confirmed.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006

thc1006 commented Sep 4, 2026

Copy link
Copy Markdown
Author

One piece of data first, since it may change the shape of the question.

Prometheus already sends metadata in a message with no samples. AppendExemplars reads seriesMetadata when it enqueues, and populateV2TimeSeries fills Metadata before it looks at what kind of item it has, so an exemplar-only TimeSeries goes out carrying type, unit and help. That is today's traffic rather than something this change introduces.

The same holds for the OTel side. A receiver reading real Prometheus traffic already had to decide what to do with exemplar-only messages, and the specification told it they were invalid. This does not hand it a new case, it names one it already had.

What the change does add is the SHOULD about the same request, and that one is mine to defend or drop. It is there so a receiver has a bounded window to associate within. If the cost is another implicit requirement on senders, dropping it leaves the MAY describing the wire format and nothing more.

On modes and on OTLP export I do not have a view worth your time. What would help me is knowing whether you want this narrowed to the wire-format statement, held until that larger question settles, or something else.

@thc1006
thc1006 requested a review from bwplotka September 4, 2026 15:23
@bwplotka

bwplotka commented Sep 4, 2026

Copy link
Copy Markdown
Member

Prometheus already sends metadata in a message with no samples. AppendExemplars reads seriesMetadata when it enqueues, and populateV2TimeSeries fills Metadata before it looks at what kind of item it has, so an exemplar-only TimeSeries goes out carrying type, unit and help. That is today's traffic rather than something this change introduces.

That is true, and it's the tech debt due to abandoning the idea of using prometheus/prometheus#15911 - if we want to bring this back, we need to fix it.
The path using __type__ and __unit__ feature leverages metadata per samples correctly, so we do have path to use it.

I understood it wrongly -- for exemplars sure, but why exemplars even have metadata then? 🙈

@thc1006

thc1006 commented Sep 4, 2026

Copy link
Copy Markdown
Author

Thanks, that framing helps, and the __type__/__unit__ pointer is one I had not followed.

Correcting something in what I wrote above: I leaned on enableTypeAndUnitLabels as though it described how things run, but it sits behind --enable-feature=type-and-unit-labels, so it is the path you are pointing at rather than the current state. The argument I built on it does not carry the weight I gave it.

What is left is narrower. Exemplars are enqueued separately from their samples whichever way metadata goes, so that part does not resolve with it.

Happy to drop the SHOULD and leave this describing only the wire format, or to park it until the metadata direction settles. Your call.

What this PR is about is that an exemplar-only TimeSeries is valid and that it
identifies its series by labels_refs. Whether a sender keeps a series'
exemplars in the same request as its samples is a different question. It is
about interoperability rather than about what the wire format allows, and it
is the part of this change that is under discussion in review.

Taking it out leaves the wire format statement on its own. Co-location is
worth writing down, just not in this rule.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@thc1006

thc1006 commented Sep 11, 2026

Copy link
Copy Markdown
Author

I took the same-request SHOULD out in 81c879a. What is left is only that an exemplar-only TimeSeries is valid and that it identifies its series by labels_refs.

@bwplotka that was the same-request half of the third item on your list, and the only one of the three this PR was writing into the spec. Whether a sender keeps a series' exemplars with its samples is about interoperability, not about what the wire format allows, so it belongs with the mode or profile question rather than as a SHOULD in the base spec.

@krajorama flagging this because of prometheus/prometheus#17857 (comment). I read it as asking to soften the per-sample MUST so the mode in that plan would not be a violation, rather than as asking for a new rule about batching. This PR makes the shape valid instead, so there is nothing left to violate. If I have read you wrong, say so and I will put the line back.

Co-location is still worth writing down somewhere. Append and AppendExemplars both enqueue on the series ref, and shards.enqueue picks the shard with ref % len(queues), so a series' samples and its exemplars go into the same shard queue in order. They are usually in one request, and a full batch is what splits them. I can open an issue for that.

Does this settle the protocol format part of your review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants