SLOsalertingpagingsymptomrunbookon-call

Alerts Worth Waking Someone For

An alert is a claim that a human should stop what they are doing right now. CPU > 80% is not that claim — it is a fact about a machine that may or may not correspond to a user having a bad time. Alert on symptoms users feel; keep causes on dashboards where they belong.

Follow the diagnosis

Frame the diagnosis

Performance work starts from a symptom and a signal — never from a resource dashboard.

Diagnostic question
Does this condition mean a user is being harmed right now, and is there something a woken human can actually do about it?
Symptom
The on-call phone fires four times a night. Three of the four resolve themselves before anyone opens a laptop. The fourth is a real outage that arrives among the noise and gets acknowledged twenty minutes late.
Signal
The SLI and its burn rate — user-visible failure, measured where users experience it. The misleading signals are resource thresholds: CPU, memory and disk utilisation describe machines, and machines being busy is often exactly what you paid for.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

Symptoms page, causes inform

The distinction that fixes most alerting is between symptom-based and cause-based conditions. A symptom alert says "users are experiencing failure": error rate is up, latency is past the threshold, the queue's oldest message is older than the SLA for that work. A cause alert says "a thing that sometimes precedes failure is happening": CPU is high, a pod restarted, disk is 85% full.

Symptom alerts have two properties cause alerts lack. They catch failures you did not anticipate — you cannot enumerate every cause of user-visible breakage, but you can measure breakage directly. And they do not fire when the anticipated cause occurs without the consequence, which is most of the time. High CPU on a service that is comfortably serving its traffic is not a problem; it is a well-utilised machine. Paging for it trains people to dismiss pages.

Cause signals are not worthless — they are what you look at *after* a symptom alert fires, to find out which layer is responsible. That is a dashboard job, not a paging job (Dashboards Built Around Questions). The narrow exception is the predictable, slow-moving, irreversible cause: a disk that will be full in four hours, a certificate expiring on Thursday, a quota that will be hit at the current growth rate. These earn a ticket or a low-urgency notification because the lead time is the entire value — but they still should not wake anyone at 03:00.

Cause-based: fires on machine states
1ALERT HighCPU expr: cpu_util > 0.8 for: 5m -> page
2ALERT HighMemory expr: mem_util > 0.85 for: 5m -> page
3ALERT PodRestarted expr: increase(restarts[5m]) > 0 -> page
4ALERT DiskWarning expr: disk_util > 0.75 -> page
5
6# Last month: 63 pages.
7# 58 self-resolved with no user impact
8# 4 were real, and were also visible in the error rate
9# 1 real outage had NORMAL cpu/memory and fired nothing
10# Median time-to-ack rose from 2 min to 19 min.
Symptom-based: fires on user harm
1ALERT CheckoutBudgetFastBurn -> page
2 expr: burn_rate_1h > 14.4 AND burn_rate_5m > 14.4
3 note: ~2.1% of the 28d budget in 1 hour; runbook RB-014
4
5ALERT CheckoutBudgetSlowBurn -> ticket
6 expr: burn_rate_6h > 6 AND burn_rate_30m > 6
7 note: 5% of budget in 6 hours; investigate next working hour
8
9ALERT QueueOldestMessageAge -> page
10 expr: oldest_message_age > 900s
11 note: users are waiting past our processing promise; RB-022
12
13ALERT DiskFullProjection -> ticket
14 expr: predict_linear(disk_free[6h], 4*3600) < 0
15 note: slow, predictable, irreversiblelead time is the point

The right-hand set fires when users are harmed, regardless of which layer caused it, and each rule names a runbook. The left-hand set fires when machines are busy, misses the outage that did not raise CPU, and taught the team that pages are usually nothing.

Four questions every paging alert must answer

Before a rule is allowed to wake someone, it should have an answer to four questions, and the absence of any one of them is grounds for demoting it to a ticket or deleting it. Is a user being harmed? If not, it is not a page. Is it urgent? If it can wait until working hours without getting materially worse, it is a ticket. Is it actionable? If the runbook says "wait and see" or the only fix is a change that needs a code review, waking someone buys nothing. Is it novel? If this alert fires alongside three others for the same root cause, it should be suppressed or grouped, or the responder spends their first ten minutes triaging notifications rather than the incident.

The runbook requirement deserves emphasis because it is the one most often waived. Every paging alert needs a link to a document that says what this alert means, what to check first, what the common causes are, and what actions are safe. Without it, alert quality depends on which person is on call, and the team's worst night is whenever its most experienced engineer is on holiday. Writing the runbook also functions as a design review of the alert: if you cannot write down what to do about it, that is the alert telling you it should not page.

Severity should map to response, not to how bad the condition feels. Two tiers handle almost everything: page (wake a human now) and ticket (someone looks during working hours). Adding a third and fourth tier mostly produces arguments about classification, and in practice everything below the top tier gets treated identically anyway.

The gate every alert passes through before it is allowed to page
QuestionIf yesIf noExample of a failure
Is a user being harmed?Candidate for pagingDashboard or ticketCPU > 80% on a service meeting its SLO comfortably
Is it urgent?PageTicketDisk 75% full with three weeks of headroom
Is it actionable?Page, with runbookFix the system, not the alertAlert whose runbook says "monitor and see if it recovers"
Is it novel?PageSuppress or groupSix alerts firing for one database failover

What to alert on, golden signal by golden signal

Working through the The Four Golden Signals gives a compact and nearly complete paging set for a request-driven service. Errors and latency become budget-burn alerts against the SLO, which is the bulk of the value (Burn-Rate Alerts: How Fast Is the Budget Going?). Traffic is almost never a paging condition by itself — high traffic is success, low traffic is only alarming if it means requests are not arriving, which is worth one carefully tuned rule for total loss of traffic. Saturation is where judgement is needed: it is a cause, so it usually belongs on a dashboard, except where saturation is irreversible or has a long lead time.

Two additions complete the set. A freshness or age alert for anything asynchronous — the oldest unprocessed message, replication lag, the age of the newest row in a pipeline — because a queue can be quietly failing while every request-level metric stays perfect (Depth Is Not an Emergency; Age Is). And a watchdog: an alert that fires continuously and is routed to a check confirming it is still firing. Without one, a monitoring outage looks exactly like perfect health, which is the failure mode nobody discovers until afterwards.

A useful sanity check on any alert set is to ask what would have fired during each of the last five incidents, and what fired when nothing was wrong. A set that would have caught all five and stayed quiet otherwise is working. A set that caught two and fired sixty times is training the team to ignore it, which is worse than having no alerts at all — because a silent system prompts people to check, while a noisy one persuades them not to.

A minimal paging set for one request-driven serviceILLUSTRATIVE
SignalValueWhat it tells youVerdict
SLO fast burn (1h + 5m windows)pageUsers are failing now, budget draining fastsmoking gun
SLO slow burn (6h + 30m windows)ticketSustained degradation; costly but not an emergencysuspect
Oldest unprocessed message > 15 minpageAsync work failing invisibly to request metricssmoking gun
Total traffic ~= 0 for 5 minpageRequests are not arriving — often upstream of yousmoking gun
Watchdog (always firing)page if absentMonitoring outage looks identical to perfect healthsmoking gun
CPU / memory / pod restartsdashboardCauses: consult after a symptom fires, never page onnormal

Key points

  • Alert on symptoms users feel; keep causes on dashboards where they explain the symptom after it fires.
  • Symptom alerts catch failures you never anticipated — you cannot enumerate every cause, but you can measure breakage directly.
  • Four gates before paging: is a user harmed, is it urgent, is it actionable, is it novel. Any "no" demotes it to a ticket.
  • Every paging alert needs a runbook; if you cannot write what to do about it, the alert is telling you it should not page.
  • A watchdog alert is mandatory — without one, a monitoring outage is indistinguishable from perfect health.

Follow the diagnosis

The causal chain, hop by hop — and the readings that invite the wrong conclusion.

  1. 1
    Threshold → alert: CPU > 80% fires during a normal traffic peak with no user impact.
  2. 2
    Alert → on-call: page at 02:40; engineer checks, finds the service healthy, dismisses it.
  3. 3
    Repetition → trust: the same shape recurs nightly; the team learns that pages are usually nothing.
  4. 4
    Real incident → alert: checkout fails at 03:10 with normal CPU; the rule that would have caught it does not exist.
  5. 5
    Team → ack time: median rises from 2 to 19 minutes, because the population of pages is mostly noise.
What this evidence makes people conclude — wrongly
  • Reading a quiet on-call rotation as a healthy system when it may mean the alerts do not cover the failures that occur.
  • Treating high CPU as a problem rather than as utilisation you are paying for.
  • Assuming an alert that fired during an incident detected it — check whether it fired *first* or merely joined in.
  • Concluding from many alerts firing at once that many things are broken, when it is usually one cause with poor grouping.
  • Believing a monitoring system is up because no alerts are firing.

Measure, fix, validate

An optimization is not finished until the metric that motivated it has moved.

How to measure it
  • • Count pages per week and the fraction that led to a human action; a low action rate is the primary alert-quality defect.
  • • Back-test the alert set against the last five incidents: which would have fired, how early, and what else fired alongside.
  • • Track median time-to-acknowledge as a proxy for trust — rising ack time usually means the team has learned to discount pages.
  • • Audit every paging rule for a linked runbook and a named owner; rules failing either check are candidates for deletion.
What actually fixes it
  • • Convert the paging set to symptom-based rules on SLI burn rate, and demote resource thresholds to dashboards ([[burn-rate-alerts]]).
  • • Delete or demote every rule that fails the four gates; deletion is a legitimate and under-used fix.
  • • Require a runbook link and an owner on every paging rule, enforced in the alert-definition review.
  • • Add freshness and age alerts for asynchronous work, which request-level metrics cannot see ([[queue-age]]).
  • • Add a watchdog and route it to an external check that fires when the watchdog goes silent.
How you know it worked
  • • Re-run the incident back-test after the change: the new set should catch the same incidents with fewer total pages.
  • • Confirm pages-per-week fell while the fraction leading to action rose — both moving together is the signal that quality improved.
  • • Trigger a controlled failure and confirm exactly one page fires, with a runbook, and that ack time returns to baseline.
What it costs
  • • Symptom-only alerting detects slightly later than a well-chosen leading cause would — you trade a little lead time for a large reduction in noise.
  • • Fewer alerts means more reliance on dashboards and investigation skill during an incident, which raises the floor on responder experience.
  • • Runbook requirements slow down adding alerts, which is mostly the point but is genuinely friction when a new failure mode appears.
Stop it coming back
  • Review alert quality monthly with pages-per-week and action-rate as standing metrics.
  • Require every new paging rule to state which SLI it protects; rules that protect none default to tickets.
  • After each incident, ask both "what fired that should not have" and "what should have fired and did not", and change rules accordingly.

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • ILLUSTRATIVEPage counts, ack times and the specific thresholds are teaching figures showing the shape of the failure, not measurements from a real rotation.
  • ENVIRONMENT-SPECIFICWhich conditions are worth paging on depends on architecture and on-call structure: a team that can roll back in 60 seconds can afford to page later than one whose only remedy is a 40-minute redeploy.

Misconceptions

Claim
“More alerts means better coverage.”
Reality
Coverage is about whether user-visible failure is detected, not about rule count. Adding cause-based rules increases noise without adding coverage, and past a threshold reduces effective coverage by training people to dismiss pages.
Claim
“We should page on CPU so we catch problems early.”
Reality
You catch busy machines early. Most high-CPU periods harm nobody, and the outages that matter frequently occur at normal CPU — lock contention, dependency failure and queue stalls all present with idle CPU (Low CPU, High Latency: Lock Contention).
Claim
“If it fired during the incident, the alert works.”
Reality
What matters is whether it fired *first* and pointed somewhere useful. An alert that joins the chorus twelve minutes in has added noise to the response, not detection.

Apply it