8 lines
22 KiB
JSON
8 lines
22 KiB
JSON
{
|
||
"index": 153,
|
||
"slug": "editorial-2023-10-practice-sli-slo",
|
||
"title": "SLI и SLO: договор измерения, которому можно доверять",
|
||
"excerpt": "Как связать показатель качества с пользовательским исходом: определить знаменатель, хороший результат, окно и error budget, проверить отрицательный путь и не принять HTTP-статус за готовую операцию.",
|
||
"contentHtml": "<p>На дашборде сервис зелёный: 99,9% запросов завершились без HTTP 5xx. Пользователь всё равно нажимает «Оплатить» второй раз. В рассмотренном учебном сценарии API принял запрос, но платёж не создался, а клиент получил тайм-аут после точки измерения. Команда видит хороший технический процент и плохой пользовательский результат. Цена ошибки — неверный приоритет релиза и расследование не того компонента.</p>\n<p>SLI и SLO помогают только после точного договора. SLI отвечает на вопрос «что измеряем?», SLO задаёт цель для этого измерения, а error budget показывает допустимую долю плохих исходов в выбранном окне. Договор должен назвать путь пользователя, eligible-события в знаменателе, good-исход, исключения, источник данных и владельца решения. Иначе процент остаётся удобной, но декоративной метрикой.</p>\n<h2>Сначала назовите пользовательский исход</h2>\n<p>Google SRE описывает SLI как количественную меру свойства сервиса, а SLO — как целевое значение или диапазон для уровня сервиса, измеренного этим SLI. Практический вывод простой: начинайте с действия, которое пользователь считает завершённым, и только потом выбирайте доступный сигнал. Готовый счётчик HTTP-кодов не становится хорошим SLI лишь потому, что уже есть в мониторинге.</p>\n<p>Для checkout полезный вопрос звучит так: «Как часто завершённая попытка оформления получает подтверждение, которое клиент может показать пользователю?» Это ещё не формула. Нужно определить границу попытки, идентификатор, событие подтверждения и допустимое время ожидания. Если сервис лишь принимает запрос в очередь, SLI приёма не доказывает создание платежа. Такой показатель можно оставить техническим proxy, но назвать его proxy в документации и на графике.</p>\n<p>Путь должен быть достаточно узким, чтобы его можно было восстановить по одному событию. «Весь сайт работает» — плохой scope для первой договорённости. «Отправка оформленного checkout до подтверждения приёма платежа» уже допускает проверку: видны начало, конец, идентификатор и ожидаемый исход.</p>\n<h2>Разделите SLI, SLO и SLA</h2>\n<p><strong>SLI</strong> — способ измерения: например, доля eligible-попыток, для которых пришёл good-результат. <strong>SLO</strong> — цель, например не менее 99% в rolling window, то есть в скользящем окне. <strong>SLA</strong> — договор с пользователем, где за невыполнение SLO предусмотрено явное последствие. Внутренний SLO не становится SLA автоматически: наличие графика не создаёт финансовых или организационных обязательств.</p>\n<p>Цель 99% ниже 100% намеренно оставляет допустимый риск. Это не рекомендация ставить именно 99%. Число должно учитывать цену ошибки, нагрузку, ожидания пользователя и возможность команды реагировать. Нельзя выбрать target только по текущему лучшему результату: тогда команда зафиксирует случайное достижение и получит дорогое обязательство.</p>\n<h2>Соберите контракт знаменателя</h2>\n<p>Для success ratio разделите события на <em>eligible</em> и <em>good</em>. Eligible — попытки, которые имеют право попасть в знаменатель. Good — подмножество eligible с заранее названным допустимым исходом. Базовая формула: <code>SLI = good / eligible</code>. Каждое исключение меняет знаменатель, поэтому его записывают до расчёта, а не добавляют после неудачного дня.</p>\n<p>Например, отмена до отправки запроса может не входить в eligible. Отмена после принятия запроса уже может быть частью результата, если сервис обязан её обработать. Потерянное событие нельзя молча считать успехом. При неоднозначном статусе нужно остановить интерпретацию, найти запись операции и решить, какое правило действует для всех таких случаев.</p>\n<div class=\"table-scroll\"><table><caption>Минимальный договор для учебного пути checkout</caption><thead><tr><th scope=\"col\">Поле</th><th scope=\"col\">Учебное значение</th><th scope=\"col\">Что проверить</th><th scope=\"col\">Граница</th></tr></thead><tbody><tr><td>Путь</td><td>От отправки checkout до подтверждения приёма</td><td>Есть начало, конец и request_id</td><td>Не описывает создание платежа</td></tr><tr><td>Eligible</td><td>Запрос принят точкой завершения API</td><td>Событие содержит итоговый статус</td><td>Отмена до запроса не входит</td></tr><tr><td>Good</td><td>Клиент получил подтверждение приёма</td><td>Статус связан с ответом клиенту</td><td>Не доказывает фактическое списание</td></tr><tr><td>Окно</td><td>28 дней в учебном примере</td><td>Отчёт и запрос используют одну границу</td><td>Не универсальная норма</td></tr><tr><td>Target</td><td>99% good среди eligible</td><td>Есть обоснование и правило пересмотра</td><td>Не назначается по одному графику</td></tr><tr><td>Владелец</td><td>Команда checkout</td><td>Названо лицо, принимающее решение</td><td>Метрика сама не блокирует релиз</td></tr></tbody></table></div>\n<figure><img src=\"/assets/editorial/2023/slo-error-budget-2023-budget-window.svg\" alt=\"Схема учебного error budget: окно 28 дней, 1000 eligible, 994 good, 6 bad, target 99%, допустимы 10 bad, остаток 4\" loading=\"lazy\" /><figcaption>Учебные числа показывают связь окна, знаменателя, цели и остатка бюджета. Иллюстрация не является дашбордом реального сервиса.</figcaption></figure>\n<p>Один и тот же процент имеет разный смысл при разном трафике. 99 из 100 попыток дают 99%, но одна ошибка в десяти попытках меняет показатель сильнее, чем одна ошибка в миллионе. Поэтому рядом с SLI показывают число eligible, good и bad. Для пакетной обработки может быть важнее throughput или время завершения, а для интерактивного пути — latency и доля успешных исходов. Не нужно насильно сводить разные пользовательские задачи к одной цифре.</p>\n<h2>Посчитайте error budget на фиксированных данных</h2>\n<p>Error budget — это допустимая доля bad-событий в том же знаменателе и окне. В учебном наборе 1 000 eligible-событий, 994 good и 6 bad. При target 99% допустимы 10 bad-событий, поэтому условный остаток равен 4. Этот пример проверяет только арифметику; он не читает логи, не обращается к мониторингу и не сообщает состояние сервиса.</p>\n<pre><code>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-сервиса.</code></pre>\n<p>Сохраните содержимое блока в файл <code>sli-example.mjs</code> и выполните <code>node sli-example.mjs</code>. В рабочем отчёте нужно заранее договориться об округлении, если допустимое число bad-событий получается дробным. В примере явно выбран Math.round для целого числа событий; другая policy требует отдельного решения. Если <code>good</code> больше <code>eligible</code>, знаменатель нулевой или данные неполны, расчёт должен остановиться, а не выдавать красивый процент.</p>\n<p>Наивная замена good на «ответ не 5xx» опасна: запрос может быть принят, но пользователь ещё не получил результат. Обратная ошибка тоже возможна: клиентский fallback завершил путь, а серверная метрика записала ошибку. В обоих случаях сравните технический сигнал с событием, которое действительно закрывает пользовательскую попытку.</p>\n<h2>Свяжите цель с решением, а не с цветом</h2>\n<p>SLO полезен как часть петли управления: измерить SLI, сравнить его с SLO, оценить риск, выбрать действие и снова измерить. Error budget поставляет evidence для приоритизации, но не является универсальным приказом остановить изменения. В официальном примере Google policy расход бюджета определяет, когда надёжности нужно уделить больше внимания; конкретные исключения и полномочия остаются договорённостью команды.</p>\n<p>Алерт должен сообщать о действующей угрозе бюджету, а не о каждом колебании процента. Google отдельно разбирает precision, recall, detection time и reset time для SLO-алертов. Для low-traffic сервиса один отказ может дать огромную мгновенную долю и ложную срочность. Возможные решения — более длинное окно, искусственный трафик с оговоркой о его покрытии, объединение связанных потоков или изменение продукта так, чтобы единичный сбой меньше вредил пользователю. Ни один вариант нельзя выбрать без оценки конкретного пути.</p>\n<p>После срабатывания SLO-алерта не ищите причину в одной панели. OpenTelemetry разделяет traces, metrics и logs: trace показывает путь запроса, metric — измерение во времени, log — запись события. Эти сигналы помогают связать симптом с компонентом, но ни один из них сам по себе не доказывает причинность. Нужны исходное событие, граница времени и проверка гипотезы.</p>\n<h2>Проверьте отрицательный путь</h2>\n<ol><li><strong>Процент растёт после фильтрации ошибок.</strong> Сравните eligible до и после фильтра, разберите один спорный request_id и верните исключение в договор, если общего правила нет.</li><li><strong>Good равен любому ответу 2xx.</strong> Проследите путь клиента после ответа и отделите приём запроса от фактического пользовательского результата.</li><li><strong>Один отказ меняет решение о релизе.</strong> Покажите число eligible за окно и долю бюджета, которую потребляет одна ошибка; для малого потока оставьте сигнал диагностическим, если он не поддерживает действие.</li><li><strong>Событие не имеет request_id или потеряно.</strong> Не восстанавливайте успех по отсутствию записи. Исправьте источник, а неполное окно пометьте как непригодное для вывода.</li><li><strong>Good больше eligible или окно различается.</strong> Остановите расчёт, исправьте контракт и только потом сравнивайте отчёт с целью.</li></ol>\n<h2>Порядок внедрения</h2>\n<ol><li>Опишите один пользовательский путь и точку, после которой попытка считается eligible.</li><li>Назовите good event, приведите пример успеха, ошибки и спорного случая.</li><li>Зафиксируйте request_id, источник события, исключения, окно, target и владельца.</li><li>Посчитайте SLI и budget на фиксированном наборе, включая отрицательные входы.</li><li>Сверьте формулу с техническими proxy и явно опишите расхождение, если измерение заканчивается раньше пользовательского результата.</li><li>В разрешённой среде сравните отчёт и исходные события на нескольких окнах; не смешивайте учебные числа с наблюдением сервиса.</li><li>Добавьте правило реакции: кто проверяет сигнал, какое действие обратимо, когда пересматривается договор и что делать при споре о знаменателе.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта схема подходит для пути, где можно определить событие начала, события завершения и принадлежность к знаменателю. Она не заменяет модель latency, throughput, durability или стоимости. Для долгих пакетных процессов success ratio может скрыть время ожидания; для хранения данных одного успешного ответа мало, если важно долговременное сохранение. Для финансовой операции подтверждение приёма также не равно фактическому списанию — это отдельная граница и отдельный показатель.</p>\n<p>28-дневное окно и target 99% здесь учебные значения. Официальный пример Google показывает форму SLO-документа и четырёхнедельное rolling window, но не назначает такую длительность вашему сервису. Низкий трафик, внешняя зависимость, ретраи и частично потерянная телеметрия меняют интерпретацию. Если команда не может доказать полноту знаменателя, честный результат — «данных недостаточно», а не приблизительный SLI.</p>\n<h2>Критерий готовности</h2>\n<p>Договор можно передавать в работу, если независимый инженер без устных пояснений отвечает на семь вопросов: какой путь измеряется; что входит в eligible; что считается good; какие исключения действуют; за какое окно считается показатель; где лежат исходные события; кто и по какому правилу принимает решение. Для трёх заранее выбранных записей отчёт и проверочный запрос должны дать одинаковый результат. После изменения должны остаться обратимый шаг и команда, которой можно повторить проверку.</p>\n<p>Если ответа нет хотя бы на один вопрос, сначала исправьте границу и названия событий. Затем меняйте дашборд, алерт или policy. Такой порядок не даёт принять число, которое легко измерить, за результат, который действительно важен пользователю.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://sre.google/sre-book/service-level-objectives/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Book: Service Level Objectives</a> — определения SLI, SLO и SLA, выбор показателей, target и error budget; источник не назначает значения для конкретного продукта.</li><li><a href=\"https://sre.google/workbook/slo-document/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Workbook: Example SLO Document</a> — официальный пример scope, SLI, SLO и четырёхнедельного rolling window; это форма договора, а не универсальная норма.</li><li><a href=\"https://sre.google/workbook/error-budget-policy/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Workbook: Example Error Budget Policy</a> — пример связи error budget с приоритизацией надёжности и явно названными исключениями; не готовая policy для другой команды.</li><li><a href=\"https://sre.google/workbook/alerting-on-slos/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Workbook: Alerting on SLOs</a> — precision, recall, окна, burn rate и ограничения алертов для low-traffic систем.</li><li><a href=\"https://opentelemetry.io/docs/concepts/signals/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Signals</a> — официальные определения traces, metrics, logs и контекста сигналов; страница не доказывает состояние конкретного сервиса.</li></ul>"
|
||
}
|