max_over_time function for exactly that:
rate(...) returns an instant vector while max_over_time expects a range vector. Also remember: in rate(metric[10m]), the 10m is the sample range for the rate() calculation (how Prometheus computes the rate), not how far back to query data for aggregation.
This is where subqueries solve the problem.
Use subqueries when you need to take an instant-expression (for example,
rate(...)) and evaluate that expression across a historical time window at a given resolution so that range-based aggregation functions (like max_over_time, min_over_time, etc.) can operate on a range vector.Subquery syntax
A Prometheus subquery wraps an instant query and appends a bracketed range and step (resolution). Optionally, you can add anoffset.
<instant_query>: any instant-vector expression (e.g.,rate(...),irate(...), or any metric selector).<range>: how far back to collect data for the subquery (the time window).<resolution>: step between samples returned by the subquery (the query step).
http_requests_total over the last 5 minutes, where rate() uses a 1 minute sample range and the subquery samples every 30 seconds:
- The inner
[1m]tellsrate()how to group samples for each instant. - The outer
[5m:30s]tells Prometheus to evaluate the instant expression for the previous 5 minutes at 30-second intervals, returning a range vector thatmax_over_timecan consume.
Quick comparison: instant vs range vectors
Examples
- Trying to nest
rate(...)directly insidemax_over_time(...)(invalid):
- Correct approach using a subquery:
- Visualizing how a subquery returns samples:
- The
1minsiderate()is the sample range used byratefor each evaluated instant. - The outer
[2m:10s]asks Prometheus to evaluate thatrate(...)expression across the past 2 minutes at 10-second intervals, producing a range vector.
Be careful with very small
resolution values (the subquery step). High-resolution subqueries can be expensive and may increase query latency and Prometheus load. Choose a resolution appropriate for the precision you need.Practical example: network transmit bytes
Let’s use a real metric:node_network_transmit_bytes_total (total transmitted bytes on a network interface). To get the current transmission rate:

max_over_time receives a range vector:
- Inner
[1m]is therate()sample range (howratecomputes an instant rate). - Outer
[5h:30s]requests the last 5 hours of therate(...)values sampled every 30 seconds. max_over_time(...)then returns the maximum of those sampled rate values.
max_over_time and just run the subquery:
When to use subqueries
- Converting an instant-vector expression into a range vector for range aggregations (e.g.,
max_over_time,avg_over_time). - Evaluating rate-like expressions historically at a chosen resolution without using recording rules.
- Performing windowed calculations over computed instant values.