4. SLA, SLO, SLI и как они влияют на дизайн
Управление надежностью и влияние на дизайн систем
«Если вы не можете что-то измерить, вы не можете это улучшить. А если вы не способны определить, когда система дает сбой, вы не сможете спроектировать ее успешной».
— Свободная интерпретация классического принципа Bell Labs
В предыдущей главе мы разделили требования к программной системе на функциональные («что делает код») и нефункциональные («каковы качественные атрибуты его работы»). Теперь пришло время перейти от общих пожеланий к строгой математике надежности.
Каждый инженер слышал фразы: «Наш сервис должен работать быстро» или «Система обязана быть максимально надежной». Для системного архитектора подобные формулировки лишены смысла. Насколько быстро? Для скольких процентов пользователей? Сколько минут простоя в месяц допустимо, прежде чем бизнес начнет терять миллионы?
SLI, SLO и SLA — три фундаментальных инструмента Site Reliability Engineering (SRE), с помощью которых бизнес, продуктовые менеджеры и инженеры заключают негласный контракт. Для разработчика на Go уровня Senior+ понимание этих концепций критично: они определяют бюджет на таймауты сетевых вызовов, лимиты пулов горутин, выбор стратегий ретраев и даже конфигурацию аллокатора памяти рантайма.
Определения: SLI, SLO, SLA
Эти три понятия выстраиваются в четкую вертикальную иерархию — от низкоуровневых счетчиков ядра до юридических соглашений с клиентами:
| Аббревиатура | Расшифровка | Суть | Пример |
|---|---|---|---|
| SLI | Service Level Indicator | Конкретная числовая метрика, характеризующая аспект работы сервиса. Измеряем. | P99 latency = 45ms, error rate = 0.05% |
| SLO | Service Level Objective | Целевое значение SLI за определённый период. Целимся. | P99 latency ≤ 100ms за 28 дней |
| SLA | Service Level Agreement | Юридически обязывающий договор с клиентом, включающий SLO и компенсации при нарушении. Обещаем. | Доступность 99.9% ежемесячно, иначе штраф 10% от оплаты |
flowchart TD
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
SLI["SLI (Индикатор)\nЧто измеряем в коде?\n(Метрики Prometheus, P99, Error Rate)"]
SLO["SLO (Цель)\nК чему стремимся внутри команды?\n(99.9% запросов быстрее 100 мс)"]
SLA["SLA (Соглашение)\nЧто юридически обещаем клиенту?\n(Штрафы и неустойки при нарушении)"]
Budget["Error Budget\n(Бюджет ошибок = 1 - SLO)\nПраво на риск и скорость релизов"]
SLI -->|"устанавливаем цель"| SLO
SLO -->|"включает буфер безопасности"| SLA
SLO -->|"определяет допустимый простой"| Budget
SLA -->|"определяет компенсации при нарушении"| Business["Бизнес-последствия\n(Финансовые потери)"]
SLI для Go-сервиса: четыре золотых сигнала
В методологии Google SRE выделяют Four Golden Signals — четыре ключевых индикатора, исчерпывающе описывающих здоровье любого сетевого сервиса:
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
Signals["Четыре Золотых Сигнала (SRE)"]:::process --> Lat["1. Latency (Задержка)\nP50, P90, P99"]
Signals --> Traf["2. Traffic (Трафик)\nRPS, пропускная способность"]
Signals --> Err["3. Errors (Ошибки)\nДоля HTTP 5xx"]
Signals --> Sat["4. Saturation (Насыщение)\nГорутины, CPU, GC pauses, RAM"]
1. Latency (Задержка)
Время обработки запроса. Для веб-сервиса на Go это время от получения HTTP-запроса до отправки ответа клиенту.
Как измерять в Go:
Middleware, замеряющий длительность с помощью time.Since() и экспортирующий гистограмму в Prometheus:
package middleware
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Длительность обработки HTTP запросов",
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5},
},
[]string{"method", "path"},
)
func MetricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
duration := time.Since(start).Seconds()
httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
}()
next.ServeHTTP(w, r)
})
}
2. Traffic (Трафик)
Количество входящих запросов к сервису. Измеряется в RPS (Requests Per Second) или RPM.
Как измерять:
Счетчик (Counter), инкрементируемый в том же Middleware:
var httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Общее число входящих запросов",
},
[]string{"method", "path"},
)
// Внутри обработчика:
httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path).Inc()
3. Errors (Ошибки)
Доля запросов, завершившихся сбоем. Критически важно разделять клиентские ошибки (4xx, например 400 Bad Request или 404 Not Found) и системные сбои бэкенда (5xx). В расчете SLO сервиса учитываются исключительно ошибки 5xx.
Как измерять:
Условный инкремент счетчика ошибок при statusCode >= 500:
var httpRequestErrors = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_request_errors_total",
Help: "Количество ошибок сервера 5xx",
},
[]string{"method", "path"},
)
// При фиксации статуса ошибки:
if statusCode >= 500 {
httpRequestErrors.WithLabelValues(r.Method, r.URL.Path).Inc()
}
4. Saturation (Насыщение)
Степень загруженности системных ресурсов. Для Go-сервиса ключевыми показателями насыщения служат:
- Количество активных горутин (
runtime.NumGoroutine()). - Утилизация ядер процессора относительно
GOMAXPROCS. - Частота и длительность фаз сборки мусора (GC Pause Duration).
- Заполненность внутренних очередей и буферизованных каналов.
Как измерять:
Экспорт runtime-метрик через prometheus.NewGoCollector() или сбор детальных кастомных метрик из структуры runtime.MemStats:
// Регистрация стандартных метрик Go runtime
prometheus.MustRegister(prometheus.NewGoCollector())
SLO и Error Budget
SLO — это целевой уровень надежности, выраженный через SLI.
Пример: «99.9% запросов к эндпоинту /api/order за скользящее окно в 30 дней должны завершаться со статусом 200 OK и выполняться быстрее 200 мс».
Из значения SLO напрямую рассчитывается Error Budget (Бюджет ошибок):
$$\text{Error Budget} = 1 - \text{SLO}$$
Если внутренний SLO по доступности равен 99.9%, то допустимый объем сбоев за 30-дневный период составляет:
$$(100\% - 99.9\%) \times 30 \times 24 \times 60 \approx 43.2 \text{ минуты}$$
Бюджет ошибок — это законное право инженерной команды идти на оправданный риск:
- Выкатывать сложные архитектурные релизы.
- Проводить канареечные развертывания и A/B-тесты.
- Экспериментировать с новыми версиями компилятора Go.
Пока бюджет ошибок не исчерпан, команда может сохранять высокую скорость разработки (Feature Velocity). Но если произошла серия инцидентов и бюджет сгорел до нуля, релизы нового функционала автоматически замораживаются: все силы команды переключаются на укрепление отказоустойчивости и устранение корневых причин сбоев.
gantt
title Сгорание бюджета ошибок (Error Budget Burn Rate), окно 30 дней
dateFormat YYYY-MM-DD
axisFormat %d
section Бюджет доступности
Нормальная работа (Burn Rate 1x) :active, b1, 2026-01-01, 2026-01-16
Инцидент в ЦОД — сгорает 60% бюджета :crit, b2, 2026-01-16, 2026-01-18
Заморозка релизов и стабилизация :b3, 2026-01-18, 2026-01-31
Как SLO и Error Budget влияют на архитектурный дизайн
SLO — это не просто цветные графики в Grafana. Они диктуют выбор конкретных технических решений в кодовой базе.
Таймауты и дедлайны
Если совокупный SLO по latency для эндпоинта равен 200 мс, то все вызовы к базам данных и микросервисам должны иметь жесткие квоты времени, укладывающиеся в этот лимит. В Go это реализуется через context.WithTimeout:
package service
import (
"context"
"errors"
"time"
)
var ErrTemporaryUnavailable = errors.New("сервис временно недоступен")
type StockClient interface {
CheckAvailability(ctx context.Context, items []string) (bool, error)
}
type OrderService struct {
stockClient StockClient
}
func (s *OrderService) CreateOrder(ctx context.Context, req []string) error {
// Если SLO сервиса 200ms, выделяем жесткий лимит 50ms на проверку наличия
stockCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
_, err := s.stockClient.CheckAvailability(stockCtx, req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
// Таймаут превышен: аккуратно отдаем ошибку без блокировки потока
return ErrTemporaryUnavailable
}
return err
}
return nil
}
Ретраи и Circuit Breaker
При временной недоступности зависимого сервиса бездумные ретраи (Retries) способны мгновенно испепелить весь бюджет ошибок и устроить лавинообразный отказ всей системы (Thundering Herd). Архитектура обязана предусматривать:
- Exponential backoff со случайным смещением (jitter).
- Строгое ограничение числа попыток.
- Circuit Breaker (предохранитель), размыкающий цепь при превышении порогового процента сбоев (подробнее в 36. Circuit Breaker, Retry, Timeout и Backoff).
Репликация и Multi-Region
Если для системы выставлен SLO доступности 99.99% («четыре девятки»), допустимое время простоя составляет всего ~52 минуты в год. Достичь такого показателя в рамках одного дата-центра физически невозможно: любая авария на магистральной оптоволоконной линии или плановые работы энергетиков сожгут годовой бюджет.
Следовательно, архитектура обязана стать:
- Географически распределенной (Multi-Region).
- Stateless на уровне прикладных сервисов Go (7. Stateless vs Stateful сервисы), чтобы трафик можно было мгновенно переключить на резервный регион.
- Опирающейся на репликацию данных с учетом компромиссов CAP-теоремы (30. CAP теорема и реальные компромиссы).
Управление горутинами и пулами соединений
Бесконтрольный запуск горутин или открытие тысяч одновременных сокетов к базе данных приводит к быстрому исчерпанию ресурсов (Saturation) и деградации Latency.
Ограничение параллелизма через буферизованный канал-семафор:
package limit
import "context"
// Ограничение количества одновременных тяжелых операций числом 100
var sem = make(chan struct{}, 100)
func HeavyOperation(ctx context.Context) error {
select {
case sem <- struct{}{}:
defer func() { <-sem }()
// Выполнение полезной работы
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Тонкая настройка пула соединений к PostgreSQL через пакет database/sql:
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
Mechanical Sympathy: GC и хвостовые задержки
Для сервисов на Go с жесткими требованиями к хвостовой задержке (P99 < 50ms) критическое значение приобретает поведение сборщика мусора.
Хотя паузы Stop-The-World в рантайме Go составляют доли миллисекунды, при интенсивном темпе аллокаций фазы маркировки (GC Mark Phase) запускаются слишком часто. Если горутина попадает на фазу GC Assist (когда рантайм привлекает пользовательскую горутину к помощи в разметке памяти), время ее выполнения скачкообразно возрастает, что мгновенно пробивает границу P99.
Инженерный комплекс мер:
- Снижение аллокаций: переиспользование временных объектов через
sync.Pool. - Профилирование памяти: регулярный запуск
go tool pprof -alloc_space. - Тюнинг GC: настройка параметров
GOGCи мягкого лимитаGOMEMLIMIT(Go 1.19+). - Предпочтение Value-типов: размещение данных на стеке без выхода в кучу (Escape Analysis).
package buffer
import (
"net/http"
"sync"
)
type Response struct {
Data []byte
}
// Пул переиспользуемых структур для горячего пути
var responsePool = sync.Pool{
New: func() interface{} {
return &Response{Data: make([]byte, 0, 1024)}
},
}
func HandlerOptimized(w http.ResponseWriter, r *http.Request) {
resp := responsePool.Get().(*Response)
defer responsePool.Put(resp)
resp.Data = resp.Data[:0] // Сброс длины среза без повторной аллокации памяти
w.WriteHeader(http.StatusOK)
}
Практический пример: расчет бюджета ошибок для Go-сервиса
Спроектируем контур надежности для микросервиса обработки платежей.
SLI:
- Availability: доля ответов
2xx/3xxк общему числу запросов. - P99 Latency: 99-й процентиль времени формирования ответа.
SLO:
- Availability $\ge$ 99.95% за скользящее окно 30 дней.
- P99 Latency $\le$ 100 мс за те же 30 дней.
Расчет бюджета ошибок:
- Месячный интервал: $30 \times 24 \times 60 = 43\,200$ минут.
- Допустимое время деградации: $0.05\% \times 43\,200 = \mathbf{21.6 \text{ минуты}}$ в месяц.
Архитектурные последствия:
- Неаккуратный деплой, вызывающий 2-минутный разрыв соединений, съедает 10% месячного бюджета.
- Инцидент с падением пода на 5 минут сжигает сразу 23% бюджета.
- Следовательно, сервис обязан поддерживать:
- Корректный Graceful Shutdown с перехватом
SIGTERM. - Настроенные Readiness и Liveness probes в манифестах Kubernetes.
- Стратегию развертывания Rolling Update с параметрами
maxUnavailable: 0иmaxSurge: 1.
- Корректный Graceful Shutdown с перехватом
SLA: когда метрики становятся деньгами
SLA (Service Level Agreement) — это уже юридический и финансовый документ между поставщиком и клиентом.
Классическая формулировка:
«Платформа гарантирует доступность сервиса на уровне 99.9% ежемесячно. В случае превышения суммарного времени простоя более 43.2 минут в месяц, заказчику предоставляется компенсация в размере 10% от ежемесячной абонентской платы за каждый последующий час недоступности».
Фундаментальное правило системного проектирования: внутренний SLO обязан быть жестче внешнего SLA.
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
SLA["Внешний SLA: 99.9%\n(Допустимо: 43.2 мин)"]
SLO["Внутренний SLO: 99.95%\n(Допустимо: 21.6 мин)"]
Buffer["Буфер безопасности:\n21.6 мин на ликвидацию аварии"]:::process
SLO --> Buffer --> SLA
Итог
- SLI — объективные числовые измерения работы системы (Latency, Traffic, Errors, Saturation), собираемые через Prometheus middleware.
- SLO — амбициозная внутренняя цель надежности, порождающая Error Budget (инженерное право на риск и эксперименты).
- SLA — юридический договор с клиентом, нарушение которого ведет к штрафам.
- SLO напрямую диктуют архитектуру: таймауты сетевых контекстов (
context.WithTimeout), паттерны отказоустойчивости (Circuit Breaker), размеры пулов горутин и стратегии развертывания в Kubernetes. - Для систем на Go контроль хвостовых задержек P99 неразрывно связан с тюнингом Garbage Collector (
GOMEMLIMIT) и переиспользованием памяти (sync.Pool).
Освоив метрики надежности, мы готовы перейти к фундаментальным законам распределенных вычислений: компромиссам между задержкой, пропускной способностью и доступностью. В следующей лекции: 5. Latency, Throughput, Availability и Trade-offs.