Prometheus recording rules

Prometheus recording rules let Prometheus run a query on a timetable and save the answer as a brand new series. Instead of every Grafana panel and every alert working out CPU percent from raw per-core counters again and again, Prometheus does it once every 30 seconds and stores instance:node_cpu_utilisation:rate5m. Dashboards get faster, alert rules get shorter and everyone uses the same maths.

In this lesson you write the three recording rules the rest of the course depends on, load them on prom01, read them on the Rules page and in PromQL, and unit-test them with promtool test rules so a typo never reaches the server. Lesson 8 then builds alerts on top of these series.

Where this lesson fits

Lesson 7 of 10 in Prometheus Monitoring · Phase 2: Collect, query and store metrics · Before this: Lesson 6, the Prometheus TSDB · Next: Lesson 8, alerting rules

TimeYou needBy the end you can
About 75 minutesprom01 and node01 from Lesson 6 (Prometheus as a service, job “node” scraping both VMs with role labels), SSH to prom01A working /etc/prometheus/rules/recording.yml with three tested rules, and the habit of checking and testing every rule file before reload

What it is, in one line

A recording rule is a PromQL expression that Prometheus evaluates on a fixed interval and stores as a new, named time series, so later queries can read the precomputed answer.

In Simple Words

The school’s daily attendance register

In a big school, each class teacher marks attendance for her own forty children every morning. If the principal wanted the whole school’s attendance percentage, she could walk to every classroom, count every name and do the sum herself each time someone asked.

Instead, the office clerk adds it up once at 10 am and writes one line in the school register: “Class 5B, 92%”. When the inspector visits, the principal opens the register and answers in a second, and every report uses the same figure because everyone reads the same line.

The clerk’s line is a recording rule: one calculation done on a timetable and written down under a clear name.

Engineer's View

How the rule engine works

The rule files listed under rule_files in prometheus.yml contain groups. Each group has a name, an optional interval (default: the global evaluation_interval) and a list of rules. Prometheus evaluates the rules in a group one after another at the same timestamp, so a later rule can use the output of an earlier one, and different groups run in parallel.

FieldMeaningSample value
recordName of the new seriesinstance:node_memory_available:ratio
exprAny PromQL that returns an instant vector or scalarnode_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
labelsOptional labels added to (or overwriting) every output seriesteam: platform
interval (group)How often the whole group runs30s
limit (group)Fail the rule if it returns more than this many series (0 = no limit)500
query_offset (group)Evaluate a little in the past, to wait for late samples1m

The output is written to the TSDB like scraped data, with the __name__ set to the record value and the labels the expression returned. The official naming convention is level:metric:operations: level is the labels kept after aggregation (instance, job), metric is the original metric name with _total dropped when rate() was applied, and operations lists what was done, newest first (rate5m, ratio, sum). Colons are reserved for recording rules, so never put them in exporter metric names.

How it works, step by step

1Prometheus loads the rule files

At start and on every reload, Prometheus expands the rule_files globs, parses each YAML file and checks every expression. If any file fails, the reload fails as a whole and the previous rule set keeps running, which is safe but easy to miss. The diagram below shows a rule’s full cycle.

How Prometheus recording rules work from rule file to dashboard

2Each group runs on its own timer

Every interval, the group evaluates its rules in order at one shared timestamp. The result of each rule is appended to the TSDB with that timestamp. If a rule returns no series this time, nothing is written, and any series it wrote last time are marked stale so they vanish from queries straight away instead of hanging around for five minutes.

3Readers use the recorded series

Dashboards, alert rules and ad hoc queries now select instance:node_cpu_utilisation:rate5m directly. A dashboard that showed 30 days of CPU for 500 servers used to read 500 x number-of-cores raw counters for every point; now it reads 500 precomputed series. The comparison below shows the three rules you’ll write, with their raw query and their recorded name side by side.

The three Prometheus recording rules used in this course: CPU, memory and filesystem

4When a recording rule is worth it

Not every query deserves a rule, because each rule adds new series and costs memory and disk like any other metric. Use these as a guide:

