A few years back, we pushed a slick new image optimization feature to our edge fleet on a Friday afternoon — you can guess how this ends. Within an hour, cache hit ratios tanked in Singapore and Frankfurt, origin traffic spiked 300%, and we'd burned through nearly three weeks of error budget before anyone could roll it back. That's when I finally understood that on a CDN, an error budget isn't just an abstract SRE number. It's the only thing standing between shipping fast and taking down half the internet.
Why Error Budgets Hit Harder on a Global Edge Network
On a typical backend service, you've got one cluster and a relatively predictable failure mode. On a CDN, you've got 200+ PoPs, three tiers of cache, shield layers, and origin dependencies that all fail in different, creative ways. Your 99.9% availability SLO doesn't mean 43 minutes of downtime per month for one server — it means any combination of regional brownouts, cache stampedes, or bad config pushes that collectively exceed that window counts against you. And trust me, they add up quicker than your dashboard suggests.
I learned this the hard way back in 2018 when we were running a 99.95% SLO for edge request success. We thought we had plenty of breathing room. Then a routine TLS certificate rotation script failed silently on about 15% of our edge nodes. It didn't trigger a full outage, just a 0.8% increase in 502s for clients still negotiating with those nodes. Because it was so diffuse, our legacy alerting didn't fire for 90 minutes. By the time we caught it, we'd consumed 60% of our monthly budget on what looked like background noise. If you haven't read up on SRE Practices for CDN: Applying Site Reliability Engineering to Content Delivery, that's the foundation — you can't balance reliability and feature velocity if you're measuring reliability wrong in the first place.
The math itself is simple, but its implications for release policy aren't. For a 99.9% SLO over a 30-day window, your total error budget is just 0.1% of all valid requests. That sounds tiny until you translate it into time or failed requests. The key is to calculate it continuously and make it visible to everyone shipping code, not just the SRE team. Here's a stripped-down version of how we compute remaining budget in our pipeline controller — it blocks or throttles feature rollouts automatically when the budget gets low.
# error_budget.py - simplified CDN error budget calculator
# Used in CI/CD gate to decide if a feature can proceed
import time
def calculate_error_budget(slo_target, total_requests, failed_requests):
"""
slo_target: e.g., 0.999 for 99.9%
total_requests: valid requests in window (30 days)
failed_requests: requests counted as errors (5xx, timeout, etc.)
"""
allowed_errors = total_requests * (1 - slo_target)
remaining = allowed_errors - failed_requests
burn_pct = (failed_requests / allowed_errors) * 100 if allowed_errors > 0 else 0
# Policy gates tied directly to burn rate
if burn_pct >= 100:
return {"status": "FREEZE", "remaining": 0, "action": "Halt all non-emergency deploys"}
elif burn_pct >= 75:
return {"status": "CAUTION", "remaining": remaining, "action": "Require SRE approval for releases"}
else:
return {"status": "OK", "remaining": remaining, "action": "Normal velocity"}
# Example: 500M requests/month, 99.9% SLO
result = calculate_error_budget(0.999, 500_000_000, 380_000)
print(result)
# {'status': 'CAUTION', 'remaining': 120000.0, 'action': 'Require SRE approval for releases'}
Crafting SLOs That Don't Choke Feature Velocity
There's a temptation to set absurdly tight SLOs because CDN marketing loves nines. I've seen teams insist on 99.99% edge availability and p95 latency under 50ms globally. That's a noble goal, but if your error budget is razor-thin, you'll spend all your time defending it and never ship anything. Your developers will get frustrated, they'll start finding workarounds to bypass the freeze gates, and your reliability balance falls apart. The point of an error budget isn't to achieve perfection — it's to make the trade-off between reliability and velocity explicit and negotiable.
What works better is splitting your SLOs by what users actually feel. We run four primary SLOs now: edge availability (did the edge return a valid HTTP response), origin offload success (could we serve from cache or shield without hammering origin), p95 time-to-first-byte for cache hits, and config propagation time. Each has its own budget. That way, a team working on a new WAF rule that might affect availability doesn't get blocked because the video optimization team burned the latency budget. It isolates blast radius, which is critical when you've got ten feature teams deploying to the same fleet. The folks at web.dev Articles have great data on how users perceive performance thresholds, and the HTTP Working Group Specifications are essential for defining what counts as a valid error versus, say, a correctly returned 404 that shouldn't touch your budget.
Here's how we define those SLOs as code so there's no ambiguity. This lives in our SRE repo and is the source of truth for both monitoring and deployment gates. Notice we explicitly exclude client-cancelled requests and 4xx errors that aren't our fault — if you don't, your budget will be noise.
# slos.yaml - CDN SLO definitions as code
# Aligned with SLI definitions from SRE practices
slos:
- name: edge-availability
description: "Proportion of valid requests that receive any 2xx/3xx/4xx from edge (excluding 5xx)"
sli: 'sum(rate(cdn_edge_requests_total{status!~"5.."}[5m])) / sum(rate(cdn_edge_requests_total[5m]))'
target: 0.999 # 99.9% over 28-day rolling window
window: 28d
error_budget_policy:
burn_rate_alert: 14.4 # 14.4x burns budget in ~2 hours
freeze_threshold: 80% # freeze feature deploys at 80% consumed
- name: cache-hit-latency-p95
description: "p95 TTFB for cache HITs must be under 80ms"
sli: 'histogram_quantile(0.95, sum(rate(cdn_ttfb_bucket{cache_status="HIT"}[5m])) by (le))'
target: 0.95 # 95% of HITs under threshold in window
threshold: 80ms
window: 28d
# Automation that respects budgets: see notes on toil vs. safety
# https://example.internal/docs/sre-error-budgets
Tracking Burn Rate When Every Second Costs You
Knowing you've got 20% of your budget left is useful. Knowing you're burning it at 10x the normal rate right now is what actually saves you. Burn rate is just how fast you're consuming budget compared to the steady-state rate. A 1x burn means you'd exactly exhaust your budget by the end of the window if you kept going. A 20x burn means you'll be out in about 36 hours on a 30-day window. On a CDN, burn rate spikes are common during PoP-level incidents, and they’re the signal you should use to auto-throttle traffic, shed new features, or trigger an automated rollback.
We used to have a manual process where an on-call engineer would eyeball graphs and declare a freeze. It was slow and inconsistent. Now our alerting looks at multi-window burn rates — a fast window (5 minutes) and a slow window (1 hour) — to avoid flipping out over a brief blip. If both are high, we automatically pause progressive delivery. This is where you'll want to be careful with automation. I've seen teams automate rollbacks so aggressively that a flapping health check causes a deployment loop. The discussion in The CDN Automation Reliability Paradox: When Automation Causes More Outages isn't theoretical — we've lived it. And if you're trying to free up humans to focus on this instead of ticket queues, CDN Toil Reduction Through Automation: Eliminating Repetitive Operations Work has practical patterns, but they only work if your error budget gates are solid first.
Don't try to track this in spreadsheets. Your CDN should be emitting SLIs to your observability stack every few seconds, and your burn rate dashboards should be the first thing feature teams check before they hit "promote to 10%." Here's a typical set of targets we use and what their budgets actually look like in practice for a mid-sized CDN handling around 1B requests per day. Your numbers will vary, but the ratios are what matter.
| SLI / SLO | SLO Target (28d) | Monthly Error Budget | What Burns It Fast |
|---|---|---|---|
| Edge Availability (2xx/3xx/4xx) | 99.90% | 43m 12s downtime or ~1M failed reqs | Bad VCL/config push, global cert expiry, anycast routing flap |
| Cache Hit TTFB p95 | 95% < 80ms | 5% of HITs can exceed 80ms (~50M slow hits) | SSD failure in PoP, cache fragmentation after purge storm |
| Origin Offload Success | 99.50% | 0.5% of origin fetches can fail (~500K errors) | Shield collapse, origin timeout, stale-while-revalidate bug |
| Config Propagation | 99.00% < 60s | 1% of nodes can be >60s stale | Control-plane queue backlog, stuck Edge API worker |
Putting Error Budgets into Practice: Automating Policy Gates
We’ve all seen the spreadsheet that tracks error budgets manually. It lasts about two sprints before someone forgets to update it after a 3 AM PoP failover. If your error budget isn’t enforced in code, it isn’t enforced at all. The trick is to wire your SLO burn directly into your deployment pipeline so the decision to ship or freeze isn’t a judgment call in Slack — it’s an automated gate.
For a global CDN, that means defining a clear, queryable policy. We don't freeze on a single 5xx spike in São Paulo; we freeze on sustained burn. A practical setup is to use a 1-hour and 6-hour burn window evaluation. For a 99.95% availability SLO — that’s just 21.6 minutes of downtime per month — a burn rate of 14.4x will exhaust your entire monthly budget in about 2.5 hours. Your gate should block non-critical config pushes, purge-all operations, and VCL/WASM deploys when that 1-hour burn rate stays above 10x for more than 15 minutes. We've found that gives us enough signal without flapping on every cache revalidation storm.
Implementation-wise, you’ll want your CI/CD runner to query your observability backend before promotion. Don’t make humans check a dashboard. Integrate it with your progressive delivery controller so canaries in a single region can still proceed while global rollout is halted. When the gate does trip, it should automatically trigger your freeze runbook. That’s where CDN Runbook Automation: Converting Manual Procedures into Executable Scripts becomes critical — an automated freeze without an automated response just leaves engineers scrambling to figure out what’s actually blocked.
# error-budget-gate.rego - OPA policy evaluated in CI/CD pipeline
package cdn.deploy
import future.keywords.in
default allow = false
# Query Prometheus/Thanos for multi-window burn rate
burn_rate_1h := data.slo.burn_rate["1h"]
burn_rate_6h := data.slo.burn_rate["6h"]
budget_remaining := data.slo.budget_remaining_percent
# Critical security/rollback deploys always bypass
allow if {
input.deploy_type in {"security_patch", "emergency_rollback"}
}
# Normal feature deploys: block if budget is burning fast
allow if {
input.deploy_type == "feature"
burn_rate_1h < 10
burn_rate_6h < 3
budget_remaining > 15
}
deny_reason[msg] if {
not allow
input.deploy_type == "feature"
msg := sprintf("Deploy blocked: 1h burn=%.1fx, 6h burn=%.1fx, budget=%.1f%% remaining. Freeze in effect.", [burn_rate_1h, burn_rate_6h, budget_remaining])
}
Advanced Patterns: Multi-Region Budgets and Taming Noisy Burn
A single global error budget is a lie on a 150+ PoP network. If you aggregate worldwide, a full outage in your Johannesburg and Mumbai PoPs can be completely masked by healthy traffic in North America and Europe, and you’ll burn through regional user trust while your global SLO stays green at 99.97%. We learned this the hard way. Instead, we run hierarchical budgets: a global budget for the control plane and anycast edge, plus partitioned budgets for our top 5 traffic regions (NA, EU, APAC, LATAM, MEA-India). Each region gets its own 99.9% SLO. A feature that tanks hit ratio in APAC won’t block a deploy targeted at EU, but it will halt the global promotion pipeline.
Troubleshooting burn rate is where most teams get stuck. Not all burn is equal. If your 5-minute burn rate spikes to 30x right after a cache purge, don’t panic — check your origin shield first. We’ve seen a 15% drop in edge hit ratio cause a 400% increase in origin 5xx that looks like an edge failure but is actually a self-inflicted thundering herd. Another classic is the noisy neighbor: a single large customer with a misconfigured origin returns 503s for 40,000 RPS, and if your SLO is measured as a raw global error rate, it’ll eat 80% of your budget even though 99% of your customers are fine. Fix this by using request-weighted SLOs segmented by customer class or by excluding authenticated origin errors (like 4xx) from your burn calculation entirely. Always alert on burn rate *and* traffic volume — a 10x burn on 200 RPS is not the same as 10x on 2M RPS.
Finally, decide ahead of time how you’ll handle a busted budget. When you hit 0% remaining with 12 days left in the month, what happens? The worst approach is an ad-hoc, blame-filled argument in an incident channel. Your policy should state that exhausting the budget triggers a feature freeze by default, but product and engineering leads can negotiate a documented risk acceptance with a clear rollback plan. Every exhaustion must lead to a proper review, not a punishment. Use your burn data to feed into your CDN Postmortem Analysis: Learning from Incidents to Prevent Recurrence and make sure those reviews stay focused on the system. We’ve found that linking budget policy directly to Building a Blameless CDN Operations Culture: Focus on Systems, Not People is the only way engineers will actually respect the freeze instead of trying to game the metrics.
Frequently Asked Questions
What happens when we hit 0% error budget mid-sprint? Do we really stop all deploys?
Not all deploys, but all *risky* deploys. We enforce a freeze on non-essential feature launches, major config refactors, and anything that touches the critical path (TLS termination, routing, caching logic). Bug fixes, security patches, and automated rollbacks are always allowed. If product absolutely needs to ship, they need to get a documented exception from engineering leadership that acknowledges the risk to the SLO.
How do you handle error budget burn caused by a customer’s origin, not our edge?
You shouldn’t pay for their origin’s failure. Our edge availability SLO explicitly excludes 5xx errors where our edge correctly proxied the request and the origin returned the error, which we tag as `origin_status=5xx`. We track a separate Origin Health SLO for that. If your SLO includes origin errors, you’ll burn budget every time a big customer deploys bad code on their end, which teaches your team to ignore the budget.
Should error budgets reset after an incident, or do we carry negative budget into next month?
Never carry negative budget — it just demoralizes the team and guarantees another month of freezes. Budgets reset to 100% at the start of the calendar month. However, if you burned 100% in 3 days due to a systemic issue, you don’t just resume normal velocity. You should hold a freeze until the corrective actions from the postmortem are actually shipped. The reset is for morale, not for ignoring the reliability debt.
Our burn rate alerts are too noisy during deployments. How do we fix the flapping?
Stop alerting on 5-minute windows. Use multi-window, multi-burn-rate alerts like Google’s approach: alert on a 1-hour window with a burn rate >14.4x AND a 6-hour window with a burn rate >6x. This filters out the 2-3 minute blips you get during a rolling restart of 800 edge nodes. Also, bake in a 10-15 minute delay before your deployment gate evaluates burn — let the canary settle before you judge it.