Overview

Alerting is the “last mile” of a monitoring system — and the hardest to get right. A common predicament: servers run dozens of alerting rules generating hundreds of alert notifications daily. On-call engineers, bombarded by WeChat/DingTalk/email, gradually become desensitized — truly urgent alerts are drowned in noise until customer complaints reveal the system has been broken for hours.

The golden rule of SRE: every alert must have a clear action. If an alert doesn’t require immediate action or tracking, it shouldn’t exist. This article starts from alert fatigue and systematically covers alert tiering, SLO-based alert design, inhibition and aggregation strategies, alert metrics, and governance methods — helping you extract true “signal” from “alert noise.”

Reference: Google SRE Book “Monitoring Distributed Systems”, Prometheus Alerting Best Practices

I. Alert Fatigue: The Root Cause

1.1 Typical Symptoms of Alert Flooding

Team alert statistics (one week):
┌──────────────────────────┬────────┬──────────┐
│ Alert Type               │ Count  │ Acted On │
├──────────────────────────┼────────┼──────────┤
│ CPU usage > 80%          │ 156    │ 3        │
│ Disk usage > 70%         │ 89     │ 2        │
│ Pod restart              │ 34     │ 5        │
│ HTTP 5xx error rate > 1% │ 12     │ 4        │
│ DB connections > 80%     │ 8      │ 1        │
│ Certificate expiring     │ 3      │ 1        │
│ Service unreachable      │ 2      │ 2        │
├──────────────────────────┼────────┼──────────┤
│ Total                    │ 304    │ 18       │
└──────────────────────────┴────────┴──────────┘
Effective alert rate: 18/304 = 5.9%

Less than 6% of alerts require actual action; the remaining 94% is noise. In this state, on-call engineer behavior patterns become:

  • See alert notification → glance at it → judge “same old problem” → ignore
  • When a truly urgent alert arrives → also ignored → incident escalates
  • Post-mortem: “There were too many alerts, didn’t notice the critical one”

1.2 Common Causes of Alert Flooding

CauseSymptomRoot Cause
Unreasonable thresholdsCPU > 80% alerts frequentlyThreshold too low, normal load triggers it
No alert tieringAll alerts go to the same channelNo severity differentiation
Missing inhibitionUpstream failure triggers downstream cascadeNo inhibit_rules configured
Duplicate alertsSame issue notified every hourImproper repeat_interval
No alert documentationDon’t know how to handle received alertMissing runbook link
Auto-resolving alertsPod restarts and immediately recoversResolution notifications also go to channel

1.3 Signal vs. Noise

Alert quality = Signal / Total alerts

Signal: Issues requiring human intervention or attention
Noise: Auto-resolving, duplicate, false-positive, meaningless alerts

Target: Signal ratio > 80%, noise ratio < 20%

SRE Principle: If an alert can’t point to a specific action (fix, scale, record, investigate), it shouldn’t exist. Better to have fewer alerts than alert fatigue.

II. Alert Tiering Strategy

2.1 Four-Level Alert System

LevelLabelNotification MethodResponse TimeExample
P0 - Criticalseverity=criticalPhone + SMS + IM< 5 minCore service down, data loss
P1 - Warningseverity=warningIM + Email< 30 minPartial feature degradation, error rate increase
P2 - Noticeseverity=infoEmail / Daily reportBusiness hoursDisk 70%, certificate 30 days to expiry
P3 - Logseverity=debugLog only, no notificationNoneTest environment alerts, non-core components

2.2 Tiered Routing Configuration

# Alertmanager routing configuration
route:
  receiver: default
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    # P0 - Critical: immediate phone notification
    - matchers:
        - severity = critical
      receiver: critical-phone
      group_wait: 0s
      repeat_interval: 30m  # Repeat every 30 minutes

    # P1 - Warning: IM channel notification
    - matchers:
        - severity = warning
      receiver: warning-im
      group_wait: 30s
      repeat_interval: 2h

    # P2 - Notice: email notification
    - matchers:
        - severity = info
      receiver: info-email
      group_wait: 5m
      repeat_interval: 12h

    # P3 - Log: no notification
    - matchers:
        - severity = debug
      receiver: null

receivers:
  - name: critical-phone
    webhook_configs:
      - url: 'http://phone-gateway/alert'    # Phone notification gateway
        send_resolved: true
  - name: warning-im
    webhook_configs:
      - url: 'http://dingtalk-webhook/alert' # DingTalk/WeCom
  - name: info-email
    email_configs:
      - to: 'ops-team@example.com'
  - name: null
    # No notification method configured

