# SLI / SLO / error budget: SRE-метрики без шума _Observability и мониторинг · LinuxLab Knowledge Base_ **TL;DR:** SLI - метрика для пользователя (availability, p99 latency). SLO - цель за период (99.9% за 30d). Error budget = 1-SLO, расходуется на инциденты+релизы. Multi-window burn rate alerting заменяет threshold-алерты, меньше шума. ## Зачем SLI/SLO Threshold-алерт «CPU > 80%», почти всегда мусор: - 80% CPU = OK на batch worker, problem на user-facing - Не отражает **что чувствует пользователь** - Шум (flap, false-positive) убивает on-call Google SRE подход (книга «Site Reliability Engineering»): 1. Определи **SLI**, Service Level Indicator. Метрика **близкая к пользователю**: % успешных запросов, p99 latency. 2. Установи **SLO**, Service Level Objective. Цель за период: «99.9% запросов успешны за 30 дней». 3. Вычисли **error budget**: `1 - SLO`. У 99.9% = **0.1% разрешённого downtime** = 43 минуты в месяц. 4. Расходуй budget на: инциденты, рискованные релизы, эксперименты. 5. Алерт срабатывает когда **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): ```yaml 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 ```yaml 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: ```yaml 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](/kb/cardinality-explosion.md). - **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 ## Команды ```bash promtool query instant 'slo:request_availability:ratio_rate5m' ``` Текущая availability SLI - 1.0 = всё успешно, 0.999 = 0.1% errors ```bash sloth generate -i slo.yaml -o rules.yaml ``` Sloth: spec → готовые recording+alerting rules для Prometheus ```bash curl -s 'http://prom:9090/api/v1/query?query=1-slo:request_availability:ratio_rate30d' | jq ``` Сколько budget израсходовано за 30d - алертинг material ```bash amtool alert query alertname=ErrorBudgetBurnFast ``` Текущие fast-burn alerts по error budget ```bash promtool check rules slo-rules.yaml ``` Валидация SLO recording+alerting rules - синтаксис, expr-references ```bash kubectl apply -f slo-spec.yaml -n slo # OpenSLO via Pyrra ``` SLO-as-code: Pyrra оператор поднимет recording rules в Prom ## См. также - [Alertmanager: route tree, inhibit, dedup, on-call](/kb/alerting-rules-alertmanager.md) - [Prometheus: scrape, TSDB, PromQL и production-pitfalls](/kb/prometheus-basics.md) - [Типы метрик: counter, gauge, histogram, summary](/kb/metric-types.md) - [OpenTelemetry: signals, OTLP, Collector pipeline](/kb/opentelemetry.md) - [Cardinality explosion: как убить Prometheus и как чинить](/kb/cardinality-explosion.md)