Preface#
You've come a long way.
- 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:
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.
"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: Foundations#
Chapter 1: The Language of Reliability#
1.1 Why We Need Precise Language#
Imagine this conversation:
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#
%% 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:
| 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:
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
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?
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 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#
- 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:
- 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?
- 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
- 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)
- 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
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?
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:
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#
- 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
SLIs, SLOs and SLAs
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.
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.
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: Implementation#
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?
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:
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
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.
| 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?
# 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
)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?
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:
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
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.
# 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#
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#
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."
Error budgets and burn rate
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.
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.
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: Real-World Application#
Chapter 6: Case Studies#
6.1 How Google Uses SLOs#
Scale
- Thousands of services
- Each service has SLOs
- Error budgets tracked automatically
- Policies enforced by tooling
Key practices
- 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?"
- SLO culture
- Every service owner defines SLOs
- SLOs reviewed quarterly
- Missing SLO = reliability investment
- Exceeding SLO = maybe too conservative
- Error budget policies
- Written, agreed-upon policies
- "If budget exhausted, then X happens"
- Not just guidelines — enforced
- Development velocity tied to reliability
- 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#
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
- Availability
- % of play requests that succeed
- Startup time
- Time from pressing play to first frame
- Target: < 3 seconds for most users
- Rebuffer rate
- % of viewing time spent buffering
- Target: < 0.1% rebuffer ratio
- 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#
7.1 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:
- First: Set up measurement
- Then: Observe current performance
- Then: Set SLO based on data
SLOs in practice
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.
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.
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: Interview Preparation#
Chapter 8: Interview Tips#
8.1 SLO Discussion Framework#
When asked "How would you ensure this system is reliable?":
- 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."
- Define specific SLIs — "I'd measure:
- Availability: Successful requests / total requests
- Latency: p99 response time
- Correctness: For payments, transactions that complete correctly"
- 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."
- 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."
- 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."
8.2 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:
- What SLIs would you measure?
- What targets would you set?
- 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:
- What makes "reliability" different for real-time chat?
- How would you handle message delivery guarantees?
- 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#
"You've designed a notification system. How do you know if it's reliable?"
"Great question. Reliability for a notification system has several dimensions. Let me define specific SLOs.
First, the SLIs I'd measure:"
- Availability: % of send requests that succeed (non-5xx)
- Acceptance latency: Time to accept and queue a notification
- Delivery latency: Time from acceptance to actual delivery
- Delivery rate: % of accepted notifications actually delivered
"How do you set the targets?"
"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.
"What happens if you miss the SLO?"
"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."
"How do you alert on this?"
"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#
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:
# 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#
{
"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"
}
]
}
]
}
}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%?
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?
%% 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 external99.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
- Only depend on services with SLOs >= your SLO
- Build redundancy (multiple payment providers)
- Graceful degradation (queue payments if provider down)
- Adjust your SLO to reflect dependencies
12.2 Tiered SLOs#
Different customers may have different SLOs:
# 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 report12.3 SLO Review Process#
- Week 1: Data collection
- Pull last quarter's SLI data
- Calculate SLO compliance rate
- Identify patterns in violations
- Gather customer feedback
- 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?
- Week 3: Proposal
- Propose adjustments to targets
- Propose new SLOs if gaps identified
- Propose deprecating unused SLOs
- Document rationale for changes
- 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?
SLOs across the organization
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.
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.
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:
- 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:
- Book
Google Cloud: "Setting SLOs: a step-by-step guide" — https://cloud.google.com/blog/products/management-tools/practical-guide-to-setting-slos
- Blog
Honeycomb: "SLOs Are the API for Your Engineering Team"
- Read
The New Stack: "The Fundamentals of SLOs"
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.
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.
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.
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.
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.
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.


