Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Prometheus & Monitoring By James Joyner IV · · 11 min read

Native Histograms vs Classic Buckets: Getting Quantiles You Can Trust

Prometheus native histograms promise better percentiles than fixed buckets. Here's how their accuracy actually differs, and how to query them without carrying classic-histogram habits.

  • #prometheus-monitoring
  • #ai
  • #native-histograms
  • #promql
  • #percentiles

For years, the accuracy of a Prometheus p99 was decided at instrumentation time. You chose your histogram buckets up front, and if a quantile happened to land in a coarse bucket, histogram_quantile interpolated across the gap and handed you a confident guess. Native histograms change that bargain. They carry an exponentially-spaced, dynamically-resolved distribution in a single sample, so the precision adapts to the data instead of being frozen into a bucket layout you picked before you knew the traffic. That is the promise. The reality is more nuanced, and most of the pain comes from carrying classic-histogram habits into a model where they no longer apply.

How the two actually differ

A classic histogram is a fan-out of series: request_duration_seconds_bucket{le="0.1"}, le="0.25", le="+Inf", plus _count and _sum. You sum the bucket rates and feed them to histogram_quantile, which linear-interpolates within whichever bucket the quantile falls into. The accuracy ceiling is the bucket layout. If your p99 lives between le="1" and le="+Inf", the function interpolates across that entire range, which can be wildly off.

A native histogram is one complex sample. Its buckets are defined by a schema (a resolution factor) and grow or shrink automatically, so a busy latency range gets fine buckets without you predeclaring them. You query the same histogram_quantile, but it consumes the histogram sample directly:

# Classic: sum the bucket series first
histogram_quantile(0.95, sum by (le) (rate(request_duration_seconds_bucket[5m])))

# Native: the sample IS the histogram, no _bucket, no le
histogram_quantile(0.95, rate(request_duration_seconds[5m]))

The native version is shorter, and that brevity is exactly where people trip. There is no le label, no _bucket suffix, and no sum by (le). If you write classic syntax against a native histogram you get an empty result, not an error.

The accuracy story is real, with caveats

Native histograms genuinely give you better tail accuracy because resolution follows the data. But two things are worth being honest about. First, resolution is bounded by the schema and by histogram_quantile’s own interpolation between native buckets — it is far finer than classic buckets, but it is still interpolation, not a recorded exact value. Second, the win only materializes if the whole pipeline is native end to end. Mixing a classic exposition and a native one for the same metric name corrupts results, and many backends and exporters are still catching up.

For averages and fractions, native histograms add functions that make intent explicit:

# Average latency over the window
histogram_sum(rate(request_duration_seconds[5m]))
  /
histogram_count(rate(request_duration_seconds[5m]))

# Fraction of requests under 300ms
histogram_fraction(0, 0.3, rate(request_duration_seconds[5m]))

Note the rate() wrapper. For counter-style histograms it is mandatory before any of these functions, or you compute over the cumulative distribution since process start — a number that looks plausible and drifts as the process ages.

Where AI helps, and where you still verify

When a native-histogram quantile looks wrong, the fastest triage is to have an assistant classify the metric and map your goal to the right function before you start second-guessing the data. A focused prompt keeps it grounded:

Here is a metric sample and a query whose p99 looks too low. Confirm whether this is a native or classic histogram, identify whether I forgot the rate() wrapper, and give me a corrected query plus one cross-check that proves the answer is sane.

A useful answer looks like this:

This is a native histogram (no _bucket series, the raw sample is a histogram). Your query omits rate(), so histogram_quantile is reading the cumulative distribution since startup — that is why the p99 looks artificially stable. Corrected: histogram_quantile(0.99, rate(request_duration_seconds[5m])). Cross-check: histogram_count(rate(request_duration_seconds[5m])) should track your request rate; if it doesn’t, the metric isn’t what you think.

That cross-check matters because native-histogram bugs are silent. The model drafts the query and the reasoning; you run the verification and confirm histogram_count tracks request rate before you trust the percentile. This is the same AI-drafts, human-verifies discipline that the PromQL percentile prompts are built around.

A migration checklist

Before you switch a metric to native histograms:

  • Confirm the client library and your Prometheus build support native histograms and that the feature flag is enabled where required.
  • Make sure nothing scrapes the same metric name as a classic histogram — mixing the two corrupts queries.
  • Rewrite every dashboard and alert query that used sum by (le) (rate(..._bucket)) to consume the native sample directly.
  • Re-validate alert thresholds. A more accurate p99 may now fire where the coarse classic estimate quietly under-reported, which is correct but will surprise on-call if nobody warned them.

That last point is the one teams forget. Better accuracy is not free of consequences: a tail that was always real but hidden by coarse buckets becomes visible, and your alerts start telling the truth. That is the goal, but treat it as a deliberate change with a heads-up to whoever carries the pager.

The bottom line

Native histograms move accuracy from a guess you make at instrumentation time to a property that adapts to your traffic, and they are worth adopting for any latency or size metric where the tail matters. The catch is that the query model is different enough that classic habits silently fail rather than error. Learn the function-to-context mapping, always wrap counters in rate(), keep the pipeline native end to end, and verify with an independent cross-check. If you want a starting point for the queries themselves, the native histogram debugging prompt and the rest of the Prometheus and monitoring prompt library will get you most of the way there — but run the cross-check before you quote the number in an SLO review.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.