Repository navigation
Expose Prometheus histograms in both classic and native form #3817
Description
Summary
Our Prometheus histograms are almost all classic histograms, most of them with prometheus.DefBuckets, and two services still use Summaries.
Prometheus native histograms have been stable since Prometheus v3.8. They give much better resolution with far fewer time series, and need no bucket tuning.
Because self-hosters scrape OpenCloud with whatever Prometheus setup they already run, we should not switch over hard.
Instead, expose both the classic and the native form of every histogram (dual exposition), which client_golang supports out of the box.
Nothing changes for setups that don't have native histograms enabled: they keep ingesting the classic histograms. Setups that have native histograms enabled (scrape_native_histograms: true) ingest the native form instead, and stop getting the classic _bucket, _sum and _count series unless they also set always_scrape_classic_histograms: true, so their dashboards and alerts need to be migrated or that setting enabled.
Low priority: nothing is broken.
Current state
| Service | Metric(s) | Kind |
|---|---|---|
| graph | http_request_duration_seconds, ldap_client_operation_duration_seconds, identity_backend_api_duration_seconds |
classic, DefBuckets |
| postprocessing | duration_seconds |
classic, custom buckets 0.1 … 1200 |
| search | search_duration_seconds, index_duration_seconds |
classic, custom buckets |
| proxy | duration_seconds, service_duration_seconds (legacy) |
classic |
| invitations, webfinger | *_duration_seconds |
classic |
| invitations, webfinger | *_latency_microseconds |
Summary |
Notes:
DefBucketsstops at 10s. Anything slower, for example a slow LDAP call or backend request, lands in+Inf.- Summaries compute quantiles on each instance, and those quantiles can't be combined across replicas.
Native vs. classic histograms
Native histograms
Pros:
- No bucket tuning: sparse exponential buckets are created only where values actually land. A single growth factor (
NativeHistogramBucketFactor: 1.1, about 10% relative error) replaces hand-picked boundaries. - Much better resolution for about the same storage.
histogram_quantileis much more accurate than interpolating between around 11 coarse buckets. - Far fewer time series: 1 series per label set instead of one per bucket plus
_sumand_count(14 withDefBuckets). That matters forHistogramVecs with several labels (method × service × result, …). - Can be combined across instances, even when they use different resolutions.
- Better PromQL:
histogram_quantile(0.99, rate(x[5m]))withoutle/sum by (le), plushistogram_count,histogram_sum,histogram_avg,histogram_fractionandhistogram_stddev. - Maps onto OpenTelemetry exponential histograms, should we move to OTel instrumentation later.
Cons:
- The server and everything downstream must support them. They are only transferred over the protobuf scrape format and must be enabled explicitly (see the next section). Remote-write targets and long-term storage (Thanos, Mimir, Cortex, VictoriaMetrics, …) also need to support them, and support there varies by product and version.
- Dashboards and alerts break on a hard switch.
_bucket{le=…}series disappear, so existingrate(x_bucket[…])queries, recording rules and Grafana panels stop working. - No exact thresholds: bucket boundaries are fixed by the schema, so an SLO like "share of requests under 300ms" is interpolated by
histogram_fractioninstead of counted exactly. - Slightly more memory on the client while values are spread widely. This can be capped with
NativeHistogramMaxBucketNumberandNativeHistogramMinResetDuration. - Less familiar: fewer ready-made dashboards and less community knowledge.
Classic histograms
Pros: supported everywhere (any Prometheus version, any remote-write target, any dashboard), exact bucket boundaries for SLOs, simple and well-understood.
Cons: buckets must be chosen in advance and are often wrong; quantiles are coarse; every bucket multiplies cardinality; changing buckets breaks continuity.
Prometheus server support
- v2.40.0 (2022-11-08): native histograms introduced as an experimental feature, enabled with
--enable-feature=native-histograms. The flag also makes protobuf the preferred scrape format. - v3.0.0 (2024-11-14): the scrape setting
scrape_classic_histogramswas renamed toalways_scrape_classic_histograms. - v3.8.0 (2025-11-28): native histograms are stable, but optional. They are controlled by the new
scrape_native_histogramssetting (global or per scrape config, defaultfalse). Thenative-histogramsfeature flag is being phased out; in v3.8 it only sets the default ofscrape_native_histogramstotrue. - v3.9.0 (2026-01-06): the
native-histogramsfeature flag is a no-op.scrape_native_histogramsmust be set totrueexplicitly. The default staysfalse, and there is no announced plan to change it. - v3.13.0 (2026-07-01): per-target override through the
__scrape_native_histograms__and__always_scrape_classic_histograms__relabeling labels.
So no Prometheus version enables native histograms by default. Admins have to opt in, either with --enable-feature=native-histograms (v2.40 to v3.8) or with scrape_native_histograms: true (v3.8 and later; required from v3.9 on). With scrape_native_histograms: true, the default scrape_protocols puts PrometheusProto first.
For a histogram exposed in both forms, Prometheus behaves like this:
scrape_native_histograms: false: the native parts are ignored and the classic histogram is stored, exactly as today.scrape_native_histograms: true: the native histogram is stored. The classic series are only kept as well ifalways_scrape_classic_histograms: true, which is useful while migrating dashboards.
References:
- Prometheus CHANGELOG (entries for 2.40.0, 3.0.0, 3.8.0, 3.9.0, 3.13.0)
scrape_native_histogramsconfiguration- Feature flag docs for v3.8.0
Dual exposition:
- Native histograms spec, Instrumentation libraries: libraries should be able to expose a classic and a native version of the same histogram in parallel
- Native histograms spec, Scraping both classic and native histograms: how Prometheus ingests a histogram exposed in both forms (
scrape_native_histograms,always_scrape_classic_histograms) - Native histograms spec, Migration considerations: the recommended migration path, starting with exposing both forms in the instrumentation
- client_golang
HistogramOpts.Buckets: to expose both forms,Bucketsmust be set explicitly, since it defaults to no classic buckets once native buckets are used
Proposal
-
Dual exposition for all histograms: keep
Bucketsand add the native settings to the sameHistogramOpts:prometheus.HistogramOpts{ // ... Buckets: prometheus.DefBuckets, // classic, unchanged NativeHistogramBucketFactor: 1.1, NativeHistogramMaxBucketNumber: 100, NativeHistogramMinResetDuration: time.Hour, }
Ideally do this through a small helper in
pkg/metricsthat applies these defaults, so all services stay consistent. -
Review the classic bucket boundaries.
DefBucketsdoesn't fit LDAP and Graph backend latencies well. -
Deprecate and later remove the Summaries (
*_latency_microsecondsin invitations and webfinger), since the*_duration_secondshistograms already exist next to them. Announce it, because people may have dashboards on those names. -
Document it for admins: how to enable
scrape_native_histograms(plusalways_scrape_classic_histogramswhile migrating), and example PromQL for both forms.
Costs and risks: slightly higher client-side memory per histogram. No change for scrapers that use the text format or don't enable native histograms.
Cardinality trade-off: dual exposition only saves time series for setups that ingest the native form (scrape_native_histograms: true without always_scrape_classic_histograms). Setups without native histograms keep paying one series per bucket for every label set, as they do today. For histogram vectors with several labels, adding classic buckets can therefore be expensive. In those cases an alternative is a native-only histogram with all labels, plus a separate classic histogram with fewer labels as a fallback for setups without native histograms. Which approach fits should be decided per metric.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsQualification
Activity