Prometheus compatibility
PromQL in Elasticsearch reads metrics stored in time series data streams (TSDS).
Most PromQL queries run unchanged and return the same results as Prometheus 3.x.
This page lists the PromQL features Elasticsearch doesn't support yet and where its behavior differs by design, so you can tell in advance whether a query runs and what it returns.
It applies to both the Prometheus-compatible HTTP API and the ES|QL PROMQL source command, unless a section says otherwise.
The following sections follow the Prometheus querying documentation, so each difference sits under the heading that documents the construct in Prometheus. Each section has up to three parts:
- Supported: what runs and returns the same result as in Prometheus.
- Not supported yet: Elasticsearch rejects the query with an error. Prometheus supports every construct in these tables, some only with a feature flag.
- Behaves differently: the query runs in both, but the result differs.
Rows link the issue that tracks the work, where there is one. A row with a version badge applies only to those versions. A row without one applies to every version.
Sections follow Querying basics in the Prometheus documentation.
In the examples on this page, h is a native histogram metric.
Supported
exponential_histogram fields.
histogram_quantile, histogram_count, histogram_sum, histogram_avg, increase, and sum support them, such as histogram_quantile(0.9, sum by (job) (increase(h[5m]))).
histogram_fraction and changes support them too.
Classic histograms, stored as _bucket series with an le label, such as histogram_quantile(0.9, sum by (le) (rate(x_bucket[5m]))).
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 1 | Native histogram as the result
|
h, increase(h[5m]) |
Wrap it in a histogram function, such as histogram_count |
#152263 |
Supported
Range vectors as function arguments, such as rate(x[5m]).
In the PROMQL command, the range can be left out, such as rate(x).
Refer to Implicit range selectors.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 2 | Range vector as the result of an instant query | x[5m] |
- | #164220 |
Supported
String literals in single quotes, double quotes, or backticks.
Float literals, including scientific notation, hexadecimal, Inf, and NaN, such as 1e3 and 0x1f.
Time durations, such as 1h30m, and duration arithmetic, such as x[5m + 1m].
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 3 | step(), range(), min_of(), and max_of() in a duration (Prometheus: promql-duration-expr feature flag) |
x[step()], x[min_of(5m, step())] |
Use a fixed duration | #164221 |
Supported
A metric name with the label matchers =, !=, =~, and !~, such as http_requests_total{job="api", code=~"5.."}.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 4 | Selector without a metric name | {job="api"} |
- | #146977 |
| 5 | Regex matcher on __name__ |
{__name__=~"node_.*"} |
Combine the metrics with or: node_cpu or node_mem |
#146977 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 6 | Negative matcher on __name__ |
{__name__!="up", job="node"} |
No results | Every other matching metric | #146977 |
Supported
A range selector with a duration, such as x[5m] or x[1h30m].
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 7 | anchored and smoothed modifiers (Prometheus: promql-extended-range-selectors feature flag) |
increase(x[5m] anchored) |
- | #164222 |
Supported
offset on instant and range selectors, such as x offset 5m and rate(x[5m] offset 1h).
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 8 | Negative offset at the end of the query range | x offset -5m |
No value at the last steps. Samples after the query end aren't read | Reads samples after the query end | - |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 9 | @ modifier |
x @ 1700000000, x @ end() |
- | #164139 |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 10 | Subquery | max_over_time(rate(x[5m])[1h:1m]) |
- | #164139 |
Supported
A selector without a range, such as http_requests_total, returns the latest sample of each series within the lookback window at each evaluation time.
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 11 | Lookback in instant queries | /api/v1/query |
Five minutes, fixed | Five minutes by default, configurable with lookback_delta |
#149939 |
| 12 | Lookback in range queries | /api/v1/query_range |
One step. A series disappears at the first step without its own sample |
Five minutes. The last sample repeats until then | - |
| 13 | Staleness markers | - | Ignored. A series disappears once its samples leave the lookback window | A series disappears at its staleness marker | - |
Sections follow Operators in the Prometheus documentation. Each operator reference entry lists further differences.
Supported
+, -, *, /, %, and ^ between scalars, between an instant vector and a scalar, such as rate(x[5m]) * 60, and between two instant vectors.
Vector matching lists which pairs of instant vectors match.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 14 | Native histogram operands
|
h * 2, h1 + h2 |
- | #152263 |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 15 | atan2 |
x atan2 y |
- | #164153 |
Supported
==, !=, >, <, >=, and <= between an instant vector and a number, such as rate(x[5m]) > 100, with or without bool, such as x > bool 100.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 16 | Filter with a scalar as the first operand | 100 < x, time() <= x |
Swap the operands: x > 100 |
#158184 |
| 17 | Non-literal scalar as the second operand | x > time(), x > scalar(y) |
- | #158184 |
| 18 | Two vectors, or bool with a scalar as the first operand |
x > y, 100 > bool x |
For bool, swap the operands: x < bool 100 |
#158184 |
| 19 | Comparison inside another expression | (x > 100) + 1, count(x > 100) |
- | #158184 |
Supported
or between instant vectors, such as rate(a[5m]) or rate(b[5m]).
An or chain takes at most 8 operands by default.
esql.query.max_branch_count_per_merge cluster setting.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 20 | and, unless |
a and b, a unless b |
- | #158184 |
| 21 | or with on(...) or ignoring(...) |
a or on(job) b |
- | #158184 |
| 22 | or inside another expression |
(sum(a) or sum(b)) * 2, count(a or b) |
- | #158184 |
Supported
Arithmetic between unaggregated metrics stored together in the same time series, such as network.bytes_in / network.connections, without explicit vector matching modifiers.
Arithmetic between operands aggregated without grouping, such as sum(a) / sum(b).
by(...), including on, ignoring, group_left, and group_right.
For example: sum by (instance) (a) / on(instance) sum by (instance) (b).
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 23 | Different unaggregated metrics stored as separate series with a __name__ dimension, such as data ingested with Prometheus remote write
|
a / b |
Aggregate both by the labels to match on: sum by (instance) (a) / sum by (instance) (b) |
#158184 |
| 24 | on, ignoring, group_left, or group_right with an operand that isn't aggregated |
a / on(instance) b, a * on(pod) group_left(node) b |
Aggregate both operands by the labels to match on | #157669 |
| 25 | Operands aggregated differently
|
sum(a) / b, b / scalar(sum(a)), topk(2, a) * b |
Aggregate both operands alike: sum(a) / sum(b). A number, such as sum(a) * 2, and or are supported |
#158184 |
| 26 | Nested aggregation next to an operand that reads a metric
|
count(count by (pod) (a)) / sum(b) |
- | #158184 |
| 27 | A different offset per operand
|
rate(x[5m]) / rate(x[5m] offset 1h) |
or is supported |
#158184 |
| 28 | label_replace or label_join as an operand |
label_replace(a, ...) * on(pod) group_left(node) b |
- | #157672 |
| 29 | fill(), fill_left(), and fill_right() (Prometheus: promql-binop-fill-modifiers feature flag) |
a + fill(0) b |
- | #164223 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 30 | Different unaggregated metrics stored as separate series
|
a / b |
No results | Series matched on their other labels | #158184 |
Supported
sum,avg,min,max,count,quantile,stddev, andstdvar, withby(...),without(...), or no grouping.-
topk,bottomk,limitk, andlimit_ratio.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 31 | group, count_values |
group by (job) (x), count_values("v", x) |
- | #164009, #136248 |
| 32 | topk, bottomk, limitk, or limit_ratio with without(...) |
topk without (instance) (1, x) |
Use by(...) |
#157671 |
| 33 | Parameter that isn't a number literal | topk(scalar(a), b), quantile(scalar(a), b) |
Use a number | #164225 |
| 34 | Nested without(...) |
sum without (pod) (max without (container) (x)) |
Use by(...) |
#157671 |
| 35 | without(...) as an operand |
sum without (method) (x) * 2 |
Use by(...) |
#145308 |
| 36 | Grouping by a label named step |
sum by (step) (x) |
step is an output column of the PROMQL command |
- |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 37 | quantile |
quantile(0.9, x) |
Approximate, using t-digest | Exact, with linear interpolation | - |
| 38 | limitk, limit_ratio
|
limitk(10, x) |
A different, equally valid subset | A deterministic pseudo-random subset, with the promql-experimental-functions feature flag |
- |
Rows follow Functions in the Prometheus documentation and are sorted by function name. Each function reference entry lists further differences.
Supported
The functions in the PromQL functions reference, such as rate, increase, histogram_quantile, label_replace, and the <aggregation>_over_time functions.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 39 | Functions listed under Not supported yet | absent(x), resets(x[1h]), info(x) |
- | #164001, #164003, #164002 |
| 40 | Parameter that isn't a number literal | clamp_min(a, scalar(b)), histogram_quantile(scalar(a), b) |
Use a number | #164225 |
| 41 | histogram_quantile or histogram_fraction over topk, bottomk, limitk, or limit_ratio
|
histogram_quantile(0.9, topk(5, a)) |
Aggregate with by(...) first: histogram_quantile(0.9, topk(5, sum by (job, le) (a))) |
#164226 |
| 42 | histogram_quantile or histogram_fraction over without(...)
|
histogram_quantile(0.9, sum without (instance) (rate(x[5m]))) |
Use by(...) |
#157671 |
| 43 | histogram_stddev, histogram_stdvar
|
histogram_stddev(h) |
- | #164152 |
| 44 | irate, delta on native histograms
|
irate(h[5m]) |
- | #152263 |
| 45 | label_replace or label_join outside by(...) |
label_replace(x, "dst", "$1", "instance", "(.*):.*") |
by(...): sum by (dst) (label_replace(...)) |
#157672 |
| 46 | label_replace or label_join over an aggregation
|
label_replace(sum by (job) (x), ...) |
- | #157672 |
| 47 | label_replace or label_join over anything other than a by(...) aggregation, or that overwrites a grouping label
|
label_replace(sum without (instance) (x), "dst", "$1", "job", "(.*)"), label_replace(sum by (job) (x), "job", "$1", "job", "(.*)") |
Aggregate with by(...) and write a new label: label_replace(sum by (job) (x), "dst", "$1", "job", "(.*)") |
#157672 |
| 48 | label_replace or label_join that writes a label named step
|
label_replace(x, "step", ...) |
step is an output column of the PROMQL command |
- |
| 49 | predict_linear with a horizon computed by a function
|
predict_linear(x[1h], time()), predict_linear(x[1h], scalar(y)) |
Use a number, a duration literal, or arithmetic on literals | #164225 |
| 50 | rate on native histograms
|
rate(h[5m]) |
Use increase: histogram_quantile(0.99, sum by (job) (increase(h[5m]))) |
#152263 |
| 51 | sort or sort_desc below the top level |
sum(sort(x)) |
Remove it. Sorting has no effect there | #164227 |
| 52 | step(), range(), start(), end() (Prometheus: promql-experimental-functions feature flag) |
time() - start() |
- | #164228 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 53 | absent_over_time with no data or over a gap |
absent_over_time(x{job="none"}[5m]) |
No results | 1, with the labels of the selector's equality matchers |
- |
| 54 | histogram_count, histogram_sum, or histogram_avg on classic buckets |
histogram_count(rate(x_bucket[5m])) |
An error. They accept only native histograms | No results | - |
| 55 | quantile_over_time |
quantile_over_time(0.9, x[5m]) |
Approximate, using t-digest | Exact, with linear interpolation | - |
| 56 | rate, increase |
rate(x[5m]) |
Computed over fixed time buckets, interpolated at bucket boundaries. Results can differ slightly | Extrapolated within each range window | - |
Sections follow HTTP API in the Prometheus documentation. They apply only to the Prometheus-compatible HTTP API.
Supported
GET requests, and responses in the Prometheus JSON format.
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 57 | Form-encoded POST |
Content-Type: application/x-www-form-urlencoded |
406 Not Acceptable.xpack.security.http.ssl.enabled is true, and the request is authenticated. TLS must terminate at Elasticsearch. Otherwise, use GET |
Accepted | - |
| 58 | warnings and infos |
quantile(2, x) |
Not returned. The only warning is results truncated due to limit |
Returned | - |
Supported
Instant queries and range queries.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 59 | Query parameters that the PromQL HTTP API doesn't list | lookback_delta=1m, stats=all |
Configure clients to omit them. Elasticsearch returns 400 Bad Request |
#149939 |
Supported
/api/v1/metadata.
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 60 | help in /api/v1/metadata
|
GET /api/v1/metadata |
Empty | The metric's HELP text |
#157230 |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 61 | Exemplar queries | GET /api/v1/query_exemplars |
In Grafana, remove the links under Data sources → Elasticsearch → Exemplars | #154786 |