Command Palette

Search for a command to run...

Hectal
Case 4.4·Alerts & SLOsSEV1

“ShopLite was completely down for 25 minutes. Not one alert fired. Every rule looked correct.”

case name: The alert that couldn't fire

Service
shoplite-api · alerting
Impact
Total outage for 25 minutes, detected by customers
Detected by
Customers
Time to resolve
25 min outage; alert rules fixed the same day

Skills you'll use on this case

'no data' vs 'zero'up and absent()NaN in ratiosunit-testing alert rules with promtool

00 It starts

Reproduce this incident in your lab, then work the case alongside the timeline below. Try each query yourself before reading its result.

terminal
$ docker compose stop shoplite
── lab output ──
✔ Container shoplite Stopped

01 The investigation

  1. 22:10

    REPORT

    Customers: the site returns 502 from the load balancer

    The error-ratio and burn-rate alerts are perfectly written. They're just not firing.

  2. 22:14

    QUERY

    What does the SLI return right now?

    Nothing: an empty result, not 0, not 1. With the process down, Prometheus has no fresh samples for http_request_duration_seconds_count; after a few minutes those series go STALE and disappear from queries. A comparison on an empty vector is empty, and an empty alert expression means 'not firing'.

    PromQL· Prometheus
    slo:checkout_errors:ratio_rate5m
    result
    Empty query result
  3. 22:16

    QUERY

    The one metric that DOES know

    Prometheus records every scrape attempt. up is 0 when the scrape fails.

    PromQL· Prometheus
    up{job="shoplite"}
    result
    {instance="shoplite:8080", job="shoplite"}  0
  4. 22:35

    RESOLVED

    Process restarted; ShopLiteDown and absence alerts added and unit-tested

Root cause

Every alert measured the SERVICE'S OWN metrics. When the service stopped, those metrics stopped existing, so every alert expression evaluated to 'no data', which Prometheus treats as not firing. There was no alert on the scrape itself (up) and none on metrics going absent.

02 The concepts behind it

No data is not zero

PromQL works on sets of series. If the series don't exist (process down, target removed, label renamed in a deploy, exporter crashed), expressions return an EMPTY result, and alert rules only fire for series that exist and match. Ratios have a second trap: 0 errors / 0 requests is NaN, and NaN > 0.05 is false. A service with no traffic looks perfectly healthy.

up, absent, absent_over_time

up{job="x"} == 0 catches targets Prometheus knows about but can't scrape. absent(up{job="x"}) returns a series when NO matching series exists at all, which catches a target removed from service discovery or a typo in a job name. absent_over_time(metric[10m]) catches a metric that silently stopped being reported while the process stays up (renamed metric, broken instrumentation).

Test alerts like code

promtool test rules feeds synthetic series into your rules and asserts which alerts fire at which times, with which labels and annotations. It's fast, runs in CI, and is the only practical way to know that an alert handles gaps, NaN, and edge cases BEFORE an outage tests it for you. Pair it with regular fire drills (trigger a scenario, confirm the page arrives), as you've done throughout this course.

03 The fix

  1. 01Alert on the scrape and on absence

    ShopLiteDown pages; the Alertmanager inhibit rule from Case 4.1 then suppresses the noise from everything else about shoplite. ShopLiteMetricsAbsent catches the rarer case where the target itself vanished.

    obs-lab/prometheus/alerts.ymladd to fileyaml
          - alert: ShopLiteDown
            expr: up{job="shoplite"} == 0
            for: 1m
            labels: { severity: critical, service: shoplite }
            annotations:
              summary: "shoplite is down (scrape failing)"
          - alert: ShopLiteMetricsAbsent
            expr: absent(up{job="shoplite"})
            for: 5m
            labels: { severity: critical, service: shoplite }
            annotations:
              summary: "No shoplite scrape target exists — removed from discovery or job renamed"
    terminal
    $ curl -s -X POST localhost:9090/-/reload
    sleep 90 && curl -s localhost:9090/api/v1/alerts | jq -r '.data.alerts[] | .labels.alertname + " " + .state'
    ── expected output ──
    ShopLiteDown firing
  2. 02Bring it back

    terminal
    $ docker compose start shoplite
    ── expected output ──
    ✔ Container shoplite Started

04 Make sure it never surprises you again

  1. 01Unit-test the rule with promtool

    The test feeds up values minute by minute (1, 1, then 0 from minute 2) and asserts that at minute 5 the alert is firing with exactly these labels and annotations. Add a test like this for every paging alert and run promtool test rules in CI.

    obs-lab/prometheus/alerts.test.ymlwhole fileyaml
    rule_files: [alerts.yml]
    evaluation_interval: 1m
    
    tests:
      - interval: 1m
        input_series:
          - series: 'up{job="shoplite", instance="shoplite:8080"}'
            values: '1 1 0 0 0 0 0'
        alert_rule_test:
          - eval_time: 1m
            alertname: ShopLiteDown
            exp_alerts: []
          - eval_time: 5m
            alertname: ShopLiteDown
            exp_alerts:
              - exp_labels:
                  severity: critical
                  service: shoplite
                  job: shoplite
                  instance: "shoplite:8080"
                exp_annotations:
                  summary: "shoplite is down (scrape failing)"
    terminal
    $ docker compose exec prometheus promtool test rules /etc/prometheus/alerts.test.yml
    ── expected output ──
    Unit Testing: /etc/prometheus/alerts.test.yml
    SUCCESS

05 Your turn: write the query

Run each one against your lab before revealing the answer. Share your own version in the comments; there's usually more than one correct query.

01

Rewrite the checkout error ratio so it returns 0 instead of an empty result when there are no errors but there IS traffic.

02

Write an alert for 'checkout traffic dropped to nothing', which is often the first sign of an upstream outage (DNS, load balancer, app store release).

06 Interview questions from this case

01

Why might an alert fail to fire during a total outage, and how do you prevent it?

02

How do you test Prometheus alerting rules?

0/4 · 0%