~learn.
?

Day 0138 minPreface+60 XP

SLIs, SLOs, and SLAs.

System Design Mastery Series — Production Readiness and Operational Excellence

4 stages6 code

watch it first · newThe whole lesson in a 19-minute animated lecture5 quiz checkpoints · ask the AI about any moment · captions, chapters and speed

Preface · 1 min

Preface#

You've come a long way.

Your journey
  • Week 1 —
    You learned how data scales — partitioning, replication, hot keys
  • Week 2 —
    You learned how systems fail — timeouts, retries, idempotency
  • Week 3 —
    You learned async patterns — queues, streams, backpressure
  • Week 4 —
    You learned caching — invalidation, thundering herds, consistency
  • Week 5 —
    You learned coordination — consensus, sagas, conflict resolution
  • Week 6 —
    You designed a notification platform — your first complete system
  • Week 7 —
    You designed a search system — indexing, relevance, resilience
  • Week 8 —
    You designed an analytics pipeline — streaming, batch, correctness
  • Week 9 —
    You learned enterprise concerns — multi-tenancy, compliance, security
  • Now, Week 10 —
    You learn to OPERATE what you build.

Here's the thing nobody tells junior engineers:

The uncomfortable truth — building the system is maybe 30% of the work; operating the system is the other 70%

What junior engineers think

Requirements → Design → Code → Test → Deploy → Done!

What senior engineers know

Requirements → Design → Code → Test → Deploy → Monitor → Alert → Debug → Fix → Deploy → Monitor → Scale → Monitor → Incident → Respond → Fix → Postmortem → Improve → Monitor → Capacity Plan → Scale → Monitor → New Feature → Deploy → Monitor → ...forever

The system is never "done." It's either running or it's dead.

This week, we close the loop. We learn to answer the question that matters most:

"Is my system healthy?"

And it starts with defining what "healthy" even means.

ExplainerThe whole lesson: SLIs, SLOs and SLAs

"Pretty reliable" can't settle whether an outage lasted ten minutes or an hour, so reliability needs precise words: an SLI is what you measure, an SLO the internal target (99.9%) and an SLA the external promise (99.5%, or credits). Good SLIs come from the golden signals and count good events over valid events at the load balancer, SLOs leave headroom above current performance, and each extra nine costs far more. A 99.9% SLO leaves a 43.2-minute monthly error budget with written policy bands, alerts fire on burn rate (14.4x pages now), dependencies cap you at 99.83%, and the same SLO is translated for engineers, product, executives and customers.

Part I · Stage 1 of 4 · 7 min

Part I: Foundations#

Chapter 1: The Language of Reliability#

1.1 Why We Need Precise Language#

Imagine this conversation:

text8 lines
Product Manager: "Is the API reliable?"
Engineer: "Yeah, it's pretty reliable."
PM: "What does 'pretty reliable' mean?"
Engineer: "It's usually up."
PM: "Usually? We just lost a $500K customer who said the API was down."
Engineer: "It was only down for 10 minutes!"
PM: "They said it was down for an hour."
Engineer: "Well, it depends on what you mean by 'down'..."

This conversation happens because we lack precise language for reliability.

Enter SLIs, SLOs, and SLAs.

1.2 The Three Pillars#

DiagramThe reliability language
%% title: The reliability language
flowchart TD
  SLI["SLI (Service Level Indicator)<br/>WHAT: A quantitative measure of service behavior<br/>EXAMPLE: #quot;The proportion of requests that complete in #lt; 200ms#quot;<br/>ANSWER TO: #quot;What should we measure?#quot;"]:::service
  SLO["SLO (Service Level Objective)<br/>WHAT: A target value for an SLI<br/>EXAMPLE: #quot;99.9% of requests should complete in #lt; 200ms#quot;<br/>ANSWER TO: #quot;How reliable should we be?#quot;"]:::core
  SLA["SLA (Service Level Agreement)<br/>WHAT: A contract with consequences for missing targets<br/>EXAMPLE: #quot;If availability drops below 99.5%, customer gets credit#quot;<br/>ANSWER TO: #quot;What happens if we fail?#quot;"]:::external
  SLI --> SLO --> SLA

Relationship

  • SLI = The measurement
  • SLO = The target (internal)
  • SLA = The promise (external, with consequences)

Rule of thumb

  • SLO should be stricter than SLA
  • You should hit SLO 99%+ of the time
  • You should almost never violate SLA

1.3 A Concrete Example#

Let's make this real with our notification platform from Week 6:

Notification platform reliability
SLI (What we measure) SLO (What we target)
1. Availability Proportion of API requests that succeed (non-5xx) 99.9% of requests succeed over 30 days
2. Latency Proportion of notification sends completing in < 500ms 99% of sends complete in < 500ms over 30 days
3. Delivery Proportion of notifications delivered within 30 seconds 95% of notifications delivered within 30 seconds
4. Correctness Proportion of notifications delivered to correct recipient 99.99% delivered to correct recipient (this is critical!)
SLA (What we promise customers)

"The Notification API will be available 99.5% of the time, measured monthly. If we fail to meet this target, affected customers will receive a 10% service credit for that month."

Notice
  • SLO availability (99.9%) > SLA availability (99.5%)
  • We have internal buffer to fix issues before violating SLA
  • Correctness has no SLA because we CANNOT get it wrong

Chapter 2: Choosing Good SLIs#

2.1 The Four Golden Signals#

Google's SRE book popularized these four signals that apply to most services:

The four golden signals
1. Latency

The time it takes to serve a request

  • Measure: p50, p90, p99, p99.9 latencies
  • Distinguish: Successful vs failed request latency
  • Why: Users perceive slow as broken
2. Traffic

How much demand is being placed on the system

  • Measure: Requests per second, queries per second
  • Context: By endpoint, by customer, by region
  • Why: Capacity planning, anomaly detection
3. Errors

The rate of failed requests

  • Measure: 5xx rate, 4xx rate (different meanings)
  • Include: Explicit errors AND implicit (wrong data returned)
  • Why: Direct measure of user impact
4. Saturation

How "full" the service is

  • Measure: CPU, memory, disk, queue depth, connections
  • Predict: When will we hit limits?
  • Why: Leading indicator of future problems
Your turn · checkpoint 1not answered

Design it: what counts as a "good" request?

You're writing the latency SLI for the notification API with a 200 ms threshold. Where would you measure it: in the application, or at the load balancer? Should a 404 for a bad recipient ID count as good or bad? Should health checks count at all?

Think it through, or jot it down. Then compare.

One strong answer

  • At the load balancer. It sees latency the way clients do, including time the application never sees.
  • A fast 4xx counts as good. The service handled the request correctly; the client made the mistake, and that isn't unreliability.
  • Health checks aren't valid events. They aren't user traffic, so they're excluded, along with internal calls and planned maintenance.

2.2 SLI Specification Template#

A good SLI is precisely specified:

SLI specification template

SLI Name: API Request Latency

Description: The proportion of valid API requests that complete within the latency threshold, measured at the load balancer.

Good events: HTTP requests to /api/* endpoints that:

  • Return 2xx or 4xx status code (successful or client error)
  • Complete within 200ms

Valid events: All HTTP requests to /api/* endpoints, excluding:

  • Health check endpoints (/api/health)
  • Internal service-to-service calls
  • Requests during planned maintenance windows

Measurement: SLI = (count of good events / count of valid events) × 100%

Data source: Load balancer access logs, aggregated every 5 minutes

Rationale:

  • Load balancer gives client-perspective latency
  • Excludes health checks (not user-facing)
  • Includes 4xx because they're still "handled correctly"
  • 200ms threshold based on user research showing satisfaction cliff

2.3 Common SLIs by Service Type#

SLIs by service type
  • API service
    • Availability — % requests returning non-5xx
    • Latency — % requests completing < threshold (p50, p99)
    • Throughput — Requests/second (for capacity SLOs)
  • Data pipeline
    • Freshness — % time data is < N minutes old
    • Correctness — % records processed without error
    • Completeness — % expected records actually present
    • Throughput — Records/second processed
  • Storage system
    • Availability — % time system accepts reads/writes
    • Durability — % data retrievable after write acknowledged
    • Latency — % operations completing < threshold
    • Throughput — Operations/second
  • Batch job
    • Success rate — % job runs completing successfully
    • Freshness — % time since last successful run < threshold
    • Runtime — % runs completing within expected duration
  • Real-time system — like our notification platform
    • Availability — % requests accepted
    • End-to-end latency — % events processed within time window
    • Delivery rate — % events successfully delivered
    • Ordering — % events delivered in correct order (if required)

Chapter 3: Setting SLOs#

3.1 The SLO Setting Framework#

Setting SLOs is both art and science:

SLO setting framework
  1. Understand user expectations
    • What do users actually need?
    • What would make them leave for a competitor?
    • What's the user journey? Where are they most sensitive?
    • Talk to customer support — what do users complain about?
  2. Look at historical data
    • What's our current performance?
    • What were we achieving when users were happy?
    • What were we achieving when they complained?
    • Don't set SLO at current performance — leave room for drift
  3. Consider business constraints
    • What's the cost of achieving higher reliability?
    • What's the cost of NOT achieving reliability (churn, reputation)?
    • What do competitors offer?
    • What does the SLA require? (SLO must exceed SLA)
  4. Start conservative, adjust with data
    • Set initial SLO slightly below current performance
    • Measure for 1-2 quarters
    • Tighten if we're easily meeting it AND users expect better
    • Loosen if we're constantly missing AND users are still happy
Your turn · checkpoint 2not answered

Trade-off: is one more nine worth it?

The notification API meets 99.9%. Leadership asks for 99.99%. In downtime per year, what does that extra nine buy users, and what might it cost the team?

Think it through, or jot it down. Then compare.

One strong answer

  • It buys going from about 8.8 hours of downtime a year to about 53 minutes.
  • It can cost something like 10× the infrastructure, a dedicated reliability team and much slower feature work.
  • The deciding question is whether users can tell. Most can't tell 99.9% from 99.99%, but they do notice features shipping quarterly instead of monthly.

3.2 The Reliability vs Velocity Trade-off#

Here's the insight that separates good SREs from everyone else:

The fundamental trade-off
100% reliability is
  • Impossible (hardware fails, networks fail, humans err)
  • Infinitely expensive (diminishing returns)
  • Actually harmful (no room to ship new features)
Consider
Availability Downtime/year
99.9% 8.76 hours
99.99% 52.6 minutes
99.999% 5.26 minutes
To go from 99.9% to 99.99%
  • Cost: Maybe 10x infrastructure spending
  • Effort: Dedicated reliability team
  • Velocity: Much slower feature development
  • Question: Do users actually need it?

Most users can't tell the difference between 99.9% and 99.99%. But they CAN tell the difference between "ships features monthly" and "ships features quarterly."

THE GOAL IS NOT MAXIMUM RELIABILITY. THE GOAL IS APPROPRIATE RELIABILITY.

3.3 SLO Documentation#

SLO document template
  • Service: Notification Platform API
  • Owner: Platform Team
  • Last updated: 2024-01-15
  • Review cadence: Quarterly
SLO 1: Availability
  • Objective: 99.9% of API requests return non-5xx responses
  • Window: Rolling 30 days
  • Measurement: Load balancer logs, 5-minute aggregation
  • Error budget: 0.1% × 30 days = 43.2 minutes of total downtime

Rationale: Users expect high availability for notifications. Historical performance shows we achieve 99.95% typically. 99.9% gives buffer for incidents while meeting user expectations.

SLO 2: Latency
  • Objective: 99% of API requests complete within 200ms (p99 < 200ms)
  • Window: Rolling 30 days
  • Measurement: Load balancer logs (request duration), 1-minute aggregation

Rationale: API is called from user-facing applications. User research shows satisfaction drops sharply above 200ms. 99% (not 99.9%) because some slow requests are acceptable.

SLO 3: Delivery
  • Objective: 95% of notifications delivered within 30 seconds
  • Window: Rolling 30 days
  • Measurement: End-to-end tracking from accept to delivery callback

Rationale: Notifications are often time-sensitive. 95% accounts for third-party provider variability. 30 seconds is acceptable for most notification use cases.

Dependencies
  • Email provider SLA: 99.5% availability
  • SMS provider SLA: 99.9% availability
  • Push notification service: 99.95% availability
  • A hard, synchronous dependency caps the SLO. The API SLO (99.9%) can sit above the email provider's 99.5% only because sends are queued and can fail over to a second provider; the delivery SLO is the one exposed to providers
Excluded from SLOs
  • Planned maintenance windows (announced 72 hours ahead)
  • Customer-caused issues (invalid API keys, malformed requests)
  • Force majeure events
Quick check3 questions · +5 XP each

SLIs, SLOs and SLAs

  1. Q1/3 Sales wants the customer SLA to promise 99.9% availability, the same as your internal SLO. What's the main risk?

    Answer & explanation

    Answer: C

    The SLO should be stricter than the SLA (99.9% internal vs 99.5% promised) so you can catch and fix problems before penalties apply. With equal targets, the very first SLO miss already costs money.

  2. Q2/3 The latency SLI specification counts 4xx responses as good events. Why?

    Answer & explanation

    Answer: A

    The rationale in the spec is that 4xx requests are still "handled correctly": the service did its job. Counting them as bad would penalise you for client errors; the point is accuracy, not padding the number.

  3. Q3/3 Leadership wants to raise availability from 99.9% to 99.99%. What's the lesson's strongest reason to question it?

    Answer & explanation

    Answer: D

    Each extra nine is far more expensive (more infrastructure, a dedicated team, slower velocity), yet most users can't tell 99.9% from 99.99%. It's 100% that is impossible; 99.99% is achievable, just costly. The goal is appropriate reliability.

Part II · Stage 2 of 4 · 11 min

Part II: Implementation#

Your turn · checkpoint 3not answered

Estimate: how much failure does 99.9% allow?

Your availability SLO is 99.9% over a rolling 30 days. How many minutes of full outage is that? This month you've had a 10-minute outage, 15 minutes of a degraded deploy and a 20-minute third-party outage. Are you still inside the SLO?

Think it through, or jot it down. Then compare.

One strong answer

  • Budget: 0.1% × 30 days × 1,440 minutes = 43.2 minutes.
  • Spent: 10 + 15 + 20 = 45 minutes, so you're 1.8 minutes over.
  • So the SLO is missed for this window, and a written policy says what happens next: here, a feature freeze until the budget recovers.

Chapter 4: Error Budgets#

4.1 What Is an Error Budget?#

The error budget is the most powerful concept in SRE:

Error budget explained

If your SLO is 99.9% availability over 30 days, you're promising to be "good" 99.9% of the time. That means you're ALLOWED to be "bad" 0.1% of the time — and 0.1% of 30 days = 43.2 minutes.

This 43.2 minutes is your ERROR BUDGET.

  • Month start → month end —
    Error Budget: 43.2 minutes
  • Day 5 —
    10-minute outage. Remaining: 33.2 minutes
  • Day 12 —
    Slow deployment, 15 minutes degraded. Remaining: 18.2 minutes
  • Day 20 —
    Third-party outage, 20 minutes. Remaining: -1.8 minutes (BUDGET EXCEEDED)
  • Action —
    Feature freeze until budget recovers
ExplainerError budget and burn rate

A 99.9% SLO over 30 days allows 43.2 minutes of bad time. A 10-minute outage on day 5, 15 degraded minutes on day 12 and a 20-minute outage on day 20 leave −1.8 minutes: feature freeze. A 14.4x burn rate spends 2% of the budget in an hour (all of it in about 50 hours) and pages; 6x spends 5% in 6 hours.

WorksheetThe notification API's error budget in requests (traffic is assumed)
Value
Window 30 days = 43,200 minutes
Budget at 99.9% 0.1% → 43.2 minutes of total outage
Average traffic (assumed) 1,000 requests a second → ~2.6 billion a month
Failed requests the budget allows 0.1% → ~2.6 million
Critical page: 14.4× burn for an hour error rate above 1.44%; ~52,000 failures in that hour = 2% of the budget
A bad deploy failing 10% of requests 100× burn → the month's budget gone in 7.2 hours

Conclusion: the budget is 43 minutes of total outage but hours of partial failure, so most of it is lost to bad deploys, not full outages. A 10% failure rate burns at 100×, so the 14.4× page fires within minutes, long before the budget is gone.

4.2 Error Budget Policies#

What happens when the budget runs out?

pythonBudgetStatus · 245 lines
# error_budget/policy.py

"""
Error budget policy implementation.

When error budget is depleted, we shift focus from features to reliability.
"""

from dataclasses import dataclass
from enum import Enum
from datetime import datetime, timedelta, timezone
from typing import List, Optional
import logging

logger = logging.getLogger(__name__)


class BudgetStatus(Enum):
    """Error budget health status."""
    HEALTHY = "healthy"          # > 50% budget remaining
    WARNING = "warning"          # 20-50% remaining
    CRITICAL = "critical"        # 0-20% remaining
    EXHAUSTED = "exhausted"      # Budget depleted


@dataclass
class ErrorBudget:
    """Error budget state for an SLO."""
    slo_name: str
    window_days: int
    target_percentage: float  # e.g., 99.9
    
    # Current state
    current_percentage: float
    budget_remaining_minutes: float
    budget_consumed_minutes: float
    
    # Burn rate
    burn_rate_1h: float   # How fast we're consuming (1 = normal)
    burn_rate_24h: float  # Smoothed over 24 hours
    
    @property
    def total_budget_minutes(self) -> float:
        """Total error budget in minutes."""
        error_percentage = 100 - self.target_percentage
        return (error_percentage / 100) * self.window_days * 24 * 60
    
    @property
    def budget_remaining_percentage(self) -> float:
        """Percentage of budget remaining."""
        return (self.budget_remaining_minutes / self.total_budget_minutes) * 100
    
    @property
    def status(self) -> BudgetStatus:
        """Current budget status."""
        remaining_pct = self.budget_remaining_percentage
        
        if remaining_pct <= 0:
            return BudgetStatus.EXHAUSTED
        elif remaining_pct < 20:
            return BudgetStatus.CRITICAL
        elif remaining_pct <= 50:
            return BudgetStatus.WARNING
        else:
            return BudgetStatus.HEALTHY
    
    @property
    def projected_exhaustion(self) -> Optional[datetime]:
        """When budget will be exhausted at current burn rate."""
        if self.burn_rate_24h <= 0:
            return None  # Not consuming budget
        
        # At burn rate 1x the WHOLE budget lasts exactly one window, so the
        # budget burns at burn_rate * (total budget / window length) per minute
        window_minutes = self.window_days * 24 * 60
        burn_per_minute = self.burn_rate_24h * self.total_budget_minutes / window_minutes
        minutes_until_exhausted = self.budget_remaining_minutes / burn_per_minute
        return datetime.now(timezone.utc) + timedelta(minutes=minutes_until_exhausted)


class ErrorBudgetPolicy:
    """
    Defines actions based on error budget status.
    
    Philosophy:
    - Budget healthy: Ship features freely
    - Budget warning: Increase caution
    - Budget critical: Focus on reliability
    - Budget exhausted: Feature freeze
    """
    
    def __init__(self, config):
        self.config = config
    
    def get_allowed_actions(self, budget: ErrorBudget) -> dict:
        """
        Get allowed actions based on budget status.
        """
        status = budget.status
        
        if status == BudgetStatus.HEALTHY:
            return {
                "feature_development": True,
                "risky_deployments": True,
                "experiments": True,
                "on_call_escalation": "normal",
                "deployment_approval": "team_lead",
                "rollback_threshold": "5xx_rate > 5%",
                "message": "Budget healthy. Ship features!"
            }
        
        elif status == BudgetStatus.WARNING:
            return {
                "feature_development": True,
                "risky_deployments": False,  # No risky changes
                "experiments": True,
                "on_call_escalation": "normal",
                "deployment_approval": "team_lead",
                "rollback_threshold": "5xx_rate > 2%",  # Lower threshold
                "message": "Budget warning. Exercise caution with deployments."
            }
        
        elif status == BudgetStatus.CRITICAL:
            return {
                "feature_development": False,  # Pause features
                "risky_deployments": False,
                "experiments": False,
                "on_call_escalation": "heightened",  # More eyes on alerts
                "deployment_approval": "engineering_manager",
                "rollback_threshold": "5xx_rate > 1%",  # Very sensitive
                "message": "Budget critical. Focus on reliability only."
            }
        
        else:  # EXHAUSTED
            return {
                "feature_development": False,
                "risky_deployments": False,
                "experiments": False,
                "on_call_escalation": "incident_mode",
                "deployment_approval": "director",
                "rollback_threshold": "any_degradation",
                "message": "Budget exhausted. Full reliability focus until recovered."
            }
    
    def should_alert(self, budget: ErrorBudget) -> List[dict]:
        """
        Determine what alerts should fire based on budget state.
        """
        alerts = []
        
        # Budget status alerts
        if budget.status == BudgetStatus.EXHAUSTED:
            alerts.append({
                "severity": "critical",
                "title": f"Error budget exhausted: {budget.slo_name}",
                "message": f"SLO {budget.slo_name} has exhausted its error budget. "
                          f"Feature development should halt.",
                "route": "engineering_leadership"
            })
        
        elif budget.status == BudgetStatus.CRITICAL:
            alerts.append({
                "severity": "warning",
                "title": f"Error budget critical: {budget.slo_name}",
                "message": f"Only {budget.budget_remaining_percentage:.1f}% budget remaining.",
                "route": "team_lead"
            })
        
        # Burn rate alerts (predicting future problems).
        # Time to exhaust a full budget = window / burn rate
        hours_to_exhaust = budget.window_days * 24 / max(budget.burn_rate_1h, 1e-9)
        
        if budget.burn_rate_1h > 14.4:  # 2% of a 30-day budget per hour (~50h to empty)
            alerts.append({
                "severity": "critical",
                "title": f"High burn rate: {budget.slo_name}",
                "message": f"At this rate a full budget lasts ~{hours_to_exhaust:.0f} hours. "
                          f"Burn rate: {budget.burn_rate_1h:.1f}x normal.",
                "route": "on_call"
            })
        
        elif budget.burn_rate_1h > 6:  # 5% of a 30-day budget per 6h (~5 days to empty)
            alerts.append({
                "severity": "warning",
                "title": f"Elevated burn rate: {budget.slo_name}",
                "message": f"Burn rate: {budget.burn_rate_1h:.1f}x normal.",
                "route": "on_call"
            })
        
        return alerts


class ErrorBudgetCalculator:
    """
    Calculates error budget from SLI measurements.
    """
    
    def __init__(self, metrics_client):
        self.metrics = metrics_client
    
    async def calculate_budget(
        self,
        slo_name: str,
        sli_query: str,
        target: float,
        window_days: int = 30
    ) -> ErrorBudget:
        """
        Calculate current error budget state.
        """
        # Get SLI value over window
        sli_value = await self.metrics.query(
            sli_query,
            window=f"{window_days}d"
        )
        
        # Calculate budget
        target_error_rate = 100 - target
        actual_error_rate = 100 - sli_value
        
        total_budget_minutes = (target_error_rate / 100) * window_days * 24 * 60
        consumed_minutes = (actual_error_rate / 100) * window_days * 24 * 60
        remaining_minutes = total_budget_minutes - consumed_minutes
        
        # Calculate burn rates
        sli_1h = await self.metrics.query(sli_query, window="1h")
        sli_24h = await self.metrics.query(sli_query, window="24h")
        
        expected_error_rate_1h = target_error_rate
        actual_error_rate_1h = 100 - sli_1h
        burn_rate_1h = actual_error_rate_1h / expected_error_rate_1h if expected_error_rate_1h > 0 else 0
        
        actual_error_rate_24h = 100 - sli_24h
        burn_rate_24h = actual_error_rate_24h / expected_error_rate_1h if expected_error_rate_1h > 0 else 0
        
        return ErrorBudget(
            slo_name=slo_name,
            window_days=window_days,
            target_percentage=target,
            current_percentage=sli_value,
            budget_remaining_minutes=max(0, remaining_minutes),
            budget_consumed_minutes=consumed_minutes,
            burn_rate_1h=burn_rate_1h,
            burn_rate_24h=burn_rate_24h
        )
Your turn · checkpoint 4not answered

What breaks with "alert if error rate > 0.1%"?

Your SLO allows 0.1% errors, so a teammate sets a page for any 5-minute window above 0.1%. What happens to the on-call engineer over the next month, and what would you alert on instead?

Think it through, or jot it down. Then compare.

One strong answer

  • It fires constantly. Every brief spike crosses 0.1% for a few minutes even though it barely dents the month's budget, and the pager says nothing about urgency.
  • People stop trusting it, so the page that matters gets ignored.
  • Alert on burn rate instead: how fast the budget is being spent, checked over a short and a long window together. 14.4× for an hour pages now; a slow 1× burn waits for the weekly review.

4.3 Burn Rate Alerting#

Traditional alerting (alert when error rate > X%) is noisy. Burn rate alerting is smarter:

Burn rate alerting
Traditional alert — "Alert if error rate > 0.1%"

Problem:

  • Fires on every tiny spike
  • Doesn't consider budget
  • No urgency context
Burn rate alert — "Alert if consuming budget 14.4x faster than normal for 1 hour"

Better because:

  • Considers SLO context
  • Predicts future impact
  • Different urgency levels
Burn rate alert thresholds (Google's recommendation)
Burn Rate Window Budget Consumed Action
14.4x 1 hour 2% in 1 hour PAGE immediately
6x 6 hours 5% in 6 hours PAGE during business hours
3x 1 day 10% in 1 day Create ticket
1x 3 days 10% in 3 days Review in weekly meeting

(Google's SRE Workbook lists three of these rows: 14.4x over 1 hour and 6x over 6 hours as pages, and 1x over 3 days as a ticket. The 3x/1-day row and the softer actions are this lesson's own additions. Source: sre.google/workbook/alerting-on-slos.)

Why these numbers?

  • 14.4x burn rate = 2% of a 30-day budget gone in 1 hour; the whole budget in 30 days / 14.4 ≈ 50 hours if sustained
  • 6x burn rate = 5% in 6 hours; the whole budget in ~5 days
  • You want to catch problems before they become SLO violations
ExplainerBreak it: error budget and burn rate

Sliders for the SLO target, incident minutes so far and the current error rate. Model: budget = (1 − SLO) × 30 days, incident minutes count in full, burn rate = error rate ÷ (1 − SLO). 99.9% allows 43.2 minutes: 25 minutes of incidents leave 18.2, 45 leave −1.8 (feature freeze), and 99.99% allows only about 4.3. At 1.44% errors a 99.9% SLO burns 14.4× (page now); 6× pages in business hours, 3× opens a ticket, 1× goes to the weekly review.

pythonBurnRateAlert · 98 lines
# error_budget/alerting.py

"""
Multi-window, multi-burn-rate alerting.
"""

from dataclasses import dataclass
from typing import List


@dataclass
class BurnRateAlert:
    """A burn rate alert configuration."""
    name: str
    burn_rate: float
    short_window: str
    long_window: str
    severity: str
    action: str


class BurnRateAlerting:
    """
    Implements multi-window burn rate alerting.
    """
    
    # Alert configurations
    ALERTS = [
        BurnRateAlert(
            name="budget_burn_critical",
            burn_rate=14.4,
            short_window="5m",
            long_window="1h",
            severity="critical",
            action="page"
        ),
        BurnRateAlert(
            name="budget_burn_high",
            burn_rate=6.0,
            short_window="30m",
            long_window="6h",
            severity="warning",
            action="page_business_hours"
        ),
        BurnRateAlert(
            name="budget_burn_medium",
            burn_rate=3.0,
            short_window="2h",
            long_window="1d",
            severity="info",
            action="ticket"
        ),
        BurnRateAlert(
            name="budget_burn_low",
            burn_rate=1.0,
            short_window="6h",
            long_window="3d",
            severity="info",
            action="review"
        ),
    ]
    
    def generate_prometheus_rules(
        self,
        slo_name: str,
        error_ratio_metric: str,
        target: float
    ) -> str:
        """
        Generate Prometheus alerting rules for an SLO.
        """
        error_budget_ratio = 1 - (target / 100)  # e.g., 0.001 for 99.9%
        
        rules = []
        
        for alert in self.ALERTS:
            # Multi-window condition: Both windows must exceed burn rate
            rule = f"""
- alert: {slo_name}_{alert.name}
  expr: |
    (
      avg_over_time({error_ratio_metric}[{alert.short_window}]) > {error_budget_ratio * alert.burn_rate}
      and
      avg_over_time({error_ratio_metric}[{alert.long_window}]) > {error_budget_ratio * alert.burn_rate}
    )
  for: 2m
  labels:
    severity: {alert.severity}
    slo: {slo_name}
  annotations:
    summary: "SLO {slo_name} burning error budget at {alert.burn_rate}x rate"
    description: "Error budget burn rate is {alert.burn_rate}x normal over {alert.long_window}"
    runbook: "https://wiki/runbooks/{slo_name}"
    action: "{alert.action}"
"""
            rules.append(rule)
        
        return "\n".join(rules)

Chapter 5: SLO-Based Decision Making#

5.1 Using SLOs for Prioritization#

SLOs drive decisions
Scenario 1: Feature vs Reliability
  • Product: "We need to ship feature X this sprint"
  • SRE: "Error budget is at 30%, one more incident and we're in freeze"

Decision framework:

  • Budget > 50%: Ship the feature
  • Budget 20-50%: Ship if low-risk, otherwise defer
  • Budget < 20%: Defer feature, focus on reliability
  • Budget exhausted: Feature freeze until recovered
Scenario 2: Technical Debt
  • Engineer: "We should refactor the authentication service"
  • Manager: "What's the business case?"

SLO answer:

  • "Auth service latency SLI is 95.2%, target is 99%"
  • "We're consistently missing SLO"
  • "Refactor should improve by ~3% based on profiling"
  • "This directly impacts our SLO compliance"
Scenario 3: On-Call Load
  • Team: "We're getting paged too much"
  • Manager: "Are these valid alerts?"

Analysis:

  • Calculate: What % of alerts were SLO-impacting?
  • If < 50% alerts were SLO-impacting: Alerts are noisy, tune them
  • If > 50%: Real reliability problem, invest in fixes
  • If 100% but meeting SLO: Good alerts, consider loosening SLO

5.2 Communicating SLOs to Stakeholders#

Translating SLOs for different audiences

For engineers

"Our p99 latency SLI is currently 185ms against a 200ms target. Error budget is at 62%. Burn rate is 0.8x."

For product managers

"We're meeting our reliability targets. We have room to take some risks with the new feature launch. We could absorb roughly a 25-minute outage this month and still hit our target."

For executives

"System reliability is GREEN. We're on track to meet our 99.9% internal availability target, well above the 99.5% we commit to customers. No risk to the enterprise SLA."

For customers

  • "Current system status: Operational.
  • This month's availability: 99.96%.
  • Committed availability: 99.5%.
  • All systems performing within normal parameters."
Quick check3 questions · +5 XP each

Error budgets and burn rate

  1. Q1/3 99.9% availability SLO over 30 days. This month you've had a 25-minute outage and 8 minutes of degradation. Where does the budget policy put you?

    Answer & explanation

    Answer: B

    The budget is 0.1% of 43,200 minutes = 43.2 minutes; 43.2 - 33 = 10.2 minutes, about 24% left. That's the 20-50% WARNING band (features continue, risky deploys stop); CRITICAL only starts below 20%. 33 minutes is what you consumed, not what remains.

  2. Q2/3 With a 99.9% SLO, a 3-minute blip pushes the error rate to 5%, then fully recovers. The critical rule needs a 14.4x burn rate over BOTH a 5-minute and a 1-hour window. Does it page?

    Answer & explanation

    Answer: D

    Over 1 hour the blip averages to about 0.25% errors (roughly 2.5x), well under 14.4x, so the "both windows" condition fails. That is the point of multi-window burn-rate alerting: short blips that barely dent the budget don't wake anyone up.

  3. Q3/3 Over the last 3 days your burn rate is exactly 1.0x. What does that mean?

    Answer & explanation

    Answer: C

    Burn rate is the actual error rate divided by the allowed one, so 1x means you would just meet the SLO by the end of the window. It isn't an emergency, which is why the lesson only reviews a 1x burn in the weekly meeting rather than paging.

Part III · Stage 3 of 4 · 3 min

Part III: Real-World Application#

Chapter 6: Case Studies#

6.1 How Google Uses SLOs#

Google's SLO practice
Scale
  • Thousands of services
  • Each service has SLOs
  • Error budgets tracked automatically
  • Policies enforced by tooling
Key practices
  1. User-centric SLIs
    • SLIs measured from user perspective
    • Not server metrics, but client experience
    • "What does the user see?" not "What does the server do?"
  2. SLO culture
    • Every service owner defines SLOs
    • SLOs reviewed quarterly
    • Missing SLO = reliability investment
    • Exceeding SLO = maybe too conservative
  3. Error budget policies
    • Written, agreed-upon policies
    • "If budget exhausted, then X happens"
    • Not just guidelines — enforced
    • Development velocity tied to reliability
  4. Gradual rollouts
    • Canary deployments standard
    • Automated rollback if SLI drops
    • Budget consumed = slower rollouts
    • Reliability gates on every deploy

Lesson: SLOs only work if you actually use them for decisions.

6.2 How Netflix Uses SLOs#

Netflix's approach
Context
  • Streaming service with millions of concurrent users
  • User experience is everything
  • "If it buffers, we lose them"

Illustrative: Netflix publicly discusses play starts and rebuffering as key quality-of-experience metrics; the targets and budget policy below are examples, not published Netflix numbers.

Key SLIs
  1. Availability
    • % of play requests that succeed
  2. Startup time
    • Time from pressing play to first frame
    • Target: < 3 seconds for most users
  3. Rebuffer rate
    • % of viewing time spent buffering
    • Target: < 0.1% rebuffer ratio
  4. Video quality
    • % of time delivering HD+ quality
    • Target varies by device/network
Error budget usage
  • HIGH BUDGET: Roll out new encoding algorithms
  • MEDIUM BUDGET: Standard deployments
  • LOW BUDGET: Only critical fixes
  • EXHAUSTED: All-hands reliability focus
Regional SLOs
  • Different targets for different regions
  • Emerging markets: Lower targets (network variability)
  • Developed markets: Higher targets (user expectations)
  • Allows appropriate investment per region

Lesson: SLOs should match user expectations, which vary by context.

Chapter 7: Common Mistakes#

✗ Pitfall

7.1 SLO Anti-Patterns#

SLO anti-patterns
Mistake 1: Too Many SLOs

Wrong:

  • API availability SLO
  • API latency p50 SLO
  • API latency p95 SLO
  • API latency p99 SLO
  • API latency p99.9 SLO
  • Database availability SLO
  • Database latency SLO
  • Cache hit rate SLO
  • ... 50 more SLOs

Problem:

  • Can't focus on what matters
  • Conflicting signals
  • Alert fatigue

Right:

  • 3-5 SLOs per service
  • Focus on user-facing impact
  • Internal metrics are monitoring, not SLOs
Mistake 2: SLOs That Don't Reflect Users

Wrong: SLO: "Server CPU utilization < 70%"

Problem:

  • CPU could be at 50% while users experience errors
  • Not measuring what users experience

Right: SLO: "99.9% of user requests succeed" — Supporting metric (not SLO): "CPU utilization"

Mistake 3: SLO = Current Performance

Wrong: "Our current p99 latency is 150ms, so our SLO should be 150ms"

Problem:

  • No room for normal variation
  • Constant SLO violations
  • Alert fatigue

Right: "Our current p99 is 150ms, so our SLO should be 200ms"

  • Gives headroom for normal variation
  • Alerts only on meaningful degradation
Mistake 4: No Error Budget Policy

Wrong: "We have SLOs, but when we miss them, nothing happens"

Problem:

  • SLOs become meaningless
  • No behavior change
  • Reliability never improves

Right: Written policy: "When error budget < 20%, feature development pauses until reliability work brings budget above 50%"

Mistake 5: SLOs Without Data

Wrong:

  • "Our SLO is 99.99% availability"
  • "How do you measure that?"
  • "We don't have monitoring for that yet"

Problem:

  • Can't know if you're meeting SLO
  • Can't calculate error budget
  • SLO is fiction

Right:

  1. First: Set up measurement
  2. Then: Observe current performance
  3. Then: Set SLO based on data
Quick check3 questions · +5 XP each

SLOs in practice

  1. Q1/3 A team proposes the SLO "Server CPU utilization < 70%". What's wrong with it?

    Answer & explanation

    Answer: A

    SLOs should reflect what users experience, such as "99.9% of user requests succeed". CPU can look healthy while requests fail; keep it as a supporting metric, not an SLO.

  2. Q2/3 Current p99 latency is 150ms, so the team sets the latency SLO at exactly 150ms. What happens next?

    Answer & explanation

    Answer: D

    An SLO at current performance leaves no headroom, so ordinary day-to-day variation shows up as violations. Setting it at, say, 200ms leaves room for noise and only flags meaningful degradation.

  3. Q3/3 Your team has missed its SLO three months running, yet feature work carries on as usual. Which anti-pattern is this?

    Answer & explanation

    Answer: C

    Without a written, enforced policy (e.g. pause features below 20% budget), missing the SLO changes no behaviour and reliability never improves. Loosening is only right if users are still happy; otherwise it hides the problem. As Google's lesson puts it, SLOs only work if you use them for decisions.

Part IV · Stage 4 of 4 · 16 min

Part IV: Interview Preparation#

Chapter 8: Interview Tips#

8.1 SLO Discussion Framework#

Discussing SLOs in interviews

When asked "How would you ensure this system is reliable?":

  1. Start with user impact — "First, I'd identify what reliability means to users. For a payment system, that's successful transactions and acceptable latency. For a notification system, it's delivery rate and timeliness."
  2. Define specific SLIs — "I'd measure:
    • Availability: Successful requests / total requests
    • Latency: p99 response time
    • Correctness: For payments, transactions that complete correctly"
  3. Set targets with rationale — "For a payment system, I'd target 99.99% correctness — incorrect charges are unacceptable. For availability, 99.9% might be appropriate, giving us about 8.8 hours of error budget per year."
  4. Explain error budgets — "The error budget lets us balance reliability and velocity. With 99.9% availability SLO, we have 43 minutes per month of downtime budget. If we're burning it fast, we slow down on features and focus on reliability."
  5. Connect to alerting — "I'd alert on burn rate rather than raw errors. If we're consuming budget 14.4x faster than normal for an hour, that's worth a page even if we haven't violated the SLO yet."
❯ Say this

8.2 Key Phrases#

SLO key phrases
On Defining Reliability

"I define SLIs based on user experience, not server metrics. What the user sees matters more than server CPU. For an API, that's success rate and latency measured at the load balancer."

On Setting Targets

"I set SLOs tighter than what I'd put in an SLA. If my SLA promises 99.5%, my SLO is 99.9%. This gives us buffer to catch and fix issues before customers are impacted enough to trigger SLA penalties."

On Error Budgets

"Error budgets turn reliability into a feature trade-off. When budget is healthy, we ship fast. When it's depleted, we focus on reliability. This prevents both over-engineering reliability and neglecting it."

On Alerting

"I use burn rate alerting instead of threshold alerting. A 1% error rate for 1 minute might be noise. But burning 2% of a month's error budget in one hour, a pace that empties it in about two days? That's real. Multi-window burn rate catches real problems, not noise."

Chapter 9: Practice Problems#

Practice · problem 1

Problem 1: E-Commerce Checkout#

Scenario: You're defining SLOs for an e-commerce checkout service.

Questions:

  1. What SLIs would you measure?
  2. What targets would you set?
  3. How would you handle dependency failures (payment processor)?
Click for hints
  • SLIs: Checkout success rate, latency, payment accuracy
  • Consider: Different targets for browsing vs checkout (checkout more critical)
  • Dependency: Your SLO can't exceed payment processor's SLA (unless you can fail over to a second processor)
  • Error budget: Very small for payment correctness

Practice · problem 2

Problem 2: Real-Time Chat Application#

Scenario: You're defining SLOs for a Slack-like chat application.

Questions:

  1. What makes "reliability" different for real-time chat?
  2. How would you handle message delivery guarantees?
  3. What's an appropriate latency target?
Click for hints
  • Real-time = latency matters more than typical APIs
  • Message delivery: Different SLOs for "accepted" vs "delivered"
  • Latency: Users notice > 200ms in chat
  • Consider: Read vs write latency (writes more critical)

Chapter 10: Sample Interview Dialogue#

Transcript
Interviewer

"You've designed a notification system. How do you know if it's reliable?"

You

"Great question. Reliability for a notification system has several dimensions. Let me define specific SLOs.

First, the SLIs I'd measure:"

  1. Availability: % of send requests that succeed (non-5xx)
  2. Acceptance latency: Time to accept and queue a notification
  3. Delivery latency: Time from acceptance to actual delivery
  4. Delivery rate: % of accepted notifications actually delivered
Transcript
Interviewer

"How do you set the targets?"

You

"I'd base targets on user expectations and business needs:

For availability, I'd target 99.9%. Notifications are important but not mission-critical like payments. 99.9% gives us about 43 minutes of downtime budget per month.

For acceptance latency, I'd target p99 < 200ms. The API should feel instant.

For delivery latency, I'd target 95% delivered within 30 seconds. This accounts for third-party provider variability while meeting user expectations for timeliness.

For delivery rate, I'd target 99.5%. Some delivery failures are expected (invalid tokens, user blocked notifications), but we should successfully deliver almost everything.

Transcript
Interviewer

"What happens if you miss the SLO?"

You

"That's where error budgets come in. Let me explain our policy:

If we're above 50% budget, we operate normally — ship features, run experiments.

At 20-50% budget, we increase caution — no risky deployments, lower rollback thresholds.

At below 20%, we enter reliability focus — pause feature work, all engineering on reliability.

At budget exhausted, we're in freeze mode — only critical fixes, all hands on improving reliability.

This creates a self-balancing system. When we're reliable, we ship fast. When we're not, we automatically shift focus."

Interviewer

"How do you alert on this?"

You

"I use burn rate alerting instead of raw thresholds.

A 0.5% error rate for 5 minutes might be noise from a network blip. But if I'm consuming my monthly error budget 14.4x faster than normal for an hour, that's a real problem.

I'd set up multi-window alerts:

  • 14.4x burn rate for 1 hour → page immediately
  • 6x burn rate for 6 hours → page during business hours
  • 3x burn rate for 1 day → create a ticket

This catches real issues while avoiding alert fatigue."

Summary#

Day 1 key takeaways
The language of reliability
  • SLI: What we measure (the metric)
  • SLO: What we target (internal goal)
  • SLA: What we promise (external contract)
  • Error Budget: How much failure we allow
Good SLIs
  • Measure user experience, not server health
  • Four golden signals: Latency, Traffic, Errors, Saturation
  • Precisely specified (good events / valid events)
  • Measured from user's perspective (load balancer, not server)
Setting SLOs
  • Based on user expectations, not current performance
  • 3-5 SLOs per service (not 50)
  • Leave headroom (don't set at current performance)
  • Review and adjust quarterly
Error budgets
  • Balance reliability and velocity
  • Written policies for budget states
  • Budget exhausted = feature freeze
  • Burn rate alerting > threshold alerting
Key insight

100% reliability is impossible, infinitely expensive, and harmful. The goal is APPROPRIATE reliability — reliable enough for users, with enough room to ship features and innovate.

Chapter 11: Implementing SLO Dashboards#

11.1 The SLO Dashboard#

Every team needs visibility into their SLO status:

pythonSLOStatus · 199 lines
# slo/dashboard.py

"""
SLO Dashboard data provider.

Provides all the data needed for an SLO status dashboard.
"""

from dataclasses import dataclass
from typing import List, Dict, Optional
from datetime import datetime, timedelta
from enum import Enum


@dataclass
class SLOStatus:
    """Current status of an SLO."""
    name: str
    description: str
    target: float
    current_value: float
    window_days: int
    
    # Budget state
    budget_total_minutes: float
    budget_consumed_minutes: float
    budget_remaining_minutes: float
    budget_remaining_percentage: float
    
    # Burn rate
    burn_rate_1h: float
    burn_rate_6h: float
    burn_rate_24h: float
    
    # Trend
    trend_direction: str  # "improving", "stable", "degrading"
    projected_end_of_window: float  # Projected SLI at window end
    
    # Status
    status: str  # "healthy", "warning", "critical", "violated"
    status_changed_at: Optional[datetime]


class SLODashboardService:
    """
    Provides data for SLO dashboards.
    """
    
    def __init__(self, metrics_client, slo_config):
        self.metrics = metrics_client
        self.config = slo_config
    
    async def get_all_slo_status(self) -> List[SLOStatus]:
        """Get status of all configured SLOs."""
        statuses = []
        
        for slo in self.config.slos:
            status = await self.get_slo_status(slo)
            statuses.append(status)
        
        return statuses
    
    async def get_slo_status(self, slo_config) -> SLOStatus:
        """Get detailed status for a single SLO."""
        
        # Query current SLI value
        current_value = await self.metrics.query(
            slo_config.sli_query,
            window=f"{slo_config.window_days}d"
        )
        
        # Calculate error budget
        target = slo_config.target
        error_rate_allowed = 100 - target
        error_rate_actual = 100 - current_value
        
        budget_total = (error_rate_allowed / 100) * slo_config.window_days * 24 * 60
        budget_consumed = (error_rate_actual / 100) * slo_config.window_days * 24 * 60
        budget_remaining = max(0, budget_total - budget_consumed)
        budget_remaining_pct = (budget_remaining / budget_total) * 100 if budget_total > 0 else 0
        
        # Calculate burn rates at different windows
        burn_rate_1h = await self._calculate_burn_rate(slo_config, "1h")
        burn_rate_6h = await self._calculate_burn_rate(slo_config, "6h")
        burn_rate_24h = await self._calculate_burn_rate(slo_config, "24h")
        
        # Determine trend
        trend = self._calculate_trend(burn_rate_1h, burn_rate_6h, burn_rate_24h)
        
        # Project end-of-window value
        projected = self._project_eow_value(current_value, burn_rate_24h, slo_config)
        
        # Determine status
        status = self._determine_status(budget_remaining_pct, burn_rate_1h)
        
        return SLOStatus(
            name=slo_config.name,
            description=slo_config.description,
            target=target,
            current_value=current_value,
            window_days=slo_config.window_days,
            budget_total_minutes=budget_total,
            budget_consumed_minutes=budget_consumed,
            budget_remaining_minutes=budget_remaining,
            budget_remaining_percentage=budget_remaining_pct,
            burn_rate_1h=burn_rate_1h,
            burn_rate_6h=burn_rate_6h,
            burn_rate_24h=burn_rate_24h,
            trend_direction=trend,
            projected_end_of_window=projected,
            status=status,
            status_changed_at=None  # Would track in database
        )
    
    async def _calculate_burn_rate(self, slo_config, window: str) -> float:
        """Calculate burn rate for a time window."""
        value = await self.metrics.query(slo_config.sli_query, window=window)
        
        error_rate_allowed = 100 - slo_config.target
        error_rate_actual = 100 - value
        
        if error_rate_allowed == 0:
            return 0
        
        return error_rate_actual / error_rate_allowed
    
    def _calculate_trend(
        self, 
        burn_1h: float, 
        burn_6h: float, 
        burn_24h: float
    ) -> str:
        """Determine trend from burn rates."""
        # If recent burn rate is lower than longer-term, improving
        if burn_1h < burn_6h < burn_24h:
            return "improving"
        elif burn_1h > burn_6h > burn_24h:
            return "degrading"
        else:
            return "stable"
    
    def _project_eow_value(
        self,
        current: float,
        burn_rate_24h: float,
        slo_config
    ) -> float:
        """Project SLI value at end of window (simplified)."""
        # Assume we're mid-window: the elapsed half ran at the current SLI,
        # the remaining half runs at the 24h burn rate. Burn rate b means
        # an error rate of b x the allowed error rate.
        allowed_error = 100 - slo_config.target
        future_error = burn_rate_24h * allowed_error
        projected_error = 0.5 * (100 - current) + 0.5 * future_error
        
        return max(0, 100 - projected_error)
    
    def _determine_status(
        self,
        budget_remaining_pct: float,
        burn_rate_1h: float
    ) -> str:
        """Determine SLO status."""
        # Violated if budget exhausted
        if budget_remaining_pct <= 0:
            return "violated"
        
        # Critical if budget very low OR burning very fast
        if budget_remaining_pct < 20 or burn_rate_1h > 10:
            return "critical"
        
        # Warning if budget getting low OR elevated burn rate
        if budget_remaining_pct < 50 or burn_rate_1h > 5:
            return "warning"
        
        return "healthy"
    
    async def get_slo_history(
        self,
        slo_name: str,
        days: int = 30
    ) -> List[Dict]:
        """Get historical SLO performance."""
        # Query historical data points
        data_points = await self.metrics.query_range(
            self.config.get_slo(slo_name).sli_query,
            start=f"-{days}d",
            end="now",
            step="1h"
        )
        
        return [
            {
                "timestamp": dp.timestamp,
                "value": dp.value,
                "target": self.config.get_slo(slo_name).target
            }
            for dp in data_points
        ]

11.2 Grafana Dashboard Definition#

json117 lines
{
  "dashboard": {
    "title": "Service SLO Dashboard",
    "panels": [
      {
        "title": "SLO Status Overview",
        "type": "stat",
        "gridPos": {"x": 0, "y": 0, "w": 24, "h": 4},
        "targets": [
          {
            "expr": "slo:current_value{service=\"api\"}",
            "legendFormat": "{{slo_name}}"
          }
        ],
        "options": {
          "colorMode": "background",
          "graphMode": "none",
          "justifyMode": "center",
          "textMode": "value_and_name"
        },
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                {"color": "red", "value": null},
                {"color": "red", "value": 99},
                {"color": "yellow", "value": 99.5},
                {"color": "green", "value": 99.9}
              ]
            },
            "unit": "percent"
          }
        }
      },
      {
        "title": "Error Budget Remaining",
        "type": "gauge",
        "gridPos": {"x": 0, "y": 4, "w": 8, "h": 6},
        "targets": [
          {
            "expr": "slo:error_budget_remaining_ratio{service=\"api\"} * 100"
          }
        ],
        "options": {
          "showThresholdLabels": true,
          "showThresholdMarkers": true
        },
        "fieldConfig": {
          "defaults": {
            "min": 0,
            "max": 100,
            "thresholds": {
              "steps": [
                {"color": "red", "value": 0},
                {"color": "orange", "value": 20},
                {"color": "yellow", "value": 50},
                {"color": "green", "value": 75}
              ]
            },
            "unit": "percent"
          }
        }
      },
      {
        "title": "Burn Rate (1h, 6h, 24h)",
        "type": "timeseries",
        "gridPos": {"x": 8, "y": 4, "w": 16, "h": 6},
        "targets": [
          {
            "expr": "slo:burn_rate_1h{service=\"api\"}",
            "legendFormat": "1h burn rate"
          },
          {
            "expr": "slo:burn_rate_6h{service=\"api\"}",
            "legendFormat": "6h burn rate"
          },
          {
            "expr": "slo:burn_rate_24h{service=\"api\"}",
            "legendFormat": "24h burn rate"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "custom": {
              "thresholdsStyle": {
                "mode": "line"
              }
            },
            "thresholds": {
              "steps": [
                {"color": "green", "value": null},
                {"color": "yellow", "value": 1},
                {"color": "orange", "value": 6},
                {"color": "red", "value": 14.4}
              ]
            }
          }
        }
      },
      {
        "title": "SLI Over Time",
        "type": "timeseries",
        "gridPos": {"x": 0, "y": 10, "w": 24, "h": 8},
        "targets": [
          {
            "expr": "slo:sli_value{service=\"api\"}",
            "legendFormat": "{{slo_name}}"
          },
          {
            "expr": "slo:target{service=\"api\"}",
            "legendFormat": "Target"
          }
        ]
      }
    ]
  }
}
Your turn · checkpoint 5not answered

Can you promise more than your dependencies?

Your service promises 99.9%. It calls a database (99.99%) and a payment provider (99.95%), which calls a bank API (99.9%), and every call is synchronous. Treating each as an independent link in a chain, what's the best availability you can reach, and what would let you keep 99.9%?

Think it through, or jot it down. Then compare.

One strong answer

  • Multiply them: 99.99% × 99.95% × 99.99% × 99.9% ≈ 99.83%, below your 99.9% before your own code fails once.
  • You can't beat your critical dependency chain while every call is a hard, synchronous dependency.
  • To keep 99.9%, break the hard dependency: queue payments while the provider is down, or fail over to a second provider. Otherwise, lower the SLO.

Chapter 12: SLOs Across the Organization#

12.1 Service Dependency SLOs#

When your service depends on others, how do you set SLOs?

DiagramService dependency chain
%% title: Service dependency chain
flowchart TD
  Y["Your Service<br/>SLO: 99.9% availability"]:::core
  Y --> DB@{ icon: "sd:database", label: "Database<br/>SLO: 99.99%" }
  Y --> PP@{ icon: "sd:card", label: "Payment Provider<br/>SLA: 99.95%" }
  DB --> ST@{ icon: "sd:object-storage", label: "Storage<br/>SLA: 99.99%" }
  PP --> BA@{ icon: "sd:bank", label: "Bank API<br/>SLA: 99.9%" }
  class DB,ST store
  class PP,BA external
Your maximum achievable availability

99.99% × 99.95% × 99.99% × 99.9% = 99.83% (theoretical max)

(This treats every box as an independent serial dependency. If the database's 99.99% already includes its storage, and the provider's 99.95% its bank, the bound is less pessimistic; check what each number covers.)

LESSON: You cannot be more reliable than your least reliable critical dependency!

Solutions
  1. Only depend on services with SLOs >= your SLO
  2. Build redundancy (multiple payment providers)
  3. Graceful degradation (queue payments if provider down)
  4. Adjust your SLO to reflect dependencies

12.2 Tiered SLOs#

Different customers may have different SLOs:

pythonCustomerTier · 145 lines
# slo/tiered.py

"""
Tiered SLO management for different customer segments.
"""

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Dict
from enum import Enum


class CustomerTier(Enum):
    """Customer tier levels."""
    FREE = "free"
    STANDARD = "standard"
    PROFESSIONAL = "professional"
    ENTERPRISE = "enterprise"


@dataclass
class TieredSLO:
    """SLO with different targets per tier."""
    sli_name: str
    description: str
    
    # Targets by tier
    targets: Dict[CustomerTier, float]
    
    # Whether to enforce (vs aspirational)
    enforced: bool = True


# Example tiered SLOs for our notification platform
NOTIFICATION_SLOS = [
    TieredSLO(
        sli_name="api_availability",
        description="API request success rate",
        targets={
            CustomerTier.FREE: 99.0,        # 7.2 hours/month downtime OK
            CustomerTier.STANDARD: 99.5,    # 3.6 hours/month
            CustomerTier.PROFESSIONAL: 99.9, # 43 minutes/month
            CustomerTier.ENTERPRISE: 99.99,  # 4.3 minutes/month
        }
    ),
    TieredSLO(
        sli_name="api_latency_p99",
        description="99th percentile API latency",
        targets={
            CustomerTier.FREE: 1000,         # 1 second
            CustomerTier.STANDARD: 500,      # 500ms
            CustomerTier.PROFESSIONAL: 200,  # 200ms
            CustomerTier.ENTERPRISE: 100,    # 100ms
        }
    ),
    TieredSLO(
        sli_name="delivery_rate",
        description="Notification delivery success rate",
        targets={
            CustomerTier.FREE: 95.0,
            CustomerTier.STANDARD: 98.0,
            CustomerTier.PROFESSIONAL: 99.0,
            CustomerTier.ENTERPRISE: 99.9,
        }
    ),
]


class TieredSLOService:
    """
    Manages tiered SLOs across customer segments.
    """
    
    def __init__(self, metrics_client, customer_service):
        self.metrics = metrics_client
        self.customers = customer_service
    
    async def check_customer_slo(
        self,
        customer_id: str,
        slo_name: str
    ) -> Dict:
        """Check SLO status for a specific customer."""
        
        # Get customer tier
        customer = await self.customers.get(customer_id)
        tier = CustomerTier(customer.tier)
        
        # Find SLO config
        slo_config = next(s for s in NOTIFICATION_SLOS if s.sli_name == slo_name)
        target = slo_config.targets[tier]
        
        # Query SLI for this customer
        current_value = await self.metrics.query(
            f'sli:{slo_name}{{customer_id="{customer_id}"}}',
            window="30d"
        )
        
        return {
            "customer_id": customer_id,
            "tier": tier.value,
            "slo_name": slo_name,
            "target": target,
            "current_value": current_value,
            "meeting_slo": current_value >= target,
            "gap": current_value - target
        }
    
    async def get_slo_compliance_report(self) -> Dict:
        """Generate compliance report across all tiers."""
        report = {
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "by_tier": {},
            "by_slo": {}
        }
        
        for tier in CustomerTier:
            tier_customers = await self.customers.get_by_tier(tier.value)
            
            compliance = {
                "total_customers": len(tier_customers),
                "meeting_all_slos": 0,
                "violating_slos": 0
            }
            
            for customer in tier_customers:
                meeting_all = True
                
                for slo in NOTIFICATION_SLOS:
                    result = await self.check_customer_slo(
                        customer.id, 
                        slo.sli_name
                    )
                    if not result["meeting_slo"]:
                        meeting_all = False
                        break
                
                if meeting_all:
                    compliance["meeting_all_slos"] += 1
                else:
                    compliance["violating_slos"] += 1
            
            report["by_tier"][tier.value] = compliance
        
        return report

12.3 SLO Review Process#

Quarterly SLO review process
  1. Week 1: Data collection
    • Pull last quarter's SLI data
    • Calculate SLO compliance rate
    • Identify patterns in violations
    • Gather customer feedback
  2. Week 2: Analysis
    • Were SLOs too tight? (Constantly missing)
    • Were SLOs too loose? (Always met, but users unhappy)
    • Did dependencies impact our SLOs?
    • What incidents consumed error budget?
  3. Week 3: Proposal
    • Propose adjustments to targets
    • Propose new SLOs if gaps identified
    • Propose deprecating unused SLOs
    • Document rationale for changes
  4. Week 4: Review & approval
    • Review with stakeholders (product, engineering, support)
    • Align on changes
    • Update documentation
    • Communicate changes to team
Key questions to ask
  • Did we miss SLO? Why? Was it preventable?
  • Did we meet SLO but users were unhappy? Target too loose?
  • Did we exceed SLO by large margin? Over-invested in reliability?
  • Did error budget policy work? Did we actually freeze?
  • Are our SLIs still measuring the right things?
Quick check3 questions · +5 XP each

SLOs across the organization

  1. Q1/3 Your service has a 99.9% SLO, but its critical dependencies multiply out to a theoretical max of 99.83%. Which change lets you honestly keep 99.9%?

    Answer & explanation

    Answer: B

    You can't be more reliable than your critical dependency chain, so you must break the hard dependency: graceful degradation (or a second provider) stops a provider outage from failing your request. More replicas of your own service don't change the dependency math, and moving the measurement point only hides user pain.

  2. Q2/3 In the quarterly review you met the latency SLO all quarter, but support tickets about slowness rose sharply. What's the right conclusion?

    Answer & explanation

    Answer: D

    The review explicitly asks "Did we meet SLO but users were unhappy? Target too loose?" and "Are our SLIs still measuring the right things?" Piling on more SLOs is the "too many SLOs" anti-pattern; the fix is to tighten the target or correct the SLI.

  3. Q3/3 The SLO dashboard shows 60% of the error budget remaining, yet marks the SLO "critical". Why?

    Answer & explanation

    Answer: A

    Status combines remaining budget and the 1-hour burn rate: critical if budget is under 20% OR burn rate exceeds 10x. Plenty of budget doesn't help if it's being spent fast enough to empty it soon. The 75% mark is only a gauge colour threshold.

Chapter 13: Your Journey So Far#

As we begin this final week, let's reflect on how far you've come:

Your system design evolution
  • Week 1 —
    You learned WHY systems fail at scale
    • Hot keys, replication lag, partition strategies
    • Now you know: Distribution is hard, but necessary
  • Week 2 —
    You learned HOW to handle failures
    • Timeouts, retries, idempotency, circuit breakers
    • Now you know: Assume failure, design for recovery
  • Week 3 —
    You learned ASYNC patterns
    • Queues, streams, backpressure, dead letters
    • Now you know: Not everything needs to be synchronous
  • Week 4 —
    You learned CACHING deeply
    • Invalidation, thundering herds, consistency
    • Now you know: Caching is easy to add, hard to get right
  • Week 5 —
    You learned COORDINATION
    • Consensus, sagas, conflict resolution
    • Now you know: Distributed transactions are expensive
  • Week 6-8 —
    You DESIGNED complete systems
    • Notifications, Search, Analytics
    • Now you know: Real systems combine many patterns
  • Week 9 —
    You learned ENTERPRISE concerns
    • Multi-tenancy, compliance, security
    • Now you know: Business requirements shape architecture
  • Week 10 (NOW) —
    You're learning to OPERATE systems
    • SLOs tell you if your system is healthy
    • After this week: You'll own systems, not just build them

This week is the capstone of your entire journey. Everything you've learned comes together when you operate systems in production. SLOs are how you know if all that careful design is actually working.

Further Reading#

Books:

  • Book

    "Site Reliability Engineering" by Google — Chapters on SLIs/SLOs/SLAs

  • Read

    "The Site Reliability Workbook" — Practical SLO implementation

  • Book

    "Implementing Service Level Objectives" by Alex Hidalgo

Articles:

Tools:

  • Read

    Prometheus + Grafana for SLI measurement

  • Read

    Sloth — SLO generator for Prometheus

  • Read

    Nobl9 — SLO management platform

  • Read

    Google Cloud SLO Monitoring

You're in the final week. You've learned to build systems. Now you're learning to own them. This is what makes a senior engineer.

this lesson in shorts · 3

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Day 01 · SLIs, SLOs, and SLAs
Finish Day 1Stages clear as you read them (0/4 so far). Skipping ahead doesn't count: marking complete unlocks after ~7 more min of reading.

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

GoogleDocs · ~18 min

Service Level Objectives

Chris Jones, John Wilkes et al. · 2016
What
The SRE book chapter that defined SLIs, SLOs and SLAs for most of the industry.
Key idea
Pick indicators users actually feel, set targets below 100%, and keep SLAs (contracts with penalties) looser than internal SLOs.
Why here
The vocabulary of this lesson at its source, including how to choose good SLIs.
taught here: SLIs, SLOs and SLAs
GoogleDocs · ~38 min

Implementing SLOs

Steven Thurgood, David Ferguson et al. · 2018
What
A practical guide to rolling out SLOs, with a worked example service.
Key idea
Measure SLIs where users experience them, agree an error budget policy in advance, and use the budget to decide between reliability work and features.
Why here
The error budget and SLO-based decision-making chapters of this lesson, step by step.
taught here: Error budgets
GoogleDocs · ~21 min

Alerting on SLOs

Steven Thurgood, Jess Frame et al. · 2018
What
The workbook chapter on turning SLOs into pages.
Key idea
Alert on how fast the error budget is burning, over multiple windows, so fast outages page quickly and slow leaks still get caught without noise.
Why here
The step after setting SLOs in this lesson: burn-rate alerting is how an SLO reaches an on-call engineer.
taught here: Burn rate alerts

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…