ObservabilityLXXXIX · Observability Platform Monitoring ItselfPlatformMonitoring
Rule Failure Detection
What you'll learn
- Explain the Prometheus rule evaluation pipeline and the metrics it exposes
- Distinguish a slow rule from a failing rule and alert on each correctly
- Configure rules that catch rule evaluation drift, missed iterations, and dropped notifications
- Diagnose the five most common rule failure shapes (timeout, syntax, cardinality, missing metric, dependency order)
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A recording rule that has been quietly failing evaluation for six weeks. The recording rule file shipped with a typo three deployments ago. The error counter ticks up every evaluation interval. Nobody notices because no alert watches the rule failure counter itself. The dashboards that depend on the recording rule show stale data; the alerts that depend on the recording rule do not fire when they should. The platform “works”. The platform is wrong.
Rule failure detection is the discipline of watching the rule evaluator the same way the previous lessons watched the scraper and the process. Prometheus exposes its own evaluation metrics, and the right self-monitoring setup reads them.
What it is
Prometheus evaluates rules on a schedule. Each rule_files
entry is parsed into rule groups; each rule group is evaluated
on its interval (default evaluation_interval, which is 1
minute by default). The evaluation produces either a sample
written to the TSDB (recording rule) or an alert sent to
Alertmanager (alerting rule).
Prometheus exposes the following rule-evaluation metrics:
prometheus_rule_evaluation_failures_total{rule_group}— counter of evaluation failures per rule group. Increments every time the group fails to evaluate.prometheus_rule_evaluation_duration_seconds{rule_group}— histogram of evaluation wall time per group.prometheus_rule_group_last_evaluation_timestamp_seconds \{rule_group, file\}— gauge. Wall-clock timestamp of the last successful evaluation.prometheus_rule_group_last_duration_seconds \{rule_group, file\}— gauge. Wall-clock duration of the last evaluation.prometheus_rule_group_rules{rule_group, file}— gauge. Number of rules in the group.prometheus_rule_group_iterations_total{rule_group}— counter of completed iterations.prometheus_rule_group_iterations_missed_total \{rule_group\}— counter of iterations skipped because the previous one had not finished.prometheus_notifications_dropped_total— counter of alerts dropped on the path to Alertmanager.prometheus_notifications_queue_length— gauge of pending notifications.
The right approach is to alert on three of these:
rate(prometheus_rule_evaluation_failures_total[5m]) > 0— rule is failing.prometheus_rule_group_iterations_missed_totalincreasing — group is too slow for its interval.time() - prometheus_rule_group_last_evaluation_timestamp _seconds > interval * 2— group has stopped evaluating.
Why a sysadmin cares
Rule evaluation failures are the second most expensive silent failure in a Prometheus stack, after scrape failures. A recording rule that has not evaluated for an hour means every downstream dashboard is querying raw data instead of the precomputed series, and every downstream alert has stopped firing. The on-call engineer finds out only when an investigation depends on the missing data.
A second reason: rule evaluation is the most common source of CPU pressure on a Prometheus host. A single recording rule that runs an expensive aggregation over a 30-day window can saturate the engine. The right self-monitoring catches the drift before the engine does.
How it works
evaluation_interval (default 1m)
|
v
+----------------------+
| rule group scheduler |
+----------------------+
| | |
v v v
+---+ +---+ +---+
| R | | R | | R | R = rule
+---+ +---+ +---+
| | |
+-------+-------+
|
v
+-----------------+
| query engine |
+-----------------+
|
v
+-----------------+
| TSDB write | (recording rule)
+-----------------+
|
v
+-----------------+
| notify queue | (alerting rule)
+-----------------+
|
v
Alertmanager
Each group is evaluated independently. The metrics above are
emitted per group and per file. The most important
diagnostic is prometheus_rule_group_last_evaluation_timestamp _seconds: if it stops advancing, the group has stopped
running.
When a group’s evaluation exceeds the group interval, the
next iteration is missed, not queued. The
prometheus_rule_group_iterations_missed_total counter
increments. The rule’s last value persists at the TSDB until
the next successful evaluation. Alerting rules that depend on
the rule continue to fire on stale data.
Under the hood
How to configure it
Rule-failure alerts
# /etc/observer/rules/rule-failures.yml
groups:
- name: rule-evaluation-health
rules:
# 1. Any rule group is failing evaluation.
# Pages if a rule expression is broken or timing out.
- alert: RuleEvaluationFailing
expr: |
rate(prometheus_rule_evaluation_failures_total[5m]) > 0
for: 5m
labels:
severity: critical
team: platform
annotations:
summary: |
Rule group {{ $labels.rule_group }} is failing
evaluation. Inspect Prometheus logs.
# 2. Group has missed iterations.
# Catches a group that is too slow for its interval.
- alert: RuleGroupMissingIterations
expr: |
increase(prometheus_rule_group_iterations_missed_total[1h]) > 0
for: 5m
labels:
severity: warning
team: platform
# 3. Group has not evaluated within 2x its expected interval.
# Catches a stuck group that is not failing but has
# stopped running.
- alert: RuleGroupStale
expr: |
(time() - prometheus_rule_group_last_evaluation_timestamp_seconds)
> (2 * 60)
# 2x evaluation_interval; tune to your config
for: 5m
labels:
severity: warning
team: platform
# 4. Last evaluation duration is close to the interval.
# Catches a group that will miss iterations next time.
- alert: RuleGroupAlmostMissingIteration
expr: |
prometheus_rule_group_last_duration_seconds
> (0.8 * 60)
# 0.8x evaluation_interval; tune to your config
for: 5m
labels:
severity: warning
team: platform
# 5. Notification queue is growing.
# Catches the case where Alertmanager is unreachable.
- alert: PrometheusNotificationQueueGrowing
expr: |
prometheus_notifications_queue_length > 1000
for: 5m
labels:
severity: warning
team: platform
# 6. Alerts dropped on the path to Alertmanager.
- alert: PrometheusNotificationsDropped
expr: |
rate(prometheus_notifications_dropped_total[5m]) > 0
for: 5m
labels:
severity: critical
team: platform
Make rule groups small
A group that contains one expensive rule blocks every other rule in the group on the same interval. Splitting rules into smaller groups is the cheapest performance fix:
# /etc/prometheus/rules/app.yml
groups:
# Group A: cheap rules that must run every 30s.
- name: app-availability
interval: 30s
rules:
- record: app:http_error_ratio:5m
expr: |
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total[5m]))
# Group B: expensive aggregation that runs every 5 minutes.
- name: app-aggregates
interval: 5m
rules:
- record: app:requests_per_service:24h
expr: |
sum by (service) (increase(http_requests_total[24h]))
The two groups run on independent schedules. A slow group B does not delay group A.
How to validate it
Confirm rules are evaluating on schedule:
# READ-ONLY - last evaluation timestamp per group.
curl -s http://prom-primary.internal:9090/api/v1/rules \
| jq '.data.groups[] | {name: .name, lastEval: .file, interval: .interval, lastEvalAgo: (now - .rules[0].lastEvaluation)}'
A healthy output shows lastEvalAgo less than twice the
group’s interval for every group. A group with a stale
lastEvalAgo has stopped evaluating.
Confirm rule evaluation failures are visible:
# READ-ONLY
curl -s 'http://prom-primary.internal:9090/api/v1/query?query=prometheus_rule_evaluation_failures_total' \
| jq '.data.result[] | {group: .metric.rule_group, total: .value[1]}'
A healthy output shows the counter at the value it had at startup (zero for newly shipped groups, the historical value for groups with prior failures). A counter that is climbing in real time is the rule-failure signal.
Confirm the rule evaluator can keep up:
# READ-ONLY - iterations vs missed iterations.
curl -s 'http://prom-primary.internal:9090/api/v1/query?query=prometheus_rule_group_iterations_missed_total' \
| jq '.data.result[] | {group: .metric.rule_group, missed: .value[1]}'
Any non-zero value means a group has missed an iteration. Sustained missed iterations mean the group is too slow for its interval; the rule on the next lesson covers the fix.
Inject a failure to confirm the alerts fire:
# CONFIGURATION - add a rule with a deliberate syntax error.
sudo tee /etc/prometheus/rules/canary-broken.yml >/dev/null <<'YAML'
groups:
- name: canary-broken
rules:
- alert: CanaryBroken
expr: this_is_not_valid_promql((
for: 1m
YAML
sudo systemctl reload prometheus
# After 5 minutes, expect RuleEvaluationFailing to fire.
curl -s 'http://observer.internal:9090/api/v1/alerts' \
| jq '.data.alerts[] | select(.labels.alertname=="RuleEvaluationFailing")'
# Undo.
sudo rm /etc/prometheus/rules/canary-broken.yml
sudo systemctl reload prometheus
A working setup shows a firing alert within five minutes. Restoring the config removes the alert within one interval.
How it can fail
1. Syntax error in the rule file
The rule file does not parse. Symptom: Prometheus logs an
error at startup and on every reload; the rule does not load;
no metric is emitted for that group. Action: promtool check rules /etc/prometheus/rules/...yml validates syntax
before reload. Add this to the CI pipeline.
2. Query timeout in a recording rule
The recording rule’s expression is valid but takes longer
than --query.timeout. Symptom: prometheus_rule_evaluation _failures_total increments for the group; the recording rule
output is stale. Action: reduce the time window in the
expression, pre-aggregate the source metric, or move the
expensive aggregation to a longer interval group.
3. Cardinality blow-up in the rule output
The rule expression produces millions of series. Symptom:
TSDB head series count jumps when the rule evaluates; OOM
risk follows. Action: inspect the labels of the rule output
via group() ... by (...) clauses; drop the offending
high-cardinality label.
4. Missing source metric
The rule references a metric that the upstream exporter has
removed or renamed. Symptom: rule evaluates successfully but
produces empty output; downstream alerts never fire.
Action: use absent(up{job="expected_source"}) to alert
when the source itself disappears.
5. Group evaluation order dependency
A rule in group B depends on a recording rule in group A. Group A evaluates on its own interval; group B reads stale data on the first interval. Symptom: rule B’s output oscillates between correct and one-interval-behind. Action: move the dependency into the same group, or accept the lag and document it.
6. Notification queue full
The rule evaluator fires alerts faster than Alertmanager can
absorb them. Symptom: prometheus_notifications_queue_length
climbs; prometheus_notifications_dropped_total increments.
Action: investigate Alertmanager health (next lesson);
short-term, increase --alertmanager.notification-queue-capacity.
How to troubleshoot it
Security implications
Rule files are typically stored in a git repo or a config management system. A rule that has access to scrape credentials or that writes to an external URL is a small attack surface. The rule evaluator itself does not grant new permissions; the existing scrape credentials are reused.
The --query.timeout is a resource limit, not a security
limit. A rule that exceeds the timeout can be used as a
denial-of-service against the engine by an operator who
controls the rule files. Treat rule-file write access as
production write access.
Performance implications
Rule evaluation is the dominant CPU cost on a busy Prometheus host. The right sizing pattern is:
- Keep groups small. One expensive rule should not delay cheap rules.
- Match the group interval to the rule cost. A 24-hour aggregation runs every 5 minutes, not every 15 seconds.
- Pre-aggregate with recording rules so dashboards and alerting rules query precomputed series, not raw data.
- Alert on
prometheus_rule_group_last_duration_secondscrossing 80 percent of the interval. That is the early warning before the group starts missing iterations.
Production guidance
- Run
promtool check rulesin CI on every change to a rule file. The 30 seconds it takes prevents hours of silent failures. - Split rule files into groups by cost, not by topic. The cheap rules stay together; the expensive ones get their own group with their own interval.
- Alert on
prometheus_rule_evaluation_failures_totalandprometheus_rule_group_iterations_missed_totaltogether. The first catches a broken rule; the second catches a slow rule. - Use
group_left()orgroup_right()in alert rules that depend on recording rules, to surface the dependency in the alert annotation. - Re-test the alerts quarterly. Inject a syntax error in a canary rule file and confirm the alert fires.
Verification
You should now be able to answer:
- What does
prometheus_rule_evaluation_failures_totalmeasure, and what is its correct alerting threshold? - What is the difference between
last_evaluation_timestamp _secondsandlast_duration_seconds? - What does
prometheus_rule_group_iterations_missed_totalindicate, and what is the immediate operational consequence? - Why is a slow rule group a worse failure shape than a failing one?
- How does
promtool check rulesfit into the CI pipeline?
Quiz
Knowledge check · 8 questions
Q1. What does prometheus_rule_evaluation_failures_total measure?
Q2. A rule that evaluates successfully but produces an empty result is a rule evaluation failure.
Q3. Which of these are rule-evaluation health metrics? Select all that apply.
Q4. Which metric best detects a rule group that is too slow for its interval?
Q5. Name the Prometheus CLI command that validates a rule file before reload.
Q6. What does prometheus_rule_group_last_duration_seconds measure?
Q7. Which action is the correct fix when prometheus_notifications_queue_length climbs steadily?
Q8. Which of these cause prometheus_rule_evaluation_failures_total to increment? Select all that apply.
Passing score: 75%. Answers are checked in this browser.