Write a rule when…Skip the rule when…
The query is on a dashboard many people open, or on a long time rangeIt’s a one-off investigation query
An alert uses the same heavy expression as a dashboardThe raw query is already a simple selector
The expression aggregates thousands of series (per-core, per-path)The rule output would have almost as many series as its input
You need the same definition everywhere (“what is CPU %?”)The expression depends on a Grafana variable chosen at view time

Recording rules only write data from the moment they’re loaded. They don’t backfill history, so a new rule’s graph starts empty; promtool tsdb create-blocks-from rules can backfill past data if you need it.

Hands-on lab: write, check and unit-test recording rules on prom01

Before you run any command, double-check it

Read every command twice before you press Enter. Check the target: the server name, the user, the disk or device name, the path and the sizes. These commands are for learning and were tested in our lab, not on your system. At work, run the read-only commands first, follow your change process, make sure a backup exists, and ask a second person to check anything that changes or deletes data. You run them at your own risk.

Everything runs on prom01. You’ll create /etc/prometheus/rules/recording.yml with the three rules from the shared lab plan, confirm prometheus.yml already loads that folder, then write a unit test next to it. Keep the rule names as written, because Lessons 8 and 10 use them.

1Check the rules folder and the rule_files glob

Run this on prom01:

ls -ld /etc/prometheus/rules
grep -n -A1 'rule_files' /etc/prometheus/prometheus.yml
promtool check config /etc/prometheus/prometheus.yml

You should see this (your dates and line numbers may differ):

drwxr-xr-x 2 prometheus prometheus 4096 Nov  7 18:02 /etc/prometheus/rules
5:rule_files:
6-  - /etc/prometheus/rules/*.yml
Checking /etc/prometheus/prometheus.yml
 SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax

Lesson 2 created the folder and wrote the glob /etc/prometheus/rules/*.yml into prometheus.yml, so every .yml file you drop in the folder is loaded on the next reload (Lesson 8 adds alerts.yml next to today’s file). An empty folder is fine, which is why promtool doesn’t mention any rule files yet. If your line numbers differ because of the storage block from Lesson 6, that’s expected.

✅ Checkpoint: /etc/prometheus/rules exists, owned by prometheus, and rule_files lists /etc/prometheus/rules/*.yml.

2Write the recording rules

Paste this whole block:

sudo tee /etc/prometheus/rules/recording.yml >/dev/null <<'EOF'
groups:
  - name: node-recording
    interval: 30s
    rules:
      # share of time the CPUs were busy, 0 to 1, one series per server
      - record: instance:node_cpu_utilisation:rate5m
        expr: 1 - avg without (cpu, mode) (rate(node_cpu_seconds_total{job="node", mode="idle"}[5m]))

      # share of memory still available to programs, 0 to 1
      - record: instance:node_memory_available:ratio
        expr: node_memory_MemAvailable_bytes{job="node"} / node_memory_MemTotal_bytes{job="node"}

      # share of each real filesystem still free for normal users, 0 to 1
      - record: instance:node_filesystem_avail:ratio
        expr: |
          node_filesystem_avail_bytes{job="node", fstype!~"tmpfs|overlay|squashfs"}
            / node_filesystem_size_bytes{job="node", fstype!~"tmpfs|overlay|squashfs"}
EOF
sudo chown prometheus:prometheus /etc/prometheus/rules/recording.yml

All three store ratios between 0 and 1 rather than percentages; that’s the Prometheus convention, and Grafana can show them as percent with a unit setting. avg without (cpu, mode) keeps job, instance and role, so you can still filter by role="app" later. The expr: | form lets a long expression run over several lines.

✅ Checkpoint: /etc/prometheus/rules/recording.yml exists, owned by prometheus, with one group and three rules.

3Check the rules and the config with promtool

Run this on prom01:

promtool check rules /etc/prometheus/rules/recording.yml
promtool check config /etc/prometheus/prometheus.yml

You should see this:

Checking /etc/prometheus/rules/recording.yml
  SUCCESS: 3 rules found

Checking /etc/prometheus/prometheus.yml
  SUCCESS: 1 rule files found
 SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax

Checking /etc/prometheus/rules/recording.yml
  SUCCESS: 3 rules found

check config also follows the rule_files glob and checks each file it finds, so in practice one command covers both. Both commands parse every PromQL expression too, so a missing [5m] is caught here and not at 2 am.

✅ Checkpoint: Both commands print SUCCESS and “3 rules found”.

4Reload and look at the Rules page

Run this on prom01:

curl -s -X POST localhost:9090/-/reload
sleep 35
curl -s 'localhost:9090/api/v1/rules?type=record' | jq '.data.groups[] | {name, file, interval, rules: [.rules[] | {name, health, lastError}]}'

You should see:

{
  "name": "node-recording",
  "file": "/etc/prometheus/rules/recording.yml",
  "interval": 30,
  "rules": [
    {
      "name": "instance:node_cpu_utilisation:rate5m",
      "health": "ok",
      "lastError": null
    },
    {
      "name": "instance:node_memory_available:ratio",
      "health": "ok",
      "lastError": null
    },
    {
      "name": "instance:node_filesystem_avail:ratio",
      "health": "ok",
      "lastError": null
    }
  ]
}

Health “ok” means the last evaluation ran without an error; it does not mean the rule returned data, as you’ll see in the break-it section. In the browser, https://prom01.lab/ under Status > Rule health shows the same list with each rule’s last evaluation time and duration.

Prometheus recording rules on the Rule health page: node-recording group with three rules, all OK, every 30s
Status > Rule health: the node-recording group from recording.yml runs every 30s and all three rules show OK.

✅ Checkpoint: The API lists group node-recording with three rules, all health “ok”.

5Query the recorded series

Run this on prom01:

q(){ curl -s localhost:9090/api/v1/query --data-urlencode "query=$1" | jq -r '.data.result[] | [.metric.__name__, .metric.instance, (.metric.mountpoint // "-"), (.value[1] | tonumber * 1000 | round / 1000)] | @tsv'; }
q 'instance:node_cpu_utilisation:rate5m'
q 'instance:node_memory_available:ratio'
q 'instance:node_filesystem_avail:ratio{mountpoint="/"}'

You should see this (your values will differ):

instance:node_cpu_utilisation:rate5m   192.168.56.61:9100  -  0.018
instance:node_cpu_utilisation:rate5m   192.168.56.60:9100  -  0.046
instance:node_memory_available:ratio   192.168.56.61:9100  -  0.719
instance:node_memory_available:ratio   192.168.56.60:9100  -  0.484
instance:node_filesystem_avail:ratio   192.168.56.61:9100  /  0.62
instance:node_filesystem_avail:ratio   192.168.56.60:9100  /  0.56

These match what the long queries from Lesson 5 gave you, as ratios instead of percentages. In the Graph tab, compare instance:node_cpu_utilisation:rate5m with the raw expression for the last 15 minutes: the lines overlap, and the recorded one has points every 30 seconds starting from the moment you reloaded.

Prometheus recording rules compared in the Query page: raw CPU expression and instance:node_cpu_utilisation:rate5m for node01
Query page, Graph tab, last 15 minutes: the raw expression (top) and the recorded series (bottom) give the same values, and the recorded one starts at the reload.

✅ Checkpoint: All three recorded names return one series per instance (one per mount for the filesystem rule).

6Use recorded series in new queries

Recorded series are ordinary series, so you can build on them. These are queries Lesson 8 turns into alerts and Lesson 10 into dashboard panels:

Run this on prom01:

q '100 * instance:node_cpu_utilisation:rate5m{role="app"}'
q 'instance:node_filesystem_avail:ratio < 0.20'
q 'topk(1, 1 - instance:node_memory_available:ratio)'
q 'max_over_time(instance:node_cpu_utilisation:rate5m[1h])'

You should see this (your values will differ):

192.168.56.61:9100  -  1.8
192.168.56.60:9100  -  0.516
192.168.56.61:9100  -  0.031
192.168.56.60:9100  -  0.072

The second query prints nothing because no filesystem is below 20% free, which is the healthy answer. Notice the fourth query: max_over_time() over a recorded series is cheap and needs no subquery, which was the expensive fix from Lesson 5’s break-it section.

✅ Checkpoint: You get CPU % for node01 only, nothing for the 20% disk filter, and a peak CPU value for each server over the last hour.

7Write a unit test for the rules

promtool test rules starts an in-memory Prometheus, feeds it series you describe, runs your rules and compares the results. The notation '0+7.5x40' means “start at 0 and add 7.5 each step, 40 times”, and '256000000x40' means the same value 41 times.

Paste this whole block:

sudo tee /etc/prometheus/rules/recording_test.yml.test >/dev/null <<'EOF'
rule_files:
  - recording.yml

evaluation_interval: 30s

tests:
  - interval: 15s
    input_series:
      # node01 has 1 CPU, idle 7.5 of every 15 seconds = 50% busy
      - series: 'node_cpu_seconds_total{job="node", instance="192.168.56.61:9100", cpu="0", mode="idle"}'
        values: '0+7.5x40'
      - series: 'node_memory_MemAvailable_bytes{job="node", instance="192.168.56.61:9100"}'
        values: '256000000x40'
      - series: 'node_memory_MemTotal_bytes{job="node", instance="192.168.56.61:9100"}'
        values: '1024000000x40'
    promql_expr_test:
      - expr: instance:node_cpu_utilisation:rate5m
        eval_time: 10m
        exp_samples:
          - labels: 'instance:node_cpu_utilisation:rate5m{job="node", instance="192.168.56.61:9100"}'
            value: 0.5
      - expr: instance:node_memory_available:ratio
        eval_time: 10m
        exp_samples:
          - labels: 'instance:node_memory_available:ratio{job="node", instance="192.168.56.61:9100"}'
            value: 0.25
EOF
cd /etc/prometheus/rules && promtool test rules recording_test.yml.test

You should see this:

Processing test file: recording_test.yml.test
  SUCCESS

The test file ends in .test on purpose: the *.yml glob in prometheus.yml would otherwise try to load it as a rule file and the reload would fail. Paths in rule_files inside the test are relative to the test file, which is why you cd into the folder. Pick input values that give exact answers, because the test compares floats digit for digit and 0.2 can come out as 0.20000000000000007.

✅ Checkpoint: promtool test rules prints SUCCESS for recording_test.yml.test.

Prometheus recording rules lab: promtool check rules and test rules output
Lab steps 3 and 7: promtool check rules and promtool test rules both pass.

8Make check and test part of every change

From now on, every rule change goes through the same three commands before the reload. Put them in a small script so nobody skips a step:

Run this on prom01:

sudo tee /usr/local/bin/prom-rules-reload >/dev/null <<'EOF'
#!/bin/bash
set -e
cd /etc/prometheus/rules
promtool check config /etc/prometheus/prometheus.yml
for t in *.test; do [ -e "$t" ] && promtool test rules "$t"; done
curl -sf -X POST localhost:9090/-/reload && echo "reloaded OK"
EOF
sudo chmod 755 /usr/local/bin/prom-rules-reload
prom-rules-reload

You should see:

Checking /etc/prometheus/prometheus.yml
  SUCCESS: 1 rule files found
 SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax

Checking /etc/prometheus/rules/recording.yml
  SUCCESS: 3 rules found

Processing test file: recording_test.yml.test
  SUCCESS

reloaded OK

set -e stops the script at the first failure, so a broken file never reaches the reload, and curl -f makes a failed reload return an error too. At work the same commands usually run in a CI pipeline on every merge request to the monitoring Git repository.

✅ Checkpoint: prom-rules-reload prints SUCCESS for config, rules and tests, then “reloaded OK”.

Break It, Then Fix It

Three ways recording rules go wrong. The first two are loud, the third is silent, which makes it the dangerous one. Keep a copy of the good file before you start: sudo cp /etc/prometheus/rules/recording.yml ~/recording.yml.good.

1Bad YAML indentation stops the rule file loading

Open recording.yml and pull the expr: of the memory rule two spaces to the left, so it lines up with the dash instead of with record:. Then reload without checking first, the way people do when they’re in a hurry.

The broken part looks like this:

      - record: instance:node_memory_available:ratio
      expr: node_memory_MemAvailable_bytes{job="node"} / node_memory_MemTotal_bytes{job="node"}

Run this on prom01:

curl -s -X POST localhost:9090/-/reload
sudo journalctl -u prometheus -n 5 --no-pager | grep -E 'ERROR'
curl -s localhost:9090/metrics | grep '^prometheus_config_last_reload_successful'

You’ll get this error:

failed to reload config: one or more errors occurred while applying the new configuration (--config.file="/etc/prometheus/prometheus.yml")
... level=ERROR source=manager.go:286 msg="loading groups failed" component="rule manager" err="/etc/prometheus/rules/recording.yml: yaml: line 5: did not find expected '-' indicator"
... level=ERROR source=main.go:1772 msg="Failed to apply configuration" err="error loading rules, previous rule set restored"
prometheus_config_last_reload_successful 0

“Previous rule set restored” means the old rules keep running, so nothing looks broken in Grafana and the bad file sits on disk until the next restart, when Prometheus will refuse to start at all. prometheus_config_last_reload_successful 0 is the metric to alert on so this never goes unnoticed. promtool check rules would have shown the same YAML error before the reload.

Fix:

sudo cp ~/recording.yml.good /etc/prometheus/rules/recording.yml
prom-rules-reload

✅ Checkpoint: prom-rules-reload ends with “reloaded OK” and prometheus_config_last_reload_successful is 1 again.

2A rule name that loads but can’t be queried

Someone renames the memory rule with hyphens: instance:node-memory-available:ratio. Prometheus 3 accepts almost any UTF-8 text as a metric name, so this passes the check, and that’s the trap.

Run this on prom01:

sudo sed -i 's/instance:node_memory_available:ratio/instance:node-memory-available:ratio/' /etc/prometheus/rules/recording.yml
promtool check rules /etc/prometheus/rules/recording.yml && curl -s -X POST localhost:9090/-/reload
sleep 35
curl -s localhost:9090/api/v1/query --data-urlencode 'query=instance:node-memory-available:ratio' | jq -c '.data.result'
curl -s localhost:9090/api/v1/format_query --data-urlencode 'query=instance:node-memory-available:ratio' | jq -r .data

You’ll get this (no error, and no data):

Checking /etc/prometheus/rules/recording.yml
  SUCCESS: 3 rules found

[]
instance:node - memory - available:ratio

The rule is recording fine, but PromQL reads the unquoted name as instance:node minus memory minus available:ratio, three metrics that don’t exist, so the answer is empty. Every dashboard and alert that uses the new name silently shows nothing. You could only reach it with the quoted form {"instance:node-memory-available:ratio"}, which nobody will remember to type.

If you set metric_name_validation_scheme: legacy under global: in prometheus.yml, Prometheus refuses the file at load time with invalid recording rule name, which is a good safety net.

Fix: stick to letters, digits, underscores and colons:

sudo sed -i 's/instance:node-memory-available:ratio/instance:node_memory_available:ratio/' /etc/prometheus/rules/recording.yml
prom-rules-reload

✅ Checkpoint: instance:node_memory_available:ratio returns two series again.

3A rule that evaluates to nothing

The quietest failure. Change the CPU rule’s job selector to job="nodes" (a typo that happens when someone renames a job) and run the full check.

Run this on prom01:

sudo sed -i 's/job="node", mode="idle"/job="nodes", mode="idle"/' /etc/prometheus/rules/recording.yml
promtool check rules /etc/prometheus/rules/recording.yml
cd /etc/prometheus/rules && promtool test rules recording_test.yml.test

You’ll get this:

Checking /etc/prometheus/rules/recording.yml
  SUCCESS: 3 rules found

Processing test file: recording_test.yml.test
  FAILED:
    expr: "instance:node_cpu_utilisation:rate5m", time: 10m,
        exp: {__name__="instance:node_cpu_utilisation:rate5m", instance="192.168.56.61:9100", job="node"} 5E-01
        got: nil

check rules is happy because the PromQL is valid; there is no series with job=”nodes”. If this had been reloaded, the Rules page would still show health OK, the recorded series would go stale, and any alert like “CPU above 90%” built on it would never fire. Only the unit test, with its got: nil, catches it, which is the best argument for writing tests at all.

Fix:

sudo cp ~/recording.yml.good /etc/prometheus/rules/recording.yml
prom-rules-reload

On a running server you can also catch empty rules with a query like absent(instance:node_cpu_utilisation:rate5m), which Lesson 8 turns into an alert.

✅ Checkpoint: The test prints SUCCESS again and all three recorded series return data.

This happened at work

The cricket final and the slow dashboard

An SRE friend of mine was on call at a video streaming company in Mumbai on the night of a big cricket final. Traffic was four times a normal evening, and the main Grafana board, the one the whole war room kept open, took 40 seconds to load and then started timing out. Prometheus CPU on the main server was at 100%, and every refresh from twenty laptops made it worse.

The first thing the team tried was raising Grafana’s query timeout, which only meant more queries piled up at once. Looking at the query log showed the cause: the “edge bandwidth” panels summed rate() across about 180,000 per-connection series from the CDN edge servers, over a 6 hour range, every 10 seconds, on every open browser.

They wrote three recording rules on the spot, cluster:edge_bytes_sent:rate1m and two friends, aggregated by cluster and region, checked them with promtool, reloaded, and pointed the panels at them. Within ten minutes the board loaded in about a second and Prometheus CPU dropped to 30%. The follow-up was a rule in their dashboard review: any panel over 10,000 series or used in an alert must read a recorded series, and every rule file ships with a promtool test rules file in the same merge request.

Common mistakes and troubleshooting

MistakeWhat happensWhat to do instead
Reloading without promtool check rulesA YAML or PromQL error fails the reload; the old rules keep running until the next restart, which then failsRun promtool check config (or the prom-rules-reload script) before every reload
Naming the test file *.yml inside the rules folderThe rule_files glob loads the test as a rule file and the reload failsGive tests a different extension (.test) or keep them in a tests/ folder
Using sum by (instance) when you need job and role laterDashboards can no longer filter by role or jobUse avg without (cpu, mode) or keep the labels you need in by (…)
Hyphens or spaces in record namesThe rule loads but unquoted queries parse as subtraction and return nothingUse letters, digits, _ and : only, following level:metric:operations
Trusting health OK on the Rules pageA rule with a wrong label returns nothing and still shows OKUnit-test with promtool test rules and alert on absent() of key recorded series
Recording rules with almost as many output series as inputTwice the memory with no speed gainAggregate in the rule; record only when it reduces series or query cost
A rule group interval much longer than the rate windowGraphs look like steps and alerts react lateKeep interval at or below the scrape interval x 2, and the rate window at least 4 scrapes

Interview questions

Click a question to open it. Each one is written the way an interviewer would ask it. Under it you will see why they ask it and one way you could answer, in your own words.

Fresher level

1. What is a recording rule and why would you use one?

Why they ask this: They want a clear definition and one practical reason, not a textbook list.

How you might answer: A recording rule is a PromQL expression Prometheus runs on a schedule, saving the result as a new series under a name I choose. I use them when a query is heavy or used in many places, like CPU percent per server from per-core counters.

Then dashboards and alerts read instance:node_cpu_utilisation:rate5m directly, which is faster and makes sure everyone uses the same definition.

2. Explain the naming convention level:metric:operations with an example.

Why they ask this: They are checking you can read and write rule names that teams will recognise.

How you might answer: Take instance:node_cpu_utilisation:rate5m. The level, instance, says which labels are kept after aggregation; the metric part says what’s measured; rate5m says the operation, a 5 minute rate.

If I summed requests per job I’d write job:http_requests:rate5m, dropping _total because rate() was applied. Colons are only for recording rules, never for exporter metric names.

2–5 years experience

3. You changed a rule file and reloaded, but the new rule isn’t on the Rules page. Walk me through what you check.

Why they ask this: They want to see a calm, ordered troubleshooting path.

How you might answer: First the reload result itself: the /-/reload response and the journal, looking for “loading groups failed” and “previous rule set restored”, and prometheus_config_last_reload_successful. Then I run promtool check config, which also checks every file the rule_files glob matches.

If the reload worked, I check the file is matched by the glob, readable by the prometheus user, and that the group name isn’t a duplicate. Finally I look at the rule’s lastError on the Rules page or /api/v1/rules.

4. How do you test recording rules before they reach production?

Why they ask this: They are testing whether you know promtool test rules and use it seriously.

How you might answer: I write a test file with input_series in the expanding notation, like 0+7.5×40 for a counter, and promql_expr_test cases with eval_time and exp_samples. promtool test rules runs it with an in-memory engine and prints the expected and actual values on failure.

I pick inputs that give exact results, include a case for the label selectors, and run promtool check config plus the tests in CI on every merge request, so an empty rule like a job typo fails with got: nil before anyone reloads.

Senior level

5. Your Prometheus has 400 recording rules and rule evaluation is falling behind. How do you investigate and fix it?

Why they ask this: They want you to know the rule engine metrics and how groups run.

How you might answer: I’d check prometheus_rule_group_last_duration_seconds against prometheus_rule_group_interval_seconds and look at prometheus_rule_group_iterations_missed_total. Groups whose evaluation takes longer than their interval are the problem, and the Rules page shows per-rule durations.

Rules in one group run in order, so I split slow independent rules into separate groups, which run in parallel, and make heavy rules build on cheaper recorded series. I’d also ask whether some rules are needed at all, and add a limit per group so one bad rule can’t produce millions of series.

6. A team wants a new recording rule on a 1-year dashboard, starting today. What do you tell them?

Why they ask this: They are checking whether you understand that rules don’t backfill and what the options are.

How you might answer: The rule only writes data from the moment it’s loaded, so the dashboard would be empty for the past. If the raw data is still within retention, promtool tsdb create-blocks-from rules can evaluate the rule over a past range and produce blocks to add to the TSDB.

For a 1-year view, local retention probably isn’t long enough anyway, so I’d discuss downsampling or long-term storage like Thanos or Mimir, and keep the rule small enough to be cheap to store for a year.

Quick quiz

Q1. What does a recording rule produce?

  1. An email
  2. A new time series stored in the TSDB
  3. A Grafana panel
  4. A change to prometheus.yml
Show answer

B — The result of the expression is stored under the record name like any scraped series.

Q2. In instance:node_cpu_utilisation:rate5m, what does “instance” tell you?

  1. The rule only runs on one instance
  2. The labels kept after aggregation
  3. The Prometheus server name
  4. The scrape job
Show answer

B — The level part says which labels survive the aggregation.

Q3. Rules inside one group are evaluated…

  1. In parallel, in random order
  2. One after another, in the order written
  3. Only when someone queries them
  4. Once at startup
Show answer

B — Sequential evaluation lets a later rule use an earlier rule’s output.

Q4. A rule’s selector has a typo (job=”nodes”). What does the Rules page show?

  1. An error in red
  2. Health OK, but the recorded series has no data
  3. The rule is removed automatically
  4. Prometheus fails to start
Show answer

B — The PromQL is valid and returns an empty result, which is not an error.

Q5. Why did the lab name the test file recording_test.yml.test?

  1. promtool only reads .test files
  2. So the rule_files glob *.yml doesn’t load it as a rule file
  3. Tests must be hidden from promtool check
  4. It’s required by YAML
Show answer

B — The glob would otherwise pick up the test file and the reload would fail.

Quick recap

  • A recording rule runs a PromQL expression every group interval and stores the answer as a new series named by record.
  • Name rules level:metric:operations with letters, digits, underscores and colons; hyphens load in Prometheus 3 but break unquoted queries.
  • Rules live in groups in /etc/prometheus/rules/*.yml; rules in a group run in order, groups run in parallel.
  • promtool check config and check rules catch YAML and PromQL errors; only promtool test rules catches a rule that returns nothing.
  • A failed reload keeps the old rules running, so watch prometheus_config_last_reload_successful.

Next lesson

Lesson 8: Prometheus alerting rules → You build InstanceDown, HostHighCpu, HostDiskWillFillIn24h and HostLowMemory on top of today’s recorded series, watch them go pending and firing, and test them with promtool.

Further reading: Prometheus recording rules

Want to go deeper into Prometheus recording rules? The official recording rules reference, the rule naming best practices and the unit testing for rules page cover everything in this lesson in more depth. For the queries behind these rules see PromQL examples, and for what they cost in storage see the Prometheus TSDB.

You can also see the full plan for this course on the Prometheus Monitoring course page.

Scroll to Top

This website uses cookies

We use a few cookies to keep this site working. If you allow it, we also use Google Analytics to count visits so we can improve our lessons. You can accept all, deny the optional ones, or choose what to allow.