{ "index": 153, "slug": "editorial-2023-10-practice-sli-slo", "title": "SLI и SLO: договор измерения, которому можно доверять", "excerpt": "Как связать показатель качества с пользовательским исходом: определить знаменатель, хороший результат, окно и error budget, проверить отрицательный путь и не принять HTTP-статус за готовую операцию.", "contentHtml": "

На дашборде сервис зелёный: 99,9% запросов завершились без HTTP 5xx. Пользователь всё равно нажимает «Оплатить» второй раз. В рассмотренном учебном сценарии API принял запрос, но платёж не создался, а клиент получил тайм-аут после точки измерения. Команда видит хороший технический процент и плохой пользовательский результат. Цена ошибки — неверный приоритет релиза и расследование не того компонента.

\n

SLI и SLO помогают только после точного договора. SLI отвечает на вопрос «что измеряем?», SLO задаёт цель для этого измерения, а error budget показывает допустимую долю плохих исходов в выбранном окне. Договор должен назвать путь пользователя, eligible-события в знаменателе, good-исход, исключения, источник данных и владельца решения. Иначе процент остаётся удобной, но декоративной метрикой.

\n

Сначала назовите пользовательский исход

\n

Google SRE описывает SLI как количественную меру свойства сервиса, а SLO — как целевое значение или диапазон для уровня сервиса, измеренного этим SLI. Практический вывод простой: начинайте с действия, которое пользователь считает завершённым, и только потом выбирайте доступный сигнал. Готовый счётчик HTTP-кодов не становится хорошим SLI лишь потому, что уже есть в мониторинге.

\n

Для checkout полезный вопрос звучит так: «Как часто завершённая попытка оформления получает подтверждение, которое клиент может показать пользователю?» Это ещё не формула. Нужно определить границу попытки, идентификатор, событие подтверждения и допустимое время ожидания. Если сервис лишь принимает запрос в очередь, SLI приёма не доказывает создание платежа. Такой показатель можно оставить техническим proxy, но назвать его proxy в документации и на графике.

\n

Путь должен быть достаточно узким, чтобы его можно было восстановить по одному событию. «Весь сайт работает» — плохой scope для первой договорённости. «Отправка оформленного checkout до подтверждения приёма платежа» уже допускает проверку: видны начало, конец, идентификатор и ожидаемый исход.

\n

Разделите SLI, SLO и SLA

\n

SLI — способ измерения: например, доля eligible-попыток, для которых пришёл good-результат. SLO — цель, например не менее 99% в rolling window, то есть в скользящем окне. SLA — договор с пользователем, где за невыполнение SLO предусмотрено явное последствие. Внутренний SLO не становится SLA автоматически: наличие графика не создаёт финансовых или организационных обязательств.

\n

Цель 99% ниже 100% намеренно оставляет допустимый риск. Это не рекомендация ставить именно 99%. Число должно учитывать цену ошибки, нагрузку, ожидания пользователя и возможность команды реагировать. Нельзя выбрать target только по текущему лучшему результату: тогда команда зафиксирует случайное достижение и получит дорогое обязательство.

\n

Соберите контракт знаменателя

\n

Для success ratio разделите события на eligible и good. Eligible — попытки, которые имеют право попасть в знаменатель. Good — подмножество eligible с заранее названным допустимым исходом. Базовая формула: SLI = good / eligible. Каждое исключение меняет знаменатель, поэтому его записывают до расчёта, а не добавляют после неудачного дня.

\n

Например, отмена до отправки запроса может не входить в eligible. Отмена после принятия запроса уже может быть частью результата, если сервис обязан её обработать. Потерянное событие нельзя молча считать успехом. При неоднозначном статусе нужно остановить интерпретацию, найти запись операции и решить, какое правило действует для всех таких случаев.

\n
Минимальный договор для учебного пути checkout
ПолеУчебное значениеЧто проверитьГраница
ПутьОт отправки checkout до подтверждения приёмаЕсть начало, конец и request_idНе описывает создание платежа
EligibleЗапрос принят точкой завершения APIСобытие содержит итоговый статусОтмена до запроса не входит
GoodКлиент получил подтверждение приёмаСтатус связан с ответом клиентуНе доказывает фактическое списание
Окно28 дней в учебном примереОтчёт и запрос используют одну границуНе универсальная норма
Target99% good среди eligibleЕсть обоснование и правило пересмотраНе назначается по одному графику
ВладелецКоманда checkoutНазвано лицо, принимающее решениеМетрика сама не блокирует релиз
\n
\"Схема
Учебные числа показывают связь окна, знаменателя, цели и остатка бюджета. Иллюстрация не является дашбордом реального сервиса.
\n

Один и тот же процент имеет разный смысл при разном трафике. 99 из 100 попыток дают 99%, но одна ошибка в десяти попытках меняет показатель сильнее, чем одна ошибка в миллионе. Поэтому рядом с SLI показывают число eligible, good и bad. Для пакетной обработки может быть важнее throughput или время завершения, а для интерактивного пути — latency и доля успешных исходов. Не нужно насильно сводить разные пользовательские задачи к одной цифре.

\n

Посчитайте error budget на фиксированных данных

\n

Error budget — это допустимая доля bad-событий в том же знаменателе и окне. В учебном наборе 1 000 eligible-событий, 994 good и 6 bad. При target 99% допустимы 10 bad-событий, поэтому условный остаток равен 4. Этот пример проверяет только арифметику; он не читает логи, не обращается к мониторингу и не сообщает состояние сервиса.

\n
const eligible = 1000;\nconst good = 994;\nconst target = 0.99;\n\nif (!Number.isInteger(eligible) || eligible <= 0) {\n  throw new Error('eligible must be a positive integer');\n}\nif (!Number.isInteger(good) || good < 0 || good > eligible) {\n  throw new Error('good must be between 0 and eligible');\n}\n\nconst bad = eligible - good;\nconst sli = good / eligible;\nconst allowedBad = Math.round(eligible * (1 - target));\nconst budgetDelta = allowedBad - bad;\n\nconsole.log({\n  sliPercent: sli * 100,\n  bad,\n  allowedBad,\n  budgetDelta,\n  budgetState: budgetDelta >= 0 ? 'remaining' : 'exhausted',\n});\n// { sliPercent: 99.4, bad: 6, allowedBad: 10,\n//   budgetDelta: 4, budgetState: 'remaining' }\n// Это synthetic-арифметика, а не измерение production-сервиса.
\n

Сохраните содержимое блока в файл sli-example.mjs и выполните node sli-example.mjs. В рабочем отчёте нужно заранее договориться об округлении, если допустимое число bad-событий получается дробным. В примере явно выбран Math.round для целого числа событий; другая policy требует отдельного решения. Если good больше eligible, знаменатель нулевой или данные неполны, расчёт должен остановиться, а не выдавать красивый процент.

\n

Наивная замена good на «ответ не 5xx» опасна: запрос может быть принят, но пользователь ещё не получил результат. Обратная ошибка тоже возможна: клиентский fallback завершил путь, а серверная метрика записала ошибку. В обоих случаях сравните технический сигнал с событием, которое действительно закрывает пользовательскую попытку.

\n

Свяжите цель с решением, а не с цветом

\n

SLO полезен как часть петли управления: измерить SLI, сравнить его с SLO, оценить риск, выбрать действие и снова измерить. Error budget поставляет evidence для приоритизации, но не является универсальным приказом остановить изменения. В официальном примере Google policy расход бюджета определяет, когда надёжности нужно уделить больше внимания; конкретные исключения и полномочия остаются договорённостью команды.

\n

Алерт должен сообщать о действующей угрозе бюджету, а не о каждом колебании процента. Google отдельно разбирает precision, recall, detection time и reset time для SLO-алертов. Для low-traffic сервиса один отказ может дать огромную мгновенную долю и ложную срочность. Возможные решения — более длинное окно, искусственный трафик с оговоркой о его покрытии, объединение связанных потоков или изменение продукта так, чтобы единичный сбой меньше вредил пользователю. Ни один вариант нельзя выбрать без оценки конкретного пути.

\n

После срабатывания SLO-алерта не ищите причину в одной панели. OpenTelemetry разделяет traces, metrics и logs: trace показывает путь запроса, metric — измерение во времени, log — запись события. Эти сигналы помогают связать симптом с компонентом, но ни один из них сам по себе не доказывает причинность. Нужны исходное событие, граница времени и проверка гипотезы.

\n

Проверьте отрицательный путь

\n
  1. Процент растёт после фильтрации ошибок. Сравните eligible до и после фильтра, разберите один спорный request_id и верните исключение в договор, если общего правила нет.
  2. Good равен любому ответу 2xx. Проследите путь клиента после ответа и отделите приём запроса от фактического пользовательского результата.
  3. Один отказ меняет решение о релизе. Покажите число eligible за окно и долю бюджета, которую потребляет одна ошибка; для малого потока оставьте сигнал диагностическим, если он не поддерживает действие.
  4. Событие не имеет request_id или потеряно. Не восстанавливайте успех по отсутствию записи. Исправьте источник, а неполное окно пометьте как непригодное для вывода.
  5. Good больше eligible или окно различается. Остановите расчёт, исправьте контракт и только потом сравнивайте отчёт с целью.
\n

Порядок внедрения

\n
  1. Опишите один пользовательский путь и точку, после которой попытка считается eligible.
  2. Назовите good event, приведите пример успеха, ошибки и спорного случая.
  3. Зафиксируйте request_id, источник события, исключения, окно, target и владельца.
  4. Посчитайте SLI и budget на фиксированном наборе, включая отрицательные входы.
  5. Сверьте формулу с техническими proxy и явно опишите расхождение, если измерение заканчивается раньше пользовательского результата.
  6. В разрешённой среде сравните отчёт и исходные события на нескольких окнах; не смешивайте учебные числа с наблюдением сервиса.
  7. Добавьте правило реакции: кто проверяет сигнал, какое действие обратимо, когда пересматривается договор и что делать при споре о знаменателе.
\n

Ограничения применимости

\n

Эта схема подходит для пути, где можно определить событие начала, события завершения и принадлежность к знаменателю. Она не заменяет модель latency, throughput, durability или стоимости. Для долгих пакетных процессов success ratio может скрыть время ожидания; для хранения данных одного успешного ответа мало, если важно долговременное сохранение. Для финансовой операции подтверждение приёма также не равно фактическому списанию — это отдельная граница и отдельный показатель.

\n

28-дневное окно и target 99% здесь учебные значения. Официальный пример Google показывает форму SLO-документа и четырёхнедельное rolling window, но не назначает такую длительность вашему сервису. Низкий трафик, внешняя зависимость, ретраи и частично потерянная телеметрия меняют интерпретацию. Если команда не может доказать полноту знаменателя, честный результат — «данных недостаточно», а не приблизительный SLI.

\n

Критерий готовности

\n

Договор можно передавать в работу, если независимый инженер без устных пояснений отвечает на семь вопросов: какой путь измеряется; что входит в eligible; что считается good; какие исключения действуют; за какое окно считается показатель; где лежат исходные события; кто и по какому правилу принимает решение. Для трёх заранее выбранных записей отчёт и проверочный запрос должны дать одинаковый результат. После изменения должны остаться обратимый шаг и команда, которой можно повторить проверку.

\n

Если ответа нет хотя бы на один вопрос, сначала исправьте границу и названия событий. Затем меняйте дашборд, алерт или policy. Такой порядок не даёт принять число, которое легко измерить, за результат, который действительно важен пользователю.

\n

Проверяемые источники

\n" }