# Why `irate from Prometheus doesn't capture spikes`

Prometheus query language (PromQL) has two similar functions for calculating per-second rate over counters such as `requests_total`

or `bytes_total`

— rate and irate. There is a myth about `irate`

function — it captures per-second rate spikes on the given `[range]`

, while `rate`

averages these spikes.

# Spikes and irate

Look at the following picture for hypothetical `requests_total`

counter:

`v 20 50 100 200 201 230 `

----x-+----x------x-------x-------x--+-----x-----

t 10 | 20 30 40 50 | 60

| <-- range=40s --> |

^

t

It contains values `[20,50,100,200,201,230]`

with timestamps `[10,20,30,40,50,60]`

. Let’s calculate `irate(requests_total[40s])`

at the point `t`

. It is calculated as `dv/dt`

for the last two points before `t`

according to the documentation:

`(201–200) / (50–40) = 0.1 rps`

The `40s`

range ending at `t`

contains other per-second rates:

`(100–50) / (30–20) = 5 rps`

`(200–100) / (40–30) = 10 rps`

These rates are much larger than the captured rate at `t`

. `irate`

captures only 0.1 rps while skipping 5 and 10 rps. Obviously `irate`

doesn’t capture spikes. Irate documentation says:

`irate`

should only be used when graphing volatile, fast-moving counters.

It is expected to capture spikes for volatile, fast-moving counters. But `irate`

returns a sample of per-second rates for such counters. The returned sample may contain all the spikes, a part of spikes or it may miss all the spikes and capture random rates. This highly depends on the following query_range API args: `start`

and `end`

values (i.e. graph time range) and the `step`

value (i.e. graph resolution and zoom level). This means that the graph built with `irate`

tends to jump in arbitrary directions during zooming and scrolling. This is especially visible on big `step`

values covering multiple time series points (aka multiple scrape intervals).

The following graphs are captured for the same query — `irate(requests_total[25m])`

on the same time range. The only difference is modified `step`

in Grafana from `20m`

to `21m`

.

As you can see, these graphs look completely different and they definitely don’t catch spikes.

Let’s add green `rate`

line to these graphs:

Green `rate`

line is much more consistent on these graphs comparing to yellow `irate`

line for the same counter.

# Capturing spikes

Previous graphs revealed that both `irate`

and `rate`

don’t capture peaks on rapidly changing counters. Are there approaches for capturing spikes with PromQL? Probably, recently added subqueries could be used somehow, but I couldn’t figure out how to do it reliably.

If you still want capturing spikes on volatile counters, then set up VictoriaMetrics as a remote storage for Prometheus and then query VictoriaMetrics with rollup_rate() function from MetricsQL. This function returns `min`

, `avg`

and `max`

values for per-second rate. The rate is calculated for each adjacent points, so spikes are reliably captured in `min`

and `max`

values, while `avg`

value is usually close to `rate`

value, though it is calculated differently.

The following graph contains `min`

and `avg`

values for `rollup_rate`

:

`rollup=”min”`

is red, while `rollup=”avg”`

is blue. Yellow line is for `irate`

. As you can see, red line reliably captures all the minimum rates, while yellow line only sometimes reaches the the actual minimum rates.

Now let’s look at the graph with `rollup=”max”`

. It has bigger vertical scale, since rate spikes are much higher for the `requests_rate`

counter comparing to the average rate:

All the lines from the previous graph are present here for comparison. As in the previous case, yellow line (`irate`

) only sometimes reaches actual maximum rates (spikes).

# Conclusion

`Irate`

doesn’t capture spikes — it just returns a sample of per-second rate values. If you want to capture all the spikes on volatile counters, then use `rollup_rate`

function from MetricsQL.

There is another widespread myth about `irate`

— it is a faster alternative to `rate`

. The origin of the myth is: `irate`

takes only two last points on the given `[range]`

interval, while `rate`

requires all the points on the `[range]`

interval. While this is true, the performance difference is usually negligible, since Prometheus spends CPU time on extracting all the time series points for the given `[start … end]`

interval of the query_range API regardless of the used function.

If in doubt, prefer `rate`

over `irate`

, since `rate`

consistently returns average per-second rate values for the given `[range]`

, while `irate`

usually returns a random set of per-second rate values, which may look like garbage for volatile fast-moving counters.

Update: Chris Siebenmann wrote an interesting article on how to capture spikes and dips in Prometheus with irate + subqueries.

Update2: VictoriaMetrics is open source now, so you can investigate how it implements `rollup*`

functions.