Зачем SLI/SLO
Threshold-алерт «CPU > 80%», почти всегда мусор:
- 80% CPU = OK на batch worker, problem на user-facing
- Не отражает что чувствует пользователь
- Шум (flap, false-positive) убивает on-call
Google SRE подход (книга «Site Reliability Engineering»):
- Определи SLI, Service Level Indicator. Метрика близкая к пользователю: % успешных запросов, p99 latency.
- Установи SLO, Service Level Objective. Цель за период: «99.9% запросов успешны за 30 дней».
- Вычисли error budget:
1 - SLO. У 99.9% = 0.1% разрешённого downtime = 43 минуты в месяц. - Расходуй budget на: инциденты, рискованные релизы, эксперименты.
- Алерт срабатывает когда burn rate budget'а превышает порог.
Это сдвигает разговор от «что-то сломалось» к «сколько у нас осталось права быть сломанным».
SLI vs SLO vs SLA
| Термин | Что |
|---|---|
| SLI | Indicator - сама метрика (availability rate, latency p99) |
| SLO | Objective - целевое значение (99.9%) |
| SLA | Agreement - контракт с пользователем (с штрафом) |
| Error budget | 1 - SLO. Допустимый %+время сбоев |
SLO внутренний (engineering tool), SLA внешний (legal, refund money).
Обычно: SLA > SLO. SLO жёстче чтобы был запас. Если SLA=99.5%, делай SLO=99.9%.
Хорошие SLI
Должны:
- Корректно отражать user experience (если SLI зелёный, юзер счастлив)
- Aggregatable (можно посчитать % за период)
- Стабильно измеряемы (не флапают на пустяках)
Хорошие:
- Availability:
successful_requests / total_requests(status < 500 / total) - Latency:
p99 < 200ms(% requests faster than threshold) - Throughput:
actual_qps / target_qps - Correctness:
correct_results / total_results(для batch jobs) - Freshness:
data_age_p99 < 5min(для pipelines)
Плохие:
- CPU%: не отражает user experience
- Avg latency: hides tail (p99 = 5s, avg = 100ms, пользователь злой, метрика «зелёная»)
- «Пользователь жаловался»: not aggregatable
Window: rolling vs calendar
Rolling 30d: «за последние 30 дней, 99.9% success». Каждый момент считается заново. Стандарт SRE.
Calendar month: «октябрь 99.9%». Простой для бизнеса, но bad behavior на стыках месяцев.
Используй rolling.
Error budget, как считать
SLO = 99.9% за 30 дней.
Error budget = 1 - 0.999 = 0.001 = 0.1% всех запросов могут fail.
Если 100K req/day × 30d = 3M req → разрешено 3000 fail.
Прошло 15 дней, было 1500 fail = 50% budget consumed. Через 15 дней должно быть 1500 ещё. Если уже 2900, 97% consumed, остался только 100 на 15 дней.
Burn rate = consumed / time_passed. Если budget израсходован
быстрее линейного, alert.
В часах:
- 99.9% за 30d = 43.2 минуты допустимого downtime
- 99.99% = 4.3 минуты
- 99.999% = 26 секунд (нужны hot-standby + multi-region)
Multi-window burn rate alerting
Старый подход: алерт «error rate > 1% for 5m». Проблемы:
- flap на коротких spike'ах
- не различает «слегка медленно» vs «выгораем budget за час»
Multi-window burn rate (Google SRE Workbook ch. 5):
groups:
- name: slo
rules:
# Burn rate за 5m и 1h одновременно
- alert: ErrorBudgetBurnFast
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m])) /sum(rate(http_requests_total[5m]))
) > (14.4 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[1h])) /sum(rate(http_requests_total[1h]))
) > (14.4 * 0.001)
for: 2m
labels: {severity: critical}annotations:
summary: "Сжигаем budget со скоростью 14.4×, за час потеряем 2% (30-day budget)"
- alert: ErrorBudgetBurnSlow
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h])) /sum(rate(http_requests_total[1h]))
) > (3 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[6h])) /sum(rate(http_requests_total[6h]))
) > (3 * 0.001)
for: 15m
labels: {severity: warning}Магия двух окон:
- Короткое окно (5m), быстро реагируем
- Длинное окно (1h), фильтруем флапы
Multipliers (14.4 для fast, 3 для slow), выведены чтобы page рано если сжигаем за день (fast) или за неделю (slow).
Burn-rate cheatsheet
Для SLO 99.9% (budget 0.1%):
| Burn rate | Время до full burn | Когда page |
|---|---|---|
| 14.4× | 2.1 day | 2-min page (fast) |
| 6× | 5 days | 15-min page (medium) |
| 3× | 10 days | 1h page (slow) |
| 1× | 30 days (planned) | no page |
Источник: Google SRE Workbook, table 5-2.
Error budget policy
Кодифицируй что делать когда budget кончился. Пример:
Error Budget Policy v1.4
Если за rolling 30d:
- Budget < 0%: code freeze. Только bugfix-релизы.
SRE и dev делят priorities 50/50 на reliability work.
- Budget < 25%: rollout ограничен (slow rollout).
Канареечные релизы обязательны.
- Budget > 25%: normal velocity.
Reset budget, только пройденное время.
Не «прощаем» инциденты ретроспективно.
Без policy, SLO просто слайд в дашборде. С policy, реальный governance: dev команда видит реальную цену плохих релизов.
SLO для разных систем
| System | SLI | SLO |
|---|---|---|
| Web API | success rate, p99 latency | 99.9% / p99 < 200ms |
| Async queue | processed rate | 99.99% (queue HA) |
| Batch ETL | freshness, correctness | freshness < 1h, correctness 100% |
| Cache | hit rate? no, это не user-facing | latency p99 < 50ms |
| Search | relevance score, latency | latency p95 < 1s, relevance > 0.7 |
Cache hit rate, internal metric, не SLI. SLI, что видит пользователь (latency).
Prometheus recording rules для SLO
groups:
- name: slo_recording
interval: 30s
rules:
# Per-service availability
- record: slo:request_availability:ratio_rate5m
expr: |
sum by (service)(rate(http_requests_total{status!~"5.."}[5m]))/
sum by (service)(rate(http_requests_total[5m]))
# Per-service latency SLI
- record: slo:request_latency:ratio_rate5m
expr: |
sum by (service)(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))/
sum by (service)(rate(http_request_duration_seconds_count[5m]))
Burn-rate alerting использует эти recording rules вместо raw queries. Дешевле, читабельнее.
Tools
- Sloth (CNCF), генерит SLO + alerting + recording rules из YAML-spec
- OpenSLO, стандарт SLO-spec, поддерживается Sloth/Nobl9
- Pyrra, UI для SLO-as-code в Kubernetes
- Grafana SLO (paid), managed SLO в Grafana Cloud
Sloth example:
apiVersion: sloth.slok.dev/v1
kind: PrometheusServiceLevel
spec:
service: api
slos:
- name: availability
objective: 99.9
sli:
events:
error_query: sum(rate(http_requests_total{status=~"5.."}[{{.window}}])) total_query: sum(rate(http_requests_total[{{.window}}]))alerting:
page_alert: {labels: {severity: critical}} ticket_alert: {labels: {severity: warning}}Sloth генерит recording+alerting rules автоматически с правильной multi-window burn rate.
Когда что-то пошло не так
- SLO не достигнут, но budget положительный, окно 30d, флап был 30 дней назад, выпал. Норма.
- Budget = 100% всё время, SLO слишком слабый. Поджимай.
- Budget burn alerting не firing на real outage, burn rate multiplier слишком высокий, или SLI не отражает affected requests. Например, SLI на rate, outage на latency.
- Cardinality explosion в SLO recording,
sum by (user_id)сделал миллион series. См. cardinality-explosion. - Disagreement между команд про SLO, норма. Договариваться через iterations, не один раз навсегда.
- «Если пишем p99, p999 нужен», нет. p99 покрывает 99% user-experience. p999 = noise + ML-territory.
Anti-patterns
- SLO без budget policy, engineering theater
- SLO 100%, невозможно, error budget = 0, любая деплоя ломает
- SLI на internal metric (CPU, memory), не отражает user
- Алерт на любой error, нужен
for:или multi-window burn rate - Multiple SLOs которые перекрываются, choose one user-facing