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.
Frame the diagnosis
Performance work starts from a symptom and a signal — never from a resource dashboard.
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.
1ALERT HighCPU expr: cpu_util > 0.8 for: 5m -> page2ALERT HighMemory expr: mem_util > 0.85 for: 5m -> page3ALERT PodRestarted expr: increase(restarts[5m]) > 0 -> page4ALERT DiskWarning expr: disk_util > 0.75 -> page5 6# Last month: 63 pages.7# 58 self-resolved with no user impact8# 4 were real, and were also visible in the error rate9# 1 real outage had NORMAL cpu/memory and fired nothing10# Median time-to-ack rose from 2 min to 19 min.1ALERT CheckoutBudgetFastBurn -> page2 expr: burn_rate_1h > 14.4 AND burn_rate_5m > 14.43 note: ~2.1% of the 28d budget in 1 hour; runbook RB-0144 5ALERT CheckoutBudgetSlowBurn -> ticket6 expr: burn_rate_6h > 6 AND burn_rate_30m > 67 note: 5% of budget in 6 hours; investigate next working hour8 9ALERT QueueOldestMessageAge -> page10 expr: oldest_message_age > 900s11 note: users are waiting past our processing promise; RB-02212 13ALERT DiskFullProjection -> ticket14 expr: predict_linear(disk_free[6h], 4*3600) < 015 note: slow, predictable, irreversible — lead time is the pointThe 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.
| Question | If yes | If no | Example of a failure |
|---|---|---|---|
| Is a user being harmed? | Candidate for paging | Dashboard or ticket | CPU > 80% on a service meeting its SLO comfortably |
| Is it urgent? | Page | Ticket | Disk 75% full with three weeks of headroom |
| Is it actionable? | Page, with runbook | Fix the system, not the alert | Alert whose runbook says "monitor and see if it recovers" |
| Is it novel? | Page | Suppress or group | Six 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.
| Signal | Value | What it tells you | Verdict |
|---|---|---|---|
| SLO fast burn (1h + 5m windows) | page | Users are failing now, budget draining fast | smoking gun |
| SLO slow burn (6h + 30m windows) | ticket | Sustained degradation; costly but not an emergency | suspect |
| Oldest unprocessed message > 15 min | page | Async work failing invisibly to request metrics | smoking gun |
| Total traffic ~= 0 for 5 min | page | Requests are not arriving — often upstream of you | smoking gun |
| Watchdog (always firing) | page if absent | Monitoring outage looks identical to perfect health | smoking gun |
| CPU / memory / pod restarts | dashboard | Causes: consult after a symptom fires, never page on | normal |
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.
- 1Threshold → alert:
CPU > 80%fires during a normal traffic peak with no user impact. - 2Alert → on-call: page at 02:40; engineer checks, finds the service healthy, dismisses it.
- 3Repetition → trust: the same shape recurs nightly; the team learns that pages are usually nothing.
- 4Real incident → alert: checkout fails at 03:10 with normal CPU; the rule that would have caught it does not exist.
- 5Team → ack time: median rises from 2 to 19 minutes, because the population of pages is mostly noise.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- 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.