2.3 Alert Tiering Principles

Alert tiering decision tree:

Does the service provide core external functionality?
  ├── No → severity=info/debug (don't disturb on-call)
  └── Yes → Does it affect many users (> 1% perceive)?
              ├── No → severity=warning
              └── Yes → Does it make functionality completely unavailable?
                          ├── No → severity=warning
                          └── Yes → severity=critical

Key principles:

  • User impact as the standard: Not “CPU is high” → critical, but “users can’t place orders” → critical
  • P0 alerts must be rare: No more than 2-3 P0s per week; otherwise, tiering is wrong
  • Match notification channel to level: Phone for P0, IM for P1, email for P2
  • Nighttime de-escalation: Reduce P1/P2 notification frequency outside business hours; P0 is not de-escalated

III. Problems with Threshold-Based Alerting

3.1 Traditional Threshold Alerting Pitfalls

# Typical threshold alerting rule
- alert: HighCPU
  expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
  for: 5m
  labels:
    severity: warning

This rule looks fine, but in practice:

ProblemScenarioConsequence
Hard to set thresholdCPU 80% is normal for some services, dangerous for othersFalse positives or false negatives
Transient spikesScheduled tasks cause brief CPU spikesMany invalid alerts
Capacity differencesLarge instance at 50% load far exceeds small instance at 80%Threshold doesn’t fit all instances
No user perspectiveCPU 80% but users unaffectedAlert has no practical meaning
Missing contextDon’t know if this affects SLOCan’t judge severity

3.2 Multi-Level Threshold Approach

One improvement is configuring multi-level thresholds to reduce false positives:

groups:
  - name: cpu-alerts
    rules:
      # P2 - Notice: CPU > 85%, sustained 30 minutes
      - alert: HighCPUWarning
        expr: |
          100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85          
        for: 30m
        labels:
          severity: info
        annotations:
          summary: "CPU usage elevated: {{ $labels.instance }}"
          description: "CPU usage {{ $value }}% exceeds 85% for 30 minutes"

      # P1 - Warning: CPU > 95%, sustained 5 minutes
      - alert: HighCPUCritical
        expr: |
          100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95          
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU usage critical: {{ $labels.instance }}"
          description: "CPU usage {{ $value }}% exceeds 95%, may impact service"
          runbook: "https://wiki.internal/runbooks/high-cpu"

This is better than a single threshold, but still “guessing thresholds” — you don’t know if 85% or 95% actually corresponds to user-perceived problems.

IV. SLO-Based Alerting

4.1 What Is SLO-Based Alerting

SLO (Service Level Objective) is an alerting approach based on service quality objectives. The core philosophy: don’t alert on “a metric exceeded a threshold,” alert on “error budget is being consumed too fast.”

Error Budget = 1 - SLO target

Example: SLO = 99.9% availability
Error Budget = 1 - 0.999 = 0.1%

Allowable unavailability in a 30-day window = 30 × 24 × 60 × 0.1% = 43.2 minutes

SLO-based alerting doesn’t ask “is CPU high?” but “are we consuming error budget too quickly?”

4.2 Multi-Window Multi-Burn-Rate Alerting

Google SRE recommends the Multi-Window Multi-Burn-Rate strategy:

groups:
  - name: slo-alerts
    rules:
      # Fast burn: 1h window consuming 2% error budget (14.4x burn rate)
      # → Detect severe problems within 1 hour
      - alert: SLOBurnRateFast
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h])) /
            sum(rate(http_requests_total[1h]))
          ) > (1 - 0.999) * 14.4
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[5m])) /
            sum(rate(http_requests_total[5m]))
          ) > (1 - 0.999) * 14.4          
        for: 2m
        labels:
          severity: critical
          slo: availability-999
        annotations:
          summary: "SLO error rate fast burn"
          description: "Error rate in the past hour exceeds SLO by 14.4x; error budget expected to be exhausted within 2 hours"
          runbook: "https://wiki.internal/runbooks/slo-burn"

      # Slow burn: 6h window consuming 5% error budget (6x burn rate)
      # → Detect sustained, non-sudden service quality degradation
      - alert: SLOBurnRateSlow
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h])) /
            sum(rate(http_requests_total[6h]))
          ) > (1 - 0.999) * 6
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[30m])) /
            sum(rate(http_requests_total[30m]))
          ) > (1 - 0.999) * 6          
        for: 15m
        labels:
          severity: warning
          slo: availability-999
        annotations:
          summary: "SLO error rate slow burn"
          description: "Error rate in the past 6 hours exceeds SLO by 6x; error budget expected to be exhausted within 5 days"

4.3 Multi-Window Multi-Burn-Rate Parameters

Window CombinationBurn RateBudget ConsumedAlert LevelPurpose
1h + 5m14.4x2% (1h)criticalFast detection of severe issues
6h + 30m6x5% (6h)warningDetect sustained issues
1d + 2h3x10% (1d)warningLong-term trend monitoring
3d + 6h1x10% (3d)infoBudget consumption tracking
Multi-window multi-burn-rate logic:

Both short window + long window exceed threshold → alert
(Avoids false positives from transient short-window spikes while ensuring response speed)

4.4 SLO Alerting vs Threshold Alerting

DimensionThreshold AlertingSLO Alerting
FocusWhether a metric exceeds thresholdWhether user experience is affected
False positive rateHigh (thresholds hard to set precisely)Low (based on actual error rates)
ContextMissingPresent (error budget consumption progress)
User relevanceIndirectDirect
Operational burdenHigh (frequent threshold tuning)Low (SLO targets are stable)
Alert volumeHighLow (only alerts that affect SLO)

Core philosophy: SLO alerting answers “do we need to act now to protect user experience?” rather than “does a number look bad?” This is a mindset shift from “infrastructure monitoring” to “user experience monitoring.”

V. Alert Inhibition and Aggregation

5.1 Inhibition Rules

When an upstream failure occurs, downstream services generate cascading alerts. Inhibition rules automatically silence lower-level related alerts when a higher-level alert fires.

# Alertmanager inhibit_rules
inhibit_rules:
  # When a service is unreachable, inhibit CPU/memory alerts for that service
  - source_matchers:
      - alertname = ServiceDown
    target_matchers:
      - alertname =~ 'HighCPU|HighMemory|DiskSpaceWarning'
    equal: ['service', 'instance']

  # When a cluster-level alert fires, inhibit all instance alerts in that cluster
  - source_matchers:
      - alertname = ClusterDown
      - severity = critical
    target_matchers:
      - severity =~ 'warning|info'
    equal: ['cluster']

  # When DB primary is down, inhibit replica sync lag alerts
  - source_matchers:
      - alertname = MySQLMasterDown
    target_matchers:
      - alertname = MySQLReplicationLag
    equal: ['cluster']

5.2 Alert Aggregation (Grouping)

Use group_by to merge related alerts into a single notification, reducing notification volume:

route:
  group_by: ['alertname', 'cluster', 'service']  # Group by alert name + cluster + service
  group_wait: 30s      # Wait 30s for first alert, collecting same-group alerts
  group_interval: 5m   # 5-minute interval for subsequent same-group alerts
  repeat_interval: 4h  # 4-hour repeat notification interval

Aggregation effect example:

Before aggregation (no group_by):
  [10:00:01] High CPU - web-01
  [10:00:02] High CPU - web-02
  [10:00:03] High CPU - web-03
  [10:00:05] High Memory - web-01
  [10:00:06] High Disk - web-01
  → 5 separate notifications

After aggregation (group_by: ['alertname', 'cluster']):
  [10:00:30] High CPU (web-01, web-02, web-03)
  [10:05:00] High Memory (web-01) + High Disk (web-01)
  → 2 aggregated notifications

5.3 Alert Deduplication

In dual-replica Prometheus scenarios, both instances generate the same alerts. Alertmanager deduplicates via the Gossip protocol:

# Alertmanager startup parameters (cluster mode)
alertmanager \
  --cluster.listen-address=0.0.0.0:9094 \
  --cluster.peer=alertmanager-2:9094 \
  --cluster.peer=alertmanager-3:9094 \
  --cluster.gossip-interval=200ms \
  --cluster.pushpull-interval=1m

VI. Alert Metrics and Governance

6.1 Key Alert Metrics

MetricDefinitionTargetCalculation
Alert precisionValid alerts / Total alerts> 80%Manual labeling + periodic audit
MTTRMean Time To Recovery< 30minIncident records
False positive rateFalse alerts / Total alerts< 10%Alert + ticket matching
Alert volumeDaily total alertsTrending downPrometheus metrics
Alert silence rateSilenced alerts / Total alerts< 20%Alertmanager API
P0 alert countWeekly P0 alerts< 5Alert records
Auto-resolution rateAuto-resolved alerts / Total alerts< 30%Alertmanager metrics

6.2 Measuring Alerts with Prometheus

# Alert metric rules
groups:
  - name: alert-metrics
    rules:
      # Daily total alerts
      - record: alerts:fired:total_per_day
        expr: increase(ALERTS{alertstate="firing"}[24h])

      # Alert count by severity
      - record: alerts:fired:by_severity
        expr: count by(severity) (ALERTS{alertstate="firing"})

      # Auto-resolution rate
      - record: alerts:auto_resolved:rate
        expr: |
          sum(rate(ALERTS_FOR_STATE{alertstate="resolved"}[1h])) /
          sum(rate(ALERTS_FOR_STATE[1h]))          

      # P0 alert count (weekly)
      - record: alerts:critical:per_week
        expr: increase(ALERTS{alertstate="firing", severity="critical"}[7d])

6.3 Alert Governance Process

┌─────────────────────────────────────────────────────┐
│                Alert Governance Loop                  │
│                                                      │
│  1. Alert audit (monthly)                            │
│     │                                                │
│     ▼                                                │
│  2. Classification: valid / false positive /         │
│     noise / missing docs                             │
│     │                                                │
│     ▼                                                │
│  3. Root cause analysis: bad threshold / missing     │
│     inhibition / outdated rules                      │
│     │                                                │
│     ▼                                                │
│  4. Optimization: adjust thresholds / add            │
│     inhibition / delete rules / add docs             │
│     │                                                │
│     ▼                                                │
│  5. Effect validation: compare next month's          │
│     alert volume and precision                       │
│     │                                                │
│     ▼                                                │
│  6. Continuous iteration ←────────────────────────── │
└─────────────────────────────────────────────────────┘

6.4 Alert Audit Script

#!/bin/bash
# alert-audit.sh — Monthly alert audit report

AM_URL="http://alertmanager:9093"
PROM_URL="http://prometheus:9090"

echo "========== Monthly Alert Audit Report =========="
echo "Time range: $(date -d '1 month ago' '+%Y-%m-%d') ~ $(date '+%Y-%m-%d')"
echo ""

# Alert count by severity
echo "## Alert Count Statistics"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=sum by(severity)(increase(ALERTS{alertstate="firing"}[30d]))' | \
  jq -r '.data.result[] | "  \(.metric.severity): \(.value[1])"'

echo ""

# Top 10 high-frequency alerts
echo "## Top 10 High-Frequency Alerts"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=topk(10, sum by(alertname)(increase(ALERTS{alertstate="firing"}[30d])))' | \
  jq -r '.data.result[] | "  \(.metric.alertname): \(.value[1])"'

echo ""

# Auto-resolution rate
echo "## Auto-Resolution Rate"
curl -s "$PROM_URL/api/v1/query" \
  --data-urlencode 'query=sum(rate(ALERTS{alertstate="resolved"}[30d])) / sum(rate(ALERTS[30d]))' | \
  jq -r '.data.result[0].value[1] | "  Auto-resolution rate: \(.* 100 | floor)%"' 2>/dev/null

echo ""
echo "=========================================="

VII. Alerting Rule Writing Standards

7.1 Alerting Rule Template

- alert: <AlertName>           # CamelCase, concise and clear
  expr: <PromQL>               # Query expression
  for: <duration>              # Duration to avoid transient alerts
  labels:
    severity: <level>          # critical/warning/info
    service: <service>         # Affected service
    team: <team>               # Responsible team
  annotations:
    summary: "<one-line description>"  # Brief summary
    description: "<detailed description>"  # Include current value and impact
    runbook: "<URL>"           # Runbook link
    dashboard: "<URL>"         # Grafana dashboard link

7.2 Good Alert vs Bad Alert

# ✗ Bad alert: insufficient information, not actionable
- alert: HighCPU
  expr: cpu_usage > 80
  for: 5m
  labels:
    severity: warning

# ✓ Good alert: complete information, actionable
- alert: APIServerHighCPU
  expr: |
    100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle", job="api-server"}[5m])) * 100) > 90    
  for: 10m
  labels:
    severity: warning
    service: api-server
    team: platform
  annotations:
    summary: "API Server CPU usage exceeds 90%"
    description: "Instance {{ $labels.instance }} CPU usage {{ $value }}%, sustained 10 minutes, may impact API response latency"
    runbook: "https://wiki.internal/runbooks/api-server-high-cpu"
    dashboard: "https://grafana.internal/d/api-server?var-instance={{ $labels.instance }}"

7.3 Alerting Rule Review Checklist

Check ItemRequirement
Alert nameConcise and clear, problem identifiable from name
DurationReasonable for time to avoid transient spikes
Severity labelCorrect severity tier
SummaryOne sentence explaining what happened
DescriptionIncludes current value, impact scope, related instances
RunbookActionable handling steps document
DashboardGrafana dashboard link
InhibitionRelated inhibit_rules configured
DeduplicationAppropriate group_by added

VIII. Practical Alert Noise Reduction Tips

8.1 Using for to Eliminate Transient Spikes

# Disk usage alert: for 15m to avoid log write spikes
- alert: DiskSpaceWarning
  expr: |
    (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} /
     node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 < 20    
  for: 15m   # Alert only after 15 minutes sustained
  labels:
    severity: warning

8.2 Using Prediction Functions for Early Alerting

# Predict disk will fill within 24 hours
- alert: DiskWillFillIn24h
  expr: |
    predict_linear(node_filesystem_avail_bytes[2h], 24 * 3600) < 0    
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "Disk predicted to fill within 24 hours: {{ $labels.instance }}"
    description: "Based on past 2-hour trend, {{ $labels.mountpoint }} will fill within 24 hours"

8.3 Using absent to Detect Missing Data

# Exporter unreachable causing missing data
- alert: ExporterDown
  expr: absent(up{job="node-exporter"})
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "All node-exporter data missing"
    description: "Either the entire monitoring network is down, or all node-exporters are offline"

8.4 Time-Range-Aware Alerting

# Only alert non-critical issues during business hours
- alert: LowReplicaCountBusinessHours
  expr: |
    kube_deployment_status_replicas < kube_deployment_spec_replicas
    and on() (hour() >= 8 and hour() < 22 and day_of_week() > 0 and day_of_week() < 6)    
  for: 10m
  labels:
    severity: warning

IX. From Alert to Action: Runbooks

9.1 The Value of Runbooks

The value of an alert lies not in the notification itself, but in how quickly it can be acted upon after notification. A Runbook (operations manual) bridges the gap between alert and action.

Alert notification → Runbook link → Diagnostic steps → Fix operations → Verify → Close alert

9.2 Runbook Template

# Runbook: API Server High CPU Usage

## Alert Information
- Alert name: APIServerHighCPU
- Severity: warning
- Impact scope: API response latency may increase

## Diagnostic Steps

### 1. Verify the alert
Check Grafana dashboard to confirm CPU is indeed sustained high
- Dashboard: https://grafana.internal/d/api-server

### 2. Check abnormal processes
```bash
ssh {{ $labels.instance }}
top -c -b -n 1 | head -20

3. Check request volume

Confirm whether there’s an abnormal traffic spike

curl -s http://localhost:9090/api/v1/query?query=rate(http_requests_total[5m])

Fix Operations

Case A: Traffic spike

  • Check for abnormal request sources
  • Consider temporarily scaling up Pod replicas

Case B: Resource leak

  • Check Goroutine count
  • Consider rolling restart of Pods

Escalation Path

  • If unresolved within 30 minutes → escalate to P0 → notify architect team

## Summary

Building a high-quality alerting system is a continuous optimization process. Key takeaways:

- **Alerts are actions**: Every alert must have a clear action; alerts that can't be acted on should be deleted or demoted to logging
- **Tiering is the foundation**: A four-level system tells on-call engineers what to focus on and what to ignore
- **SLO is the direction**: Migrate from threshold-based to SLO-based alerting, centered on user experience
- **Inhibition reduces noise**: Leverage Alertmanager's inhibit and group_by to eliminate cascading alerts and duplicate notifications
- **Metrics drive governance**: Regularly audit alert precision, MTTR, and false positive rates; use data to drive continuous optimization
- **Runbooks close the loop**: Every alert should have a corresponding handling document; alert  diagnosis  fix  verification forms a closed loop

Alert governance is not a one-time project but a continuous iterative process. Monthly audits, periodic optimization, and continuous iteration are what transform an alerting system from a "noise machine" into a "reliable fault signal light."

## References & Acknowledgments

This article referenced the following materials during writing. We thank the original authors for their contributions:

1. [Prometheus Alerting Best Practices](https://prometheus.io/docs/practices/alerting/)  Prometheus Authors, referenced for Prometheus Alerting Best Practices