Files

8 lines
22 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 &lt;= 0) {\n throw new Error('eligible must be a positive integer');\n}\nif (!Number.isInteger(good) || good &lt; 0 || good &gt; 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 &gt;= 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>"
}