diff --git a/editorial/agent-rewrites/149.json b/editorial/agent-rewrites/149.json index 0f9414b..bbef2b8 100644 --- a/editorial/agent-rewrites/149.json +++ b/editorial/agent-rewrites/149.json @@ -1,7 +1,7 @@ { "index": 149, "slug": "editorial-2023-11-mechanism-postmortem", - "title": "Postmortem без заднего знания: как связать факт, решение и действие", - "excerpt": "После сбоя команда легко принимает позднюю гипотезу за причину. Разбираем временную границу знания, контракт записей и проверяемый профилактический шаг.", - "contentHtml": "

После сбоя в чате появляется короткое объяснение: «релиз сломал обработку, поэтому инженер откатил его». В одной фразе смешаны событие, причина, решение и оценка. Но в момент отката команда могла не знать, был ли виноват релиз. Она могла видеть только рост ошибок и доступный способ остановить поток.

\n

Цена ошибки — не неточная формулировка. Команда ставит защиту вокруг самого заметного элемента истории. Она добавляет проверку к релизу, хотя сбой мог возникнуть из-за данных, лимита или внешней зависимости. Следующий разбор повторяет ту же подмену. Postmortem становится рассказом задним числом, а не инструментом изменения системы.

\n

Рабочая модель разделяет три записи: наблюдаемый факт, решение с доступной в тот момент информацией и будущую проверку гипотезы. Время ограничивает вывод. Поздний лог может объяснить событие, но не доказывает, что этот лог был доступен оператору при выборе. Так документ сохраняет неизвестное и показывает, какое действие нужно проверить.

\n

Граница между фактом и объяснением

\n

Факт описывает то, что можно привязать к источнику: время, сигнал, значение поля, изменение состояния. Он не обязан содержать причину. Запись «в 10:03 доля ответов 5xx превысила порог» сильнее записи «сервис упал из-за релиза», если связь с релизом ещё не проверена.

\n

Решение описывает действие и снимок доступных фактов. Его нельзя оценивать полным набором данных, который появился позже. Иначе документ наказывает человека за информацию, которой у него не было, и скрывает вопрос к системе: почему нужный сигнал, инструкция или безопасный способ остановки не были доступны раньше.

\n

Эксперимент переводит гипотезу в проверяемую работу. Он должен назвать один риск, способ проверки, критерий успеха и обратный путь. Фраза «добавить больше мониторинга» не даёт критерия. Фраза «для этого маршрута появляется alert при трёх последовательных ошибках, а дежурный подтверждает его в тестовом окружении» уже задаёт проверку формы. Она всё ещё не доказывает эффект в production.

\n
Как разобрать спорную фразу postmortem
СлойЧто записатьЧто не утверждать
ФактВремя, наблюдение и ссылка на лог, метрику или change.«Это точно причина» без проверки связи.
РешениеДействие и факты, доступные до него.Оценку через поздние данные.
ГипотезаКакой механизм нужно проверить.Причину, если она пока только предполагается.
ЭкспериментКритерий, владелец роли и rollback.Обещание предотвратить любой повтор.
\n

Механизм: время ограничивает допустимый вывод

\n

У каждой записи есть occurredAt. У решения есть availableFactIds. В список попадают только факты, которые уже существовали до решения. Это простое правило удерживает границу знания. Новая запись может изменить гипотезу о причине, но не меняет набор данных, на котором приняли исходное решение.

\n

Рассмотрим учебный пример. Он не читает реальные логи и не описывает настоящий инцидент. В нём зафиксированы три факта, решение остановить проверку изменения и эксперимент с обратным путём.

\n
{\n  \"facts\": [\n    {\"id\": \"f-01\", \"at\": \"10:00\", \"text\": \"доля ответов 5xx выросла\", \"source\": \"metric-card-01\"},\n    {\"id\": \"f-02\", \"at\": \"10:03\", \"text\": \"изменена версия конфигурации\", \"source\": \"change-02\"}\n  ],\n  \"decision\": {\n    \"at\": \"10:05\",\n    \"action\": \"остановить продвижение\",\n    \"availableFactIds\": [\"f-01\", \"f-02\"]\n  },\n  \"experiment\": {\n    \"hypothesis\": \"явная проверка версии сократит время обнаружения\",\n    \"successCriterion\": \"проверка видна в тестовом сценарии\",\n    \"rollback\": \"удалить проверку и вернуть прежнюю конфигурацию\"\n  }\n}
\n

Код показывает форму, а не результат. В реальном документе source должен указывать разрешённый артефакт, который команда действительно может открыть. Время должно использовать одну часовую зону. Если источник недоступен, это нужно записать как ограничение, а не заменить догадкой.

\n

Модель допускает, что причиной окажется не изменение версии. Например, поздняя проверка покажет исчерпанный лимит внешнего сервиса. Тогда факты и исходное решение остаются полезными. Меняется гипотеза и, возможно, эксперимент. Нельзя переписать факт так, чтобы он заранее подтверждал новую версию.

\n
\"Схема
Учебная схема границ postmortem: факты входят в решение только через доступную временную последовательность, а профилактика получает критерий и rollback. Рисунок не показывает настоящий инцидент.
\n

Симптом → причина → проверка → действие

\n
Диагностическая матрица для разбора
СимптомВероятная причина записиПроверкаДействие
В первом абзаце назван виновник.Имя человека используется как объяснение состояния.Убрать имя и спросить, какое условие системы нужно изменить.Записать владельца будущего действия отдельно от причины.
Решение выглядит очевидным после чтения всей timeline.К решению добавили факты, появившиеся позже.Сравнить время решения с каждым availableFactId.Оставить только предшествующие факты и сохранить unknown.
Action item звучит как «добавить мониторинг».Гипотеза не имеет измеримого критерия.Спросить, какой артефакт должен измениться и как увидеть проход.Указать сигнал, порог, владельца роли и rollback.
После исправления обещают отсутствие повторов.Учебная проверка выдана за production-результат.Найти источник эффекта и период наблюдения.Сузить вывод до «проверяет форму» или собрать реальные данные.
\n

Как писать решение без поиска виноватого

\n

Отсутствие поиска виноватого не отменяет ответственности. В документе должны быть владельцы действий, сроки и правила эскалации. Но роль владельца отвечает на вопрос «кто доведёт изменение», а не на вопрос «почему система оказалась в таком состоянии». Для второго вопроса нужны условия: доступный сигнал, версия инструкции, права, лимит, автоматическая защита или отсутствие безопасной остановки.

\n

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

\n

Отрицательный путь важен не меньше положительного. Если источник не открывается, поле остаётся неизвестным. Если rollback нельзя выполнить безопасно, эксперимент не готов. Если критерий нельзя проверить без production-доступа, нужно сначала спроектировать безопасную проверку или признать границу. Документ не должен заполнять пробелы уверенным тоном.

\n

Порядок действий

\n
  1. Запишите наблюдаемый симптом с временем, системой и доступным источником.
  2. Отделите факт от слов «вызвал», «из-за», «виноват» и «предотвратит». Эти слова требуют отдельного доказательства.
  3. Соберите решение как снимок: действие, время и полный список фактов, известных до выбора.
  4. Проверьте временной порядок и единую часовую зону. Удалите из контекста решения все поздние записи.
  5. Сформулируйте одну гипотезу о защите. Не превращайте список идей в план без критерия.
  6. Добавьте бинарный или наблюдаемый критерий, владельца роли, границы доступа и rollback.
  7. Проверьте учебную форму отдельно от production-эффекта. Успешная проверка JSON или документа не доказывает снижение числа инцидентов.
  8. Закройте разбор только после того, как читатель, не участвовавший в инциденте, сможет восстановить факт, решение и следующий проверяемый шаг.
\n

Ограничения

\n

Эта модель не заменяет incident command, расследование безопасности, юридическую оценку или правила хранения персональных данных. В security-контуре источники и доступы требуют отдельной политики. В распределённой системе часы могут расходиться, а источник может измениться после события. Тогда нужно хранить версию артефакта, часовой пояс и допустимый уровень точности.

\n

Три слоя не доказывают причинность. Они только не дают написать вывод шире доступных данных. Причинную связь проверяют отдельными методами: воспроизведением, сравнением изменений, экспериментом или анализом данных. Если эти методы недоступны, корректная формулировка — «причина не подтверждена».

\n

Учебный JSON выше фиксирует структуру и отрицательный путь. Он не запускается на настоящей инфраструктуре, не читает метрики и не измеряет влияние. Не переносите его идентификаторы, время и критерий в production без адаптации к своим источникам, ролям и процедурам отката.

\n

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

\n

Postmortem готов к техническому review, если независимый читатель может открыть источник каждого факта, увидеть, что решение ссылается только на предшествующую информацию, и проверить один профилактический эксперимент по его критерию. Если хотя бы один пункт не выполняется, статус должен быть «не готов», а следующий шаг — устранение конкретного пробела: источник, временная граница, критерий или rollback.

\n

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

\n" + "title": "Postmortem без поиска виноватого: как отделить факт от решения", + "excerpt": "Разбираем, как сохранить в postmortem границу знания: что команда видела до действия, какую гипотезу проверяет и как связать профилактику с измеримым критерием.", + "contentHtml": "

Проблема postmortem часто начинается с одной убедительной фразы: «релиз сломал обработку, поэтому инженер его откатил». В ней сразу смешаны событие, причина, решение и оценка человека. После инцидента команда уже знает больше, чем в момент отката, и легко принимает позднее объяснение за причину, которую можно было увидеть заранее.

\n

Такой разбор плохо меняет систему. Команда ставит новый алерт на самый заметный объект, хотя сбой мог вызвать лимит партнёра, неполный runbook или неизвестный формат данных. Следующий инцидент получает тот же набор объяснений. Рабочая модель должна разделить факт, решение в моменте и следующую проверяемую гипотезу.

\n

Что именно делает postmortem полезным

\n

Postmortem — это запись события, влияния, действий по смягчению и последующих изменений. Это не стенограмма поиска виноватого и не доказательство того, что одна правка устранит все будущие отказы. В официальном описании Google SRE цель такого документа — сохранить данные об инциденте, разобраться в способствующих причинах и поставить профилактические действия. Конкретный шаблон и пороги команда выбирает сама.

\n

Термин blameless здесь означает отсутствие обвинения людей за решения, принятые с доступной им информацией. Ответственность не исчезает: у каждого действия есть владелец, срок и критерий. Меняется предмет разговора. Вместо «кто допустил ошибку?» появляются вопросы «какой сигнал был доступен?», «какая инструкция действовала?» и «какое системное ограничение подтолкнуло к этому выбору?».

\n
Четыре слоя записи и допустимый вывод
СлойМинимальное содержимоеЧто можно утверждатьЧего пока нельзя утверждать
ФактВремя, наблюдение, источник и идентификатор.Событие зафиксировано указанным источником.Факт сам по себе доказывает причинность.
РешениеДействие и факты, доступные до него.Команда выбрала действие при данном контексте.Выбор был очевидным после появления поздних данных.
ГипотезаПредполагаемый механизм и способ проверки.Назван вопрос для следующего эксперимента.Гипотеза уже является подтверждённой причиной.
Action itemИзменение, владелец роли, критерий и откат.Понятно, какой артефакт должен измениться.Изменение гарантирует отсутствие повторения.
\n

Граница знания: решение нельзя оценивать задним числом

\n

Для каждого факта введём occurredAt и source. Для решения сохраним decidedAt и список availableFactIds. Правило простое: факт можно связать с решением только тогда, когда его время не позже времени решения. Если запись появилась после отката, она может изменить гипотезу о механизме, но не должна притворяться частью исходного контекста.

\n

Время нужно хранить в одной зоне или в явном формате UTC. Для распределённых систем одной отметки мало: полезно указать точность часов и версию источника. Если dashboard пересчитывает прошлый период, сохраните запрос или снимок, иначе читатель не восстановит, что именно было видно дежурному.

\n
\"Схема
Учебная схема: в решение входят только факты из его временного контекста, а впереди остаётся отдельный эксперимент с критерием и откатом. Иллюстрация не описывает настоящий инцидент.
\n

Воспроизводимый контракт записи

\n

Ниже — самостоятельный учебный пример. Он не читает логи, не обращается к сети и не доказывает, что версия конфигурации вызвала рост ошибок. Скрипт проверяет только внутреннее правило временной границы: ссылка решения не может указывать на факт из будущего.

\n
node <<'NODE'\nconst facts = [\n  { id: 'f-01', occurredAt: '2023-11-15T10:00:00Z', text: '5xx выше порога', source: 'metric-snapshot-01' },\n  { id: 'f-02', occurredAt: '2023-11-15T10:03:00Z', text: 'обновлена конфигурация', source: 'change-record-02' },\n  { id: 'f-03', occurredAt: '2023-11-15T10:06:00Z', text: 'лимит партнёра исчерпан', source: 'partner-status-03' },\n];\nconst decision = {\n  decidedAt: '2023-11-15T10:05:00Z',\n  action: 'остановить продвижение изменения',\n  availableFactIds: ['f-01', 'f-02'],\n};\nconst known = new Map(facts.map((fact) => [fact.id, fact]));\nconst future = decision.availableFactIds.filter((id) => {\n  const fact = known.get(id);\n  return !fact || fact.occurredAt > decision.decidedAt;\n});\nif (future.length) throw new Error('future facts: ' + future.join(', '));\nconsole.log('PASS: decision uses facts available at decision time');\nNODE\n\n# Ожидаемый вывод:\n# PASS: decision uses facts available at decision time
\n

В примере f-03 появляется в 10:06 и намеренно не входит в решение в 10:05. Позднее он может поддержать другую гипотезу — например, что главным ограничением был внешний лимит. Но это не превращает исходный откат в ошибку и не позволяет переписать его контекст задним числом.

\n

Проверка не валидирует правдивость источников. Строка source лишь связывает запись с ожидаемым артефактом. На рабочем проекте нужно отдельно проверить доступ, сохранность и авторство метрики, change record или лога. Идентификаторы из примера нельзя переносить в production.

\n

Как не перепутать корреляцию с причиной

\n

Временная последовательность сужает поиск, но не устанавливает причинность. Если конфигурация изменилась перед ростом 5xx, остаются альтернативы: тот же период мог совпасть с пиком трафика, исчерпанием квоты или изменением у партнёра. В postmortem полезно хранить эти альтернативы, пока проверка не отсеет их.

\n
От симптома к проверке без скачка к verdict
НаблюдениеГипотезаПроверкаДействие после результата
5xx выросли сразу после изменения.Изменение несовместимо с частью входных данных.Сравнить фиксированный набор запросов до и после, не используя персональные данные.Добавить тест на найденный формат или отклонить гипотезу.
Ошибки совпали с ростом трафика.Ресурсный лимит ниже фактической нагрузки.Сверить rate, quota и saturation в одном временном окне.Ограничить нагрузку или изменить capacity после review.
Дежурный узнал о сбое вручную.Сигнал не покрывает пользовательский симптом.Проверить alert на синтетическом сценарии и его маршрут доставки.Изменить сигнал, порог или инструкцию, сохранив rollback.
Источник появился после решения.Причина реконструирована позднее.Убрать ссылку из контекста решения и обозначить unknown.Поставить отдельный эксперимент для проверки механизма.
\n

Формулировка «причина не подтверждена» не является провалом документа. Это точная граница знания. Неподтверждённая причина лучше красивого, но ложного вывода: она показывает, какую телеметрию, тест или безопасный эксперимент нужно добавить.

\n

Action item должен менять систему

\n

«Добавить мониторинг» — намерение, а не действие. Запись становится проверяемой, если в ней названы изменяемый артефакт, владелец роли, критерий и обратный путь. Например: «до следующего тестового релиза добавить alert на долю 5xx для маршрута X; критерий — synthetic-запрос вызывает сигнал за пять минут; откат — удалить правило и вернуть прежний порог». Такой пункт проверяет наличие защиты, но ещё не доказывает снижение production-инцидентов.

\n

Полезно разделить исправление и измерение. Исправление меняет код, конфигурацию, runbook или процесс. Измерение показывает, сработала ли защита в выбранном окне. Для измерения нужно назвать baseline, вариант сравнения и длительность наблюдения. Без них фраза «ошибок стало меньше» неотличима от впечатления.

\n

У каждого action item есть стоимость и риск. Алерт дешевле изменения протокола, но создаёт шум, если команда не определила владельца реакции. Регрессионный тест ловит известный класс входа раньше production, но не покрывает неизвестный внешний отказ. Перечислите альтернативы и выберите одну по риску, обратимости и доступному доказательству.

\n

Порядок разбора

\n
  1. Запишите влияние на пользователя и наблюдаемый симптом: маршрут, период, измерение и источник.
  2. Соберите timeline из неизменяемых или версионируемых артефактов. Для каждой записи сохраните временную зону и точность.
  3. Для каждого решения укажите только факты, появившиеся до действия. Поздние находки поместите в отдельный блок.
  4. Назовите несколько правдоподобных механизмов и проверку, которая различает их.
  5. Сформулируйте одно действие на один системный пробел: сигнал, тест, лимит, документация или безопасный rollback.
  6. Добавьте владельца роли, срок, критерий успеха, область действия и способ отмены.
  7. Проведите review с человеком, который не участвовал в инциденте. Он должен найти источник факта и восстановить ход решения.
  8. После выполнения action item вернитесь к критерию и запишите измеренный результат или сохраните статус «не проверено».
\n

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

\n

Эта модель не заменяет incident command, расследование безопасности, юридическую оценку или правила работы с персональными данными. В security-контуре доступ к логам, копирование доказательств и сроки хранения задаются отдельной политикой. Руководство NIST SP 800-61 Rev. 2 относится к компьютерным инцидентам безопасности и не является универсальным шаблоном для любой деградации продукта.

\n

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

\n

Blameless-подход также не отменяет расследование умышленного нарушения, контроля доступа или требований закона. Он отвечает на другой вопрос: как извлечь технический урок из условий, в которых система и люди действовали. Для дисциплинарных и юридических решений нужна отдельная процедура с соответствующими полномочиями.

\n

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

\n

Разбор можно отдавать на технический review, если независимый читатель открывает источник каждого факта, видит границу между временем решения и поздними находками, понимает, какая гипотеза ещё не доказана, и может проверить один action item по критерию и rollback. Если не хватает источника, временной связи или способа измерения, статус должен оставаться «не готов», а следующий шаг — устранение конкретного пробела.

\n

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

\n" } diff --git a/editorial/agent-rewrites/150.json b/editorial/agent-rewrites/150.json index dcdbcd2..eedf98f 100644 --- a/editorial/agent-rewrites/150.json +++ b/editorial/agent-rewrites/150.json @@ -1,7 +1 @@ -{ - "index": 150, - "slug": "editorial-2023-11-practice-postmortem", - "title": "Postmortem, который помогает исправить систему", - "excerpt": "Как отделить наблюдаемые факты от поздних объяснений, связать решение с доступной информацией и превратить профилактику в проверяемое действие.", - "contentHtml": "

После сбоя команда обычно помнит две вещи: какой сигнал сработал и кто последним менял систему. На встрече эти детали быстро превращаются в объяснение: «ошибка произошла из-за этого изменения». Такой вывод может быть неверным. Он смешивает факт, решение и гипотезу о причине.

\n

Цена ошибки высока. Команда тратит время на защиту вокруг случайного признака, а настоящий механизм остаётся без проверки. Следующий дежурный получает документ с обвинением и общим советом «быть внимательнее». Похожий сбой повторяется, но его уже труднее разобрать: нужные логи истекли, контекст решения забыт, а исправление объявили готовым без измерения.

\n

Рабочая схема проста: сначала записать симптом и его цену, затем собрать факты с источниками, отдельно описать решение в моменте и только после этого сформулировать небольшую профилактическую проверку. Postmortem не должен угадывать причину по одной строке лога. Он должен показывать, что известно, чего не известно и какое действие уменьшит неопределённость.

\n

Три разных типа записи

\n

Факт отвечает на вопрос «что было зафиксировано и где это видно?». Это узкое утверждение с временем и ссылкой на разрешённый артефакт: лог, trace, метрику, версию конфигурации или запись мониторинга. Фраза «в 10:03 запросы к маршруту получили 502» может быть фактом, если рядом есть запрос к источнику и задано окно времени.

\n

Решение отвечает на вопрос «что команда сделала, имея такую информацию?». В него входят время, действие и список фактов, доступных до действия. Поздний trace или результат расследования нельзя добавлять в этот список задним числом. Он помогает понять выбор, но не доказывает, что выбор был правильным или ошибочным.

\n

Профилактическая проверка отвечает на вопрос «что мы проверим, чтобы уменьшить риск повторения?». В ней нужны гипотеза, минимальный метод, бинарный критерий и обратимый путь. До прогона это намерение проверить, а не доказательство того, что новый alert, лимит или тест уже защитил пользователей.

\n
Не смешивайте записи в одной строке
ТипВопросМинимальные поляНельзя выводить
ФактЧто зафиксировано?время, наблюдение, ссылка на источниквиновника и root cause
РешениеЧто выбрали тогда?время, действие, доступные fact IDоценку задним числом
ПроверкаЧто проверим дальше?гипотеза, метод, критерий, rollbackреальный эффект до измерения
ДействиеКто доведёт работу?владелец роли, срок, ссылка на проверкуобещание устранить все риски
\n

Механизм на маленьком примере

\n

Представим учебный инцидент в HTTP-сервисе. После релиза доля ответов 502 выросла. Дежурный откатил конфигурацию таймаута. Через несколько минут доля ошибок снизилась. Этого недостаточно, чтобы написать «новый таймаут был причиной». За это время могли исчезнуть входной всплеск, зависший upstream или другая ошибка маршрутизации.

\n

Сначала запишите наблюдения:

\n
const facts = [\n  { id: 'f-1', at: '10:03', text: 'gateway reported 502 for /checkout', source: 'metric:gateway_5xx' },\n  { id: 'f-2', at: '10:04', text: 'timeout config was version 17', source: 'config:checkout@17' },\n  { id: 'f-3', at: '10:05', text: 'on-call restored version 16', source: 'change:rollback-482' },\n];\n\nconst decision = {\n  at: '10:05',\n  action: 'restore checkout config to version 16',\n  availableFactIds: ['f-1', 'f-2'],\n};\n\nconst check = {\n  hypothesis: 'the timeout change contributes to the 502 path',\n  method: 'replay the same request class with versions 16 and 17',\n  criterion: 'both outcomes and upstream status are captured',\n  rollback: 'keep version 16 and stop the replay if error rate rises',\n};
\n

Пример учебный. Он не читает настоящие метрики, не запускает rollback и не доказывает связь между таймаутом и 502. Его польза в форме: у решения видны только два доступных факта, а проверка имеет отдельный критерий и остановку. Если команда позже найдёт новый trace, его добавят в факты и пересмотрят гипотезу, но не перепишут историю доступной информации.

\n

Нужна и отрицательная ветка. Если в карточке факта появилось поле rootCause, система или ревью должны остановить запись. Если решение ссылается на факт, который возник позже, его нельзя считать контекстом решения. Если проверка говорит «сбой больше не повторится», но не называет вход, окно и измерение, это обещание, а не критерий.

\n

Симптом → причина → проверка → действие

\n
Маршрут разбора
СимптомВероятная причина смешенияПроверкаДействие
В черновике есть имя инженера, но нет источниковоценка человека заменяет анализ условийнайти timestamp и evidence для каждой фразыубрать имя из объяснения, добавить владельца следующего действия
«Релиз вызвал ошибку» написано как фактгипотеза попала в timelineсравнить время релиза, симптома и альтернативные измененияпометить связь как непроверенную и сформулировать эксперимент
Action item звучит как «добавить мониторинг»нет сценария и порога срабатыванияназвать вход, сигнал, окно и ожидаемое значениесделать критерий бинарным и указать обратное действие
После отката написано «проблема решена»снижение симптома приняли за доказательство причинысопоставить ошибку с upstream, версиями и временемописать откат как mitigation, а причину оставить открытой
Документ нельзя проверить через неделюв нём остались воспоминания без артефактовпроверить каждое утверждение по ссылке и сроку хранениясохранить минимальный разрешённый evidence или отметить пробел
\n

Иллюстрация временной границы

\n
\"Учебная
Учебная схема: факты стоят до решения, а профилактическая проверка — после него. Она не представляет настоящий инцидент и не показывает production-метрики.
\n

Временная граница нужна не для бюрократии. Она защищает от hindsight bias: после сбоя команда видит больше, чем видела в момент действия. Поэтому в записи решения храните не весь итоговый материал, а именно набор сведений, который мог повлиять на выбор. Если действие необратимо, отдельно запишите владельца точки возврата и сигнал остановки.

\n

Порядок работы

\n
  1. Опишите симптом. Укажите затронутый путь, окно времени, наблюдаемый сигнал и цену ошибки: недоступность операции, потерю данных, ручное восстановление или задержку.
  2. Зафиксируйте факты. Для каждой строки назовите источник, время и точную формулировку. Не добавляйте в факт причину, виновника или эффект, которого источник не измеряет.
  3. Восстановите контекст решения. Запишите действие, доступные fact ID, обратимость и сигнал, по которому команда решала продолжать или остановиться.
  4. Разделите mitigation и cause. Откат, переключение трафика или отключение функции может убрать симптом. Это ещё не доказательство механизма сбоя.
  5. Сформулируйте одну гипотезу. Укажите конкретный вход, способ проверки и альтернативу. Не начинайте с общего «повысить надёжность».
  6. Задайте критерий. Критерий должен приводить к PASS или FAIL и ссылаться на измеримый артефакт. Добавьте rollback и условие остановки.
  7. Проверьте документ. Уберите фразы, которые шире источника. Отдельно перечислите открытые вопросы и назначьте владельца только для следующей проверяемой работы.
\n

Ограничения

\n

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

\n

Blameless не означает «никто ни за что не отвечает». Документ должен содержать владельца действия, срок и критерий завершения. Он также должен фиксировать небезопасное изменение, нарушенный контроль или отсутствие доступа к сигналу, если это подтверждено. Не следует приписывать человеку мотив или использовать его имя как техническую причину.

\n

Учебный код выше не подключается к сети, CI, логам, alert-системе или production runtime. Его нельзя выдавать за результат прогона. В реальной системе доступ к incident data, приватность, retention и право публикации требуют отдельной проверки. Если источник недоступен, честная запись — «не проверено», а не правдоподобная реконструкция.

\n

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

\n

Postmortem готов к разбору, когда другой инженер может пройти его без устного пересказа:

\n\n

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

\n

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

\n" -} +{"index":150,"slug":"editorial-2023-11-practice-postmortem","title":"Postmortem, который помогает исправить систему","excerpt":"Практическая схема разбора сбоя: отделяем наблюдение от гипотезы, сохраняем контекст решения и превращаем профилактику в проверяемое действие.","contentHtml":"

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

\n

Цена такого postmortem — не только повторный простой. В документе теряется контекст: какие сигналы были доступны в момент решения, почему выбрали откат, какие данные ещё не проверены и что именно должно изменить систему. Обсуждение смещается к имени последнего автора изменения, а не к слабому контролю, неясному лимиту или отсутствующему сигналу.

\n

Ниже — практический маршрут для небольшого HTTP-инцидента. Он не выдаёт учебный сценарий за production-расследование. Его задача — помочь записать факты, связать действие с доступной информацией и назначить следующую проверку так, чтобы другой инженер мог воспроизвести её без устного пересказа.

\n

Начните с симптома и цены ошибки

\n

Первая запись должна описывать наблюдаемое событие, а не объяснение. Назовите путь или компонент, окно времени, сигнал и последствия для пользователя. «С 10:03 до 10:08 шлюз вернул 502 на 18% запросов к /checkout» уже можно проверять по метрике. «Новый таймаут сломал оплату» — пока гипотеза.

\n

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

\n
Четыре записи, которые нельзя смешивать
ЗаписьНа какой вопрос отвечаетМинимальные поляГраница вывода
СимптомЧто увидел пользователь или мониторинг?путь, окно, сигнал, воздействиене объясняет причину
ФактЧто подтверждено источником?timestamp, наблюдение, ссылка на лог, метрику или конфигурациюне назначает виновника
РешениеЧто сделали с доступной информацией?время, действие, список доступных fact ID, обратимостьне доказывает, что решение устранило причину
ПроверкаКак отделить гипотезу от альтернативы?вход, метод, критерий PASS/FAIL, остановка, владелецне обещает результат до прогона
\n

Сохраните временную границу

\n

В postmortem полезно различать две временные шкалы. Первая — события системы: ошибка, изменение конфигурации, запрос к upstream, откат. Вторая — знания команды: что было видно до решения и что выяснилось уже после. Поздний trace может объяснить механизм, но не был основанием для решения, принятого пять минут раньше.

\n
\"Учебная
Учебная схема временной границы: решение связано только с фактами, доступными до него; эксперимент проверяет гипотезу после временного восстановления.
\n

Для каждого события храните источник и время, а для решения — список фактов, которыми дежурный действительно располагал. Это защищает от hindsight bias, то есть от подмены прежней неопределённости знанием, появившимся позже. Так команда оценивает не личную «внимательность», а качество сигналов и доступных процедур.

\n

Разберите учебный HTTP-инцидент

\n

Возьмём изолированный пример. В 10:03 шлюз заметил рост 502 на /checkout. В 10:04 команда увидела, что сервис использует версию конфигурации 17 с новым таймаутом. В 10:05 дежурный вернул версию 16. В 10:08 доля 502 снизилась. Последнее наблюдение подтверждает эффект отката во времени, но не доказывает, что именно таймаут был единственной причиной: мог закончиться всплеск нагрузки, восстановиться upstream или исчезнуть другая ошибка маршрутизации.

\n

Следующий фрагмент можно запустить локально: ему нужен только Node.js. Он проверяет узкое правило — решение не может ссылаться на факт, появившийся позже. Команда не обращается к логам и не меняет конфигурацию, поэтому результат запуска не является доказательством состояния настоящего сервиса.

\n
node <<'NODE'\nconst facts = [\n  { id: 'f-1', at: '2023-11-07T10:03:00Z', text: 'gateway returned 502 for /checkout', source: 'metric://gateway-5xx' },\n  { id: 'f-2', at: '2023-11-07T10:04:00Z', text: 'checkout config is version 17', source: 'config://checkout/17' },\n  { id: 'f-3', at: '2023-11-07T10:08:00Z', text: '502 share fell after rollback', source: 'metric://gateway-5xx' },\n];\nconst decision = {\n  at: '2023-11-07T10:05:00Z',\n  action: 'restore checkout config to version 16',\n  availableFactIds: ['f-1', 'f-2'],\n};\n\nconst byId = new Map(facts.map((fact) => [fact.id, fact]));\nfor (const factId of decision.availableFactIds) {\n  const fact = byId.get(factId);\n  if (!fact || new Date(fact.at) >= new Date(decision.at)) {\n    throw new Error('Fact ' + factId + ' was not available at decision time');\n  }\n}\nconsole.log('PASS: decision uses only earlier facts');\nconsole.log(JSON.stringify({ facts, decision }, null, 2));\nNODE
\n

В shell этот блок завершится строкой PASS и напечатает запись. Если заменить список availableFactIds на ['f-1', 'f-3'], проверка завершится ошибкой: факт о снижении ошибок возник после решения. Это и есть полезный отрицательный тест. Он не доказывает root cause, зато не позволяет задним числом приписать дежурному знание, которого у него не было.

\n

Для реального расследования к этой структуре добавьте идентификатор инцидента, часовой пояс, единицы измерения и ссылки с понятным сроком хранения. Источник должен вести к разрешённому артефакту. Ссылка на поиск в логах без фиксированного окна и запроса через неделю может вернуть уже другой результат.

\n

Не принимайте откат за доказанную причину

\n

Откат — это mitigation: действие, которое уменьшает воздействие прямо сейчас. Причина — объяснение механизма, подтверждённое несколькими наблюдениями или безопасным экспериментом. Между ними может быть связь, но её нельзя объявлять установленной только потому, что симптом ослаб после отката.

\n
Маршрут от наблюдения к следующему действию
НаблюдениеЧто пока нельзя утверждатьПроверкаДействие и критерий
502 вырос после релизарелиз — единственная причинасопоставить версии, трафик, upstream-коды и соседние изменения в одном окнесохранить откат как mitigation; PASS — все сравниваемые источники согласованы
ошибки снизились после возврата конфигурацииновый таймаут доказан как root causeповторить на тестовом трафике тот же класс запросов с версиями 16 и 17остановить эксперимент при росте 5xx; PASS — записаны ответ шлюза и статус upstream для каждой попытки
в логе есть имя инженерачеловек объясняет техническую причинупроверить, какой контроль разрешил изменение и какая информация была доступназаменить имя в причинной цепочке на изменение, контроль и владельца follow-up
в отчёте написано «добавить мониторинг»новый сигнал предотвратит повторназвать маршрут, окно, порог, канал и тест тревогиPASS — тестовый сигнал срабатывает на искусственном нарушении; FAIL — открыть действие с rollback
\n

Гипотеза должна быть узкой: «при одинаковом классе запроса версия 17 увеличивает долю 502 из-за таймаута ожидания upstream». Она лучше общего «конфигурация ненадёжна», потому что задаёт входы и наблюдения. Альтернатива тоже нужна: например, «502 вызван исчерпанием соединений независимо от таймаута». Иначе эксперимент будет подтверждать любимое объяснение, а не различать варианты.

\n

Сделайте action item проверяемым

\n

Строка «разобраться с таймаутами» не является планом. Хорошая corrective action меняет контроль или код и имеет наблюдаемый критерий. В ней должны быть один владелец, срок, ссылка на место изменения, способ проверки и обратное действие. Если результат нельзя выразить через PASS/FAIL, сначала уточните, какой артефакт должен появиться.

\n
Action: add a bounded upstream-timeout regression test\nOwner: checkout-service team\nInput: the recorded /checkout request class from incident INC-482\nPASS: versions 16 and 17 produce captured gateway and upstream statuses;\n      the test fails when the timeout exceeds the contract.\nRollback: keep version 16 until the test and staged replay are green.\nEvidence: test run URL + configuration revision + timestamp.
\n

Пример описывает будущую проверку, а не уже достигнутый результат. До запуска нельзя писать «тест защитил пользователей». После запуска сохраните фактический статус, версию теста и условия прогона. Если staged replay не повторяет production-нагрузку или upstream недоступен, результат ограничен именно этой средой.

\n

Выберите момент для postmortem

\n

Postmortem стоит запускать по заранее известным признакам, а не только по субъективному ощущению масштаба. Google SRE перечисляет среди типовых триггеров заметную деградацию для пользователей, потерю данных, вмешательство дежурного вроде отката или перенаправления трафика, длинное восстановление и отказ мониторинга. Это ориентир, а не обязательный порог для каждой команды: его нужно сопоставить с риском сервиса, договорённостями об уровне доступности и правилами приватности.

\n

До инцидента зафиксируйте, кто открывает документ, где лежит timeline, кто делает технический review и что считается закрытым follow-up. После инцидента сначала сохраните артефакты с нужным сроком хранения, затем попросите review у людей, которые могут проверить полноту воздействия, глубину причины и реалистичность действий. Не закрывайте запись лишь потому, что пользовательский симптом исчез.

\n

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

\n

Blameless не означает отсутствие ответственности. Такой подход убирает обвинение человека из причинной цепочки, но оставляет владельца действия, срок, контроль и критерий завершения. Если изменение нарушило правило доступа или процесс, документируйте сам факт нарушения и слабость контроля; не приписывайте мотив, не публикуйте лишние персональные данные и не превращайте имя в техническое объяснение.

\n

В распределённой системе один trace не равен полной истории. Сообщение могло потеряться до входа в сервис, часы на узлах могли расходиться, а метрика шлюза могла не учитывать ошибки до него. Откат может убрать симптом и оставить повреждённые данные. Поэтому для операций записи отдельно проверяйте целостность, повторяемость и идемпотентность; для приватных инцидентов — доступ, retention и допустимый объём публикации.

\n

Учебный Node.js-код проверяет только порядок двух timestamp. Он не подключается к HTTP, не читает production-метрики, не выполняет откат и не устанавливает причинность. Приведённые имена маршрута, версии и проценты вымышлены для воспроизводимости. В своём проекте замените их реальными источниками и укажите версию среды: синтетический прогон на Node.js не подтверждает поведение конкретного шлюза, базы данных или провайдера.

\n

Проверьте документ перед разбором

\n
  1. Назовите симптом. Запишите путь, окно, единицы измерения и воздействие. Отделите неизвестное от нулевого значения.
  2. Соберите факты. Привяжите каждое наблюдение к источнику, timestamp и сроку хранения. Исправьте часовые пояса.
  3. Восстановите решение. Укажите только те fact ID, которые существовали до действия, и опишите его обратимость.
  4. Разделите временное и постоянное. Отметьте откат или переключение как mitigation, а root cause оставьте гипотезой, пока её не подтверждает проверка.
  5. Оформите follow-up. Добавьте узкую гипотезу, вход, метод, PASS/FAIL, условие остановки, владельца и evidence.
  6. Проведите review. Попросите другого инженера пройти timeline и выполнить локальный пример с отрицательной веткой.
  7. Закройте действие по факту. Сохраните результат прогона, ссылку на изменение и остаточный риск. Если критерий не выполнен, это FAIL, а не «почти готово».
\n

Готовность postmortem — это не обещание, что инцидент больше не повторится. Более узкий и честный критерий таков: инженер, не участвовавший в сбое, видит, что произошло, что было известно в момент решения, какое объяснение ещё проверяется и какое действие даст следующий измеримый сигнал.

\n

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

\n"} diff --git a/editorial/agent-rewrites/151.json b/editorial/agent-rewrites/151.json index ac683e0..67404fd 100644 --- a/editorial/agent-rewrites/151.json +++ b/editorial/agent-rewrites/151.json @@ -3,5 +3,5 @@ "slug": "editorial-2023-10-field-sli-slo", "title": "SLI/SLO в релизном разговоре: от красного графика к проверяемому решению", "excerpt": "Как связать SLI, SLO и error budget с пользовательским путём, окном измерения и обратимым действием — и не выдать один график за доказательство инцидента или автоматический запрет релиза.", - "contentHtml": "

В день релиза на панели краснеет error budget. Один инженер говорит: «бюджет почти закончился». Другой просит не задерживать исправление. On-call не может показать, какой пользовательский путь пострадал, за какой период считался показатель и какие события попали в знаменатель. Команда спорит о цвете, а не о данных.

\n

Цена ошибки двойная. Шумный сигнал может остановить безопасное изменение. Слишком узкий SLI может пропустить отказ после точки измерения, и команда выпустит рискованный релиз. В обоих случаях SLO превращается в отчётность: число есть, но оно не подсказывает следующий безопасный шаг.

\n

Тезис статьи простой: error budget не принимает решение вместо команды. Он запускает проверяемую петлю. Сначала нужно подтвердить договор SLI: что измеряем, для кого, в каком окне и по какой формуле. Затем нужно проверить контекст и выбрать действие по policy. Только после этого можно обсуждать rollout, паузу или исправление.

\n

Механизм: сигнал, цель, бюджет и policy

\n

SLI — количественная мера свойства сервиса. Например, доля запросов, которые завершились полезным результатом, или доля операций с задержкой ниже порога. SLO — целевое значение этой меры в заданных условиях. Error budget — допустимая часть неуспеха в том же договоре. Если target равен 99%, бюджет равен 1% eligible-событий за указанное окно.

\n

Формула сама по себе ничего не решает. Для success ratio нужны как минимум scope, eligible count, good count, target и window. Scope задаёт путь пользователя и границу ответственности. Eligible определяет знаменатель. Good определяет успешный исход. Window задаёт период сравнения. Policy связывает состояние бюджета с действием и владельцем.

\n
eligible = 1000\ngood = 994\ntarget = 0.99\nactual = good / eligible       // 0.994\nallowed_bad = eligible * (1 - target) // 10\nactual_bad = eligible - good          // 6\nremaining = allowed_bad - actual_bad  // 4\n\n// Все числа учебные. Источник событий отсутствует.\n// Результат не описывает production-доступность.
\n

В этом примере остаются четыре условные единицы бюджета. Это арифметика модели, а не факт о сервисе. Если исключить отменённые операции, изменить окно или считать только ответы одного backend, результат станет другим. Поэтому процент без версии договора нельзя сравнивать с прошлым процентом и нельзя использовать как самостоятельную причину для блокировки.

\n

Почему scope важнее красивого процента

\n

Пользователь оценивает путь, а не внутренний HTTP-ответ. Запрос может получить код 202, но очередь позже отклонит операцию. Backend может ответить быстро, пока клиент ждёт подтверждение в другом компоненте. Если SLI измеряет только первый ответ, он может быть технически точным и продуктово бесполезным.

\n

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

\n

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

\n

Окно определяет, чему доверять

\n

Фиксированное окно проще объяснить: события с 1 по 28 число сравниваются с предыдущим таким же периодом. Скользящее окно быстрее показывает недавнее ухудшение, но каждый момент измерения содержит немного иной набор событий. В обоих вариантах нужно назвать часовой пояс, границы, задержку поступления событий и правило пересчёта.

\n

Низкий трафик усиливает цену одного отказа. При десяти eligible-событиях один failure меняет ratio сильнее, чем при миллионе. Это не означает, что малотрафиковый путь нельзя измерять. Это означает, что порог, окно и способ реакции надо выбирать вместе. Иногда полезнее ticket и ручной разбор, чем срочное оповещение на каждое колебание.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Процент стал краснымНеизвестны окно и версия формулыСверить SLI-contract, границы и периодПриостановить интерпретацию, запросить источник
Команды считают доступность по-разномуРазные eligible и goodСравнить запросы, исключения и отрицательные случаиЗафиксировать одну формулу и владельца
Процент хороший, путь сломанSLI измеряет ранний backend-ответПройти пользовательский сценарий до результатаРасширить scope или добавить отдельный SLI
Один отказ резко изменил ratioМалое окно или низкий трафикПосчитать eligible и проверить распределение событийВыбрать устойчивое окно и ручной response
Красный график блокирует любой релизУ policy нет исключений и владельцаПроверить обратимость, срочность и evidenceСузить rollout, исправить, отложить или продолжить по policy
\n
\"Петля
Петля решения отделяет измерение от причины и ручного решения. Иллюстрация показывает учебную схему, а не мониторинг, CI-gate или журнал инцидента.
\n

Пример policy для релизного разговора

\n

Policy должна отвечать на пять вопросов. Какое состояние бюджета запускает разбор? Какие данные обязан принести владелец? Какое действие обратимо? Кто принимает решение? Когда команда пересматривает договор? Запись «при красном графике остановить всё» не отвечает ни на один вопрос до конца.

\n

Практичная ветка может выглядеть так: если формула или scope неизвестны, решение откладывают до проверки данных. Если договор подтверждён, но причина неясна, открывают разбор и уменьшают exposure рискованного изменения. Если budget exhausted, non-urgent rollout приостанавливают, а обязательное исправление оценивают отдельно с владельцем и планом отката. Если сигнал восстановился, повторяют ту же проверку; новый процент не должен появиться из другой формулы.

\n

Такая policy не запрещает каждый релиз. Она не разрешает и каждый релиз. Она задаёт минимальное evidence и оставляет полномочие у владельца. Security fix, изменение инфраструктуры и продуктовый rollout могут иметь разные уровни срочности, поэтому один универсальный gate создаёт ложную уверенность.

\n

Порядок действий

\n
  1. Сформулируйте наблюдаемую проблему: какой пользовательский путь, симптом и цена ошибки обсуждаются.
  2. Зафиксируйте версию SLI-contract: scope, eligible, good, exclusions, target, window и источник событий.
  3. Пересчитайте показатель на небольшом проверяемом наборе и добавьте отрицательный пример. Если две команды получают разные значения, сначала устраните расхождение.
  4. Отделите сигнал от причины. В разрешённой среде проверьте версию, зависимость, класс ответов, задержку, очередь или данные, которые связаны с тем же периодом.
  5. Выберите действие по policy: исправить причину, сузить rollout, отложить несрочное изменение или продолжить с явным контролем.
  6. Назначьте владельца и план обратного действия. Укажите, что вернуть, кто это сделает и каким наблюдением подтвердить результат.
  7. После действия примените ту же формулу и критерий. Если результат не изменился, пересмотрите гипотезу, а не denominator.
\n

Ограничения отрицательного пути

\n

Один SLI не объясняет корневую причину. Trace, log и dashboard помогают только тогда, когда они относятся к той же операции, версии и периоду. Корреляция не доказывает причинность. Красный budget не доказывает инцидент. Зелёный budget не доказывает, что весь пользовательский путь работает.

\n

Учебная арифметика выше не читает файлы, часы, monitoring, CI, сеть, HTTP или production-конфигурацию. Она не создаёт alert, не меняет rollout и не выдаёт разрешение на выпуск. В реальной системе эти полномочия должны находиться в явно назначенных инструментах и runbook. Если команда не может проверить источник или обратить действие, это ограничение нужно записать до решения.

\n

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

\n

Разбор готов, когда другой инженер без устного контекста может воспроизвести число и понять решение. В записи есть пользовательский путь, версия формулы, eligible и good, target, окно, источник, владелец, выбранное действие, план отката и повторная проверка. Есть хотя бы один отрицательный пример: событие, которое нельзя молча исключить, или путь, который текущий SLI не покрывает.

\n

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

\n

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

" + "contentHtml": "

На релизе график error budget становится красным. Один инженер предлагает остановить выкладку, другой просит не задерживать исправление. Но никто не может сразу ответить, какой пользовательский путь измеряет график, какие события входят в знаменатель и за какое окно посчитан расход. В итоге команда обсуждает цвет панели вместо проверяемого факта.

\n

Ошибка в такой ситуации стоит дорого в обе стороны. Слабый SLI может оставить сломанным путь, который сервер формально считает успешным. Нечёткая policy может остановить безопасное изменение из-за одного нерепрезентативного всплеска. Поэтому SLO полезен не как печать «можно» или «нельзя», а как часть петли: измерили, сравнили с целью, проверили контекст, выбрали действие, повторили измерение.

\n

Ниже — практический способ провести этот разговор. Сначала зафиксируем контракт показателя, затем разберём знаменатель и окно, после чего превратим расход бюджета в ограниченное и обратимое решение. Все числа в примерах учебные: они показывают арифметику и порядок проверки, но не описывают конкретную production-систему.

\n

Что именно измеряют SLI, SLO и error budget

\n

SLI (service level indicator) — количественная мера свойства сервиса: например, доля успешных запросов или доля запросов, завершившихся быстрее порога. SLO (service level objective) — целевое значение SLI при явно названных условиях. Error budget — допустимая часть неуспеха за то же окно. Для цели 99% это 1% событий, которые могут не соответствовать критерию, если договор считает их одинаково.

\n

Минимальный контракт должен назвать пользовательский scope, способ измерения, eligible-события, good-события, target и window. Scope говорит, какой результат защищаем. Eligible задаёт знаменатель. Good задаёт числитель. Window задаёт период, в котором результат сравнивают с целью. Policy добавляет владельца и действие, но не меняет саму формулу.

\n
eligible = 1000\ngood = 994\ntarget = 0.99\nactual = good / eligible                  // 0.994 = 99.4%\nallowed_bad = eligible * (1 - target)      // 10\nactual_bad = eligible - good               // 6\nremaining_bad_capacity = allowed_bad - actual_bad // 4\n\n# Учебные числа: здесь нет источника событий и реального окна.
\n

В учебной модели осталось место ещё для четырёх неуспешных событий до цели 99%. Это не означает, что сервис «на 99,4% надёжен» во всех смыслах: код 202 может лишь поставить операцию в очередь, а клиентский JavaScript может сломаться после ответа API. Если изменить exclusions, источник или границу завершения, изменятся eligible и good, а вместе с ними — весь вывод.

\n

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

\n

Серверная метрика удобна, но удобство не делает её пользовательским SLI. Запрос «создать заказ» может получить 202, а очередь позднее отклонит заказ. Или backend ответит за 80 мс, пока браузер ждёт загрузки скрипта и не показывает подтверждение. Такой backend-SLI честно описывает свою точку измерения, но не весь путь.

\n

Формулировка должна начинаться с действия: «пользователь отправляет заказ и видит подтверждение». Затем задайте границу завершения: получение 202, появление записи в заказах или видимый экран подтверждения. Выберите источник, который действительно видит эту границу: серверный лог, black-box проверка или клиентская телеметрия. У каждого способа своя цена покрытия и сопровождения.

\n

Не смешивайте спецификацию и реализацию. Спецификация может звучать как «доля заказов, подтверждённых не позднее пяти минут». Реализация через лог API не увидит отказы до backend; реализация через браузерный synthetic-проверяющий охватит доступность пути, но может не отражать всех клиентов. В договоре нужно записать, какой пробел принят и зачем.

\n
\"Петля
Схема показывает порядок разговора: сигнал не равен причине, а расход бюджета не является автоматическим релизным gate. Это учебная диаграмма, а не мониторинг или журнал инцидента.
\n

Знаменатель и окно могут перевернуть вывод

\n

Eligible — не «все записи, которые удобно посчитать», а заранее определённая популяция. Таймауты, повторные попытки, отмены и некорректные входы нельзя молча выкинуть только потому, что они ухудшают процент. Если событие исключается, запишите техническую причину, владельца правила и отрицательный пример. Иначе команда улучшает отчёт, меняя объект измерения.

\n

Окно тоже часть контракта. Скользящее окно сохраняет недавний сбой в расчёте и не обнуляет его в начале календарного месяца. Календарное окно удобнее для отчётности и планирования, но посреди периода сложнее оценить, сколько трафика ещё поступит. Для обоих вариантов укажите часовой пояс, границы, задержку событий и правило пересчёта.

\n

На малом трафике один отказ заметно меняет ratio. Это не запрет на SLO, а причина не строить срочный автоматический вывод на малой выборке. Можно увеличить окно, поднять минимальное число eligible-событий для page или отправлять такой сигнал в ticket на ручной разбор. Порог реакции выбирается вместе с формулой, а не после неё.

\n
Карта разбора красного или зелёного сигнала
НаблюдениеВозможная причинаПроверяемСледующее действие
Красный budget, но неизвестна формулаСмешаны окно, exclusions или версия запросаСверяем SLI-контракт и исходные выборкиНе принимать релизное решение до восстановления evidence
Команды получили разные процентыРазные eligible, good или часовой поясСравниваем запрос, период, фильтры и повторные попыткиНазначаем владельца одной версии расчёта
Зелёный SLI, но путь не работаетТочка измерения раньше пользовательского результатаПроходим сценарий до подтвержденияРасширяем scope или добавляем отдельный клиентский SLI
Один отказ сильно изменил процентМалое окно или низкий трафикСчитаем объём и распределение событийМеняем окно или переводим сигнал в ручной разбор
Красный график блокирует любой релизPolicy не различает срочность и обратимостьПроверяем blast radius, rollback и тип измененияСужаем rollout, откатываемся или продолжаем с контролем
\n

Как превратить budget в решение о rollout

\n

Policy — это не фраза «при красном остановить всё». В ней должны быть условие, evidence, владелец и действие. Например, при неизвестной формуле команда сначала восстанавливает источник данных. При подтверждённом расходе и неясной причине уменьшает долю трафика для нового релиза и назначает разбор. При исчерпанном бюджете откладывает несрочный rollout, но отдельно рассматривает security fix или исправление причины с явным планом отката.

\n

Прогрессивный rollout ограничивает blast radius, но не исправляет плохой SLI. На каждом этапе задайте длительность наблюдения, минимальный объём событий, критерий остановки и способ вернуть предыдущую версию. Ручное решение остаётся важным: одинаковый расход может означать известную деградацию, ошибку сбора или новую проблему после выкладки.

\n

После действия повторите расчёт на той же популяции и в сопоставимом окне. Если процент улучшился только после удаления таймаутов из eligible, это не восстановление сервиса. Если причина устранена, а signal не изменился, проверяйте задержку доставки или гипотезу, а не подгоняйте denominator.

\n

Воспроизводимый порядок проверки

\n
  1. Назовите путь. Запишите действие пользователя, ожидаемый результат, симптом и цену ошибочного решения.
  2. Зафиксируйте контракт. Укажите scope, источник, eligible, good, exclusions, target, окно, часовой пояс и задержку поступления данных.
  3. Пересчитайте малую выборку. Возьмите несколько успехов и отказов, вручную проверьте классификацию и сохраните отрицательный пример. Сверьте результат с формулой из статьи.
  4. Проверьте реализацию. Сопоставьте логи, метрики, synthetic или клиентскую телеметрию с одной операцией и одной версией. Не называйте корреляцию причиной.
  5. Оцените решение. Проверьте объём трафика, срочность, обратимость и blast radius. Выберите pause, узкий rollout, rollback, исправление или продолжение с наблюдением по policy.
  6. Назначьте владельца. Запишите, кто выполняет действие, в какой момент и каким наблюдением подтверждается результат.
  7. Повторите без смены правил. Пересчитайте тот же SLI после изменения. Отдельно отметьте, что осталось вне покрытия.
\n

Ограничения и отрицательные случаи

\n

SLI показывает соответствие выбранному критерию, но не объясняет корневую причину. Он также не покрывает то, что не попало в scope: не дошедшие до backend запросы, неверный результат, отдельный регион или позднее событие. Для этих рисков нужны дополнительные сигналы и проверки. Несколько зелёных SLI не складываются автоматически в доказательство здоровья всей системы.

\n

Числовой пример не подключён к файлам, CI, мониторингу, сети, HTTP или реальной конфигурации. Он не создаёт alert, не меняет rollout и не разрешает выпуск. Google SRE описывает error budget как механизм совместного решения, но конкретные target, окно, page и исключения требуют согласования продуктового владельца, разработки и эксплуатации.

\n

Если данных мало, задержка не известна или источник нельзя воспроизвести, правильный результат проверки — «решение отложено» либо «сигнал недостаточен», а не выдуманный инцидент. Если действие необратимо, сначала нужна дополнительная защита: staged rollout, snapshot, rollback или ручное подтверждение.

\n

Критерий готовности к релизному решению

\n

Разговор можно закрыть, когда другой инженер без устного контекста воспроизводит число и понимает действие. В записи есть пользовательский результат, версия формулы, eligible и good, target, окно, источник, владелец, критерий остановки, план отката и повторная проверка. Есть отрицательный пример, который текущий SLI не скрывает.

\n

Перед релизом достаточно задать три вопроса: что именно измеряется, почему это покрывает нужный путь и что произойдёт при ухудшении. Если на первый вопрос отвечает только цвет графика, а на третий — «разберёмся по ситуации», SLO пока остаётся отчётной метрикой, а не рабочим договором.

\n

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

" } diff --git a/editorial/agent-rewrites/152.json b/editorial/agent-rewrites/152.json index 86f2c86..b8c4556 100644 --- a/editorial/agent-rewrites/152.json +++ b/editorial/agent-rewrites/152.json @@ -1,7 +1,7 @@ { "index": 152, "slug": "editorial-2023-10-mechanism-sli-slo", - "title": "Error budget без магии: как проверить SLI, окно и решение", - "excerpt": "Процент доступности не объясняет сам себя. Разбираем связь SLI, SLO и error budget: от пользовательского пути и знаменателя до проверки формулы, policy и безопасного действия.", - "contentHtml": "

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

\n

Цена ошибки — не спор о терминах. Команда может остановить полезное исправление из-за неверной формулы. Или продолжить рискованный rollout, потому что неуспешные события не попали в расчёт. Красный цвет не становится решением, пока за ним нет проверяемого договора.

\n

Тезис: budget начинается с границы измерения

\n

SLI — это количественная мера конкретного свойства сервиса. SLO задаёт для этой меры цель или диапазон. Error budget — допустимая часть неуспешных событий внутри той же границы и того же окна. Если команда меняет scope, eligible-события, good-события, окно или target, она меняет модель. Старый процент больше нельзя сравнивать с новым без оговорки.

\n

Начинайте не с девяток. Сначала назовите действие пользователя. Для условного checkout это может быть «пользователь отправил заказ и получил подтверждение». Затем определите множество eligible-событий, правило успеха, исключения, окно, источник данных и владельца. Только после этого формула получает смысл.

\n
Минимальный договор для SLI
ПолеПримерПроверка
Scopecheckout-submitСобытие относится к нужному пользовательскому пути
Eligibleзавершённая попытка отправкиЗнаменатель включает все случаи этой границы
Goodподтверждение получено в срокПравило не зависит от цвета dashboard
Окноrolling 28 daysПериод одинаков в расчёте и обсуждении
Target99,5%Число связано с policy и владельцем
\n

Как работает арифметика

\n

Учебный пример ниже не читает monitoring и не описывает production. В окне есть 1 000 eligible-событий. Из них 994 соответствуют правилу good. SLI равен 994 / 1 000 = 99,4%. При target 99,5% условный budget исчерпан: допустимая доля bad равна 0,5%, то есть пять событий, а фактическая — шесть.

\n
const eligible = 1000; const good = 994; const target = 0.995; const sli = good / eligible; const allowedBad = eligible * (1 - target); const actualBad = eligible - good; console.log({ sli, allowedBad, actualBad, budgetExhausted: actualBad > allowedBad }); // учебный результат: { sli: 0.994, allowedBad: 5, actualBad: 6, budgetExhausted: true }
\n

Числа в коде намеренно synthetic. Они не доказывают доступность, burn rate, incident или стоимость простоя. В рабочей системе нужно подтвердить, откуда пришло каждое событие и почему оно относится к scope. Формула без этого лишь аккуратно делит неизвестные данные.

\n

Есть и отрицательный путь. Если знаменатель равен нулю, процент нельзя объявлять равным 100%. Если good больше eligible, источник или преобразование сломаны. Если сервис измеряет только HTTP-ответ, а пользовательская ценность появляется после фоновой обработки, SLI может быть полезным proxy, но не прямым измерением результата. Proxy gap надо назвать явно.

\n

Окно не лечит плохой знаменатель

\n

Rolling window показывает недавнее состояние и постепенно вытесняет старые события. Fixed window проще связать с отчётным периодом. Ни один режим не исправляет ошибку в eligible set. При малом трафике одна ошибка резко меняет процент. При большом трафике среднее может скрыть хвост задержки. Для latency среднее также может быть слишком грубым: несколько очень медленных запросов исчезнут в общей цифре.

\n

Окно нужно записать рядом с формулой, а не оставить подписью графика. При смене 28 дней на 30 дней, при смене fixed на rolling или при изменении часового пояса меняется сравнение. Пересчитайте исторические значения либо пометьте границу новой версией договора.

\n

Симптом → причина → проверка → действие

\n
Диагностическая матрица для SLI/SLO
СимптомПричинаПроверкаДействие
Два отчёта показывают разные SLIРазные scope или exclusionsСравнить определения eligible и good на трёх одинаковых событияхВерсионировать один контракт и убрать скрытый фильтр
Процент равен 100% при отсутствии трафикаНулевой знаменатель превращён в успехПроверить обработку пустого окнаВернуть состояние no-data и отдельное правило для него
Красный budget не связан с жалобамиSLI измеряет proxy или не тот путьСопоставить событие метрики с user journeyСузить scope либо добавить пользовательский сигнал
После смены окна исчезла деградацияСравнили несовместимые периодыПроверить версию окна и границы timestampПересчитать историю или явно разделить серии
Budget требует немедленной блокировкиНет policy и проверки контекстаНазвать owner, обратимость и тип измененияВыбрать review, rollback, сужение rollout или продолжение с контролем
\n
Связь SLI-контракта, окна и error budget: scope задаёт eligible-события, good-события формируют SLI, target задаёт допустимый budget, а policy определяет действие.
Механизм начинается с границы события. Процент появляется после определения scope, eligible, good, окна и target. Policy связывает результат с ручным решением; сама арифметика не выдаёт право блокировать релиз.
\n

Budget не является автоматическим gate

\n

Расход бюджета — сигнал для принятия решения, а не универсальная команда остановить deploy. Policy должна назвать владельца, обязательные evidence, допустимые действия и исключения. Security fix может потребовать другого пути согласования. Обратимый rollout может потребовать сужения exposure. Неверный расчёт требует остановить интерпретацию метрики, а не обязательно остановить весь релиз.

\n

Отдельно разделяйте SLI и диагностику. SLI отвечает на вопрос, нарушается ли выбранная мера. Логи, трассы, версии, очереди и зависимости помогают искать причину. Один сигнал не обязан объяснять другой. Если после красного процента команда сразу объявляет incident, она пропускает проверку scope, времени и источника данных.

\n

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

\n
  1. Зафиксируйте симптом. Сохраните значение, timestamp, версию SLI-контракта и точный user journey. Не меняйте формулу во время расследования.
  2. Проверьте границу. Для одного good, одного bad и одного спорного события определите, попадает ли каждое в eligible и почему.
  3. Пересчитайте малую выборку. Сравните ручной подсчёт с запросом или exporter. Отдельно проверьте нулевой знаменатель и ошибочное превышение good над eligible.
  4. Проверьте окно. Сверьте начало, конец, timezone, fixed или rolling режим и задержку доставки событий.
  5. Отделите proxy от результата. Проверьте, измеряет ли событие ценность пользователя или только слой системы. Запишите расхождение.
  6. Примените policy. Назначьте owner и выберите обратимое действие: исправить источник, сузить rollout, отложить non-urgent изменение или продолжить с контролем.
  7. Повторите расчёт. Используйте ту же формулу и тот же критерий. Если сигнал не изменился ожидаемым образом, вернитесь к гипотезе.
\n

Ограничения

\n

SLI не измеряет всё качество продукта. Доступность backend не гарантирует успешный пользовательский сценарий. Error budget не показывает корневую причину и не определяет важность изменения. Target 99,5% и окно 28 дней в примере не являются рекомендацией. Для редкого трафика, пакетной обработки, долгих операций и юридического SLA нужны отдельные решения.

\n

Учебный код также не создаёт alert, не читает реальные события, не меняет CI и не принимает решение о выпуске. Его можно использовать для проверки арифметики и отрицательных веток. Production-вывод появляется только после проверки источника данных, владельца, разрешений и поведения системы на реальном трафике.

\n

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

\n

Договор готов, если инженер за несколько минут может показать scope, eligible, good, exclusions, окно, target, источник, owner и policy branch. Для трёх выбранных событий два инженера получают одинаковый ответ. Пустое окно не становится успешным автоматически. После действия та же версия формулы даёт ожидаемое изменение, а новое значение можно связать с timestamp и источником.

\n

Если хотя бы одно поле неизвестно, статус должен быть «договор не готов». Не добавляйте ещё один график поверх неопределённости. Сначала восстановите границу измерения, затем решайте, какое действие безопасно.

\n

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

\n" + "title": "Error budget без магии: как связать SLI, окно и решение", + "excerpt": "Error budget появляется не из красного процента, а из явных границ измерения. Разбираем SLI, SLO, знаменатель, окно и policy на воспроизводимом примере и показываем, почему арифметика не является release-gate.", + "contentHtml": "

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

\n

Цена ошибки — не спор о терминах. Команда может остановить полезное исправление из-за неверной формулы. Или продолжить рискованный rollout, потому что неуспешные события не попали в расчёт. Красный цвет не становится решением, пока за ним нет проверяемого договора.

\n

Пять частей одной модели

\n

SLI — это количественная мера конкретного свойства сервиса. SLO задаёт для этой меры цель или диапазон. Error budget — допустимая часть неуспешных событий внутри той же границы и того же окна. Если команда меняет scope, eligible-события, good-события, окно или target, она меняет модель. Старый процент больше нельзя сравнивать с новым без оговорки.

\n

Начинайте не с девяток. Сначала назовите действие пользователя. Для условного checkout это может быть «пользователь отправил заказ и получил подтверждение». Затем определите множество eligible-событий, правило успеха, исключения, окно, источник данных и владельца. Только после этого формула получает смысл.

\n
Минимальный договор для SLI
ПолеПримерПроверка
Scopecheckout-submitСобытие относится к нужному пользовательскому пути
Eligibleзавершённая попытка отправкиЗнаменатель включает все случаи этой границы
Goodподтверждение получено в срокПравило не зависит от цвета dashboard
Окноrolling 28 daysПериод одинаков в расчёте и обсуждении
Target99,5%Число связано с policy и владельцем
\n

Как работает арифметика

\n

Учебный пример ниже не читает monitoring и не описывает production. В окне есть 1 000 eligible-событий. Из них 994 соответствуют правилу good. SLI равен 994 / 1 000 = 99,4%. При target 99,5% условный budget исчерпан: допустимая доля bad равна 0,5%, то есть пять событий, а фактическая — шесть.

\n
const eligible = 1000; const good = 994; const target = 0.995; const sli = good / eligible; const allowedBad = eligible * (1 - target); const actualBad = eligible - good; console.log({ sli, allowedBad, actualBad, budgetExhausted: actualBad > allowedBad }); // учебный результат: { sli: 0.994, allowedBad: 5, actualBad: 6, budgetExhausted: true }
\n

Числа в коде намеренно synthetic. Они не доказывают доступность, burn rate, incident или стоимость простоя. В рабочей системе нужно подтвердить, откуда пришло каждое событие и почему оно относится к scope. Формула без этого лишь аккуратно делит неизвестные данные.

\n

Арифметика должна иметь отрицательные ветки

\n

Учебный расчёт полезен только вместе с проверками входа. Нулевой знаменатель нельзя превращать в 100%: это состояние no-data, а не доказательство успеха. Значение good, превышающее eligible, указывает на ошибку агрегации или источника. Target вне диапазона от нуля до единицы нельзя молча принимать как SLO. Такие условия лучше отвергать до построения графика.

\n
node --input-type=module <<'EOF'\nfunction check({ eligible, good, target, windowDays }) {\n  if (!Number.isInteger(eligible) || eligible <= 0) throw new Error('eligible > 0');\n  if (!Number.isInteger(good) || good < 0 || good > eligible) throw new Error('0 <= good <= eligible');\n  if (!(target > 0 && target < 1)) throw new Error('0 < target < 1');\n  if (windowDays !== 28) throw new Error('window is 28 days in this fixture');\n\n  const bad = eligible - good;\n  const sli = good / eligible;\n  const exactAllowedBad = eligible * (1 - target);\n  return {\n    sli,\n    bad,\n    allowedBad: Number(exactAllowedBad.toFixed(6)),\n    budgetExhausted: bad > exactAllowedBad,\n    releaseAuthority: 'not-granted'\n  };\n}\n\nconsole.log(check({ eligible: 1000, good: 994, target: 0.995, windowDays: 28 }));\nEOF\n\n# ожидается: sli 0.994, bad 6, allowedBad 5, budgetExhausted true\n
\n

Запуск не обращается к monitoring, CI, сети, HTTP, SDK или часам. Поле releaseAuthority намеренно не даёт fixture полномочий: PASS подтверждает арифметику и границы входа, но не доступность сервиса и не разрешение на rollout.

\n

Есть и отрицательный путь. Если знаменатель равен нулю, процент нельзя объявлять равным 100%. Если good больше eligible, источник или преобразование сломаны. Если сервис измеряет только HTTP-ответ, а пользовательская ценность появляется после фоновой обработки, SLI может быть полезным proxy, но не прямым измерением результата. Proxy gap надо назвать явно.

\n

Окно не лечит плохой знаменатель

\n

Rolling window показывает недавнее состояние и постепенно вытесняет старые события. Fixed window проще связать с отчётным периодом. Ни один режим не исправляет ошибку в eligible set. При малом трафике одна ошибка резко меняет процент. При большом трафике среднее может скрыть хвост задержки. Для latency среднее также может быть слишком грубым: несколько очень медленных запросов исчезнут в общей цифре.

\n

Окно нужно записать рядом с формулой, а не оставить подписью графика. При смене 28 дней на 30 дней, при смене fixed на rolling или при изменении часового пояса меняется сравнение. Пересчитайте исторические значения либо пометьте границу новой версией договора.

\n

Симптом → причина → проверка → действие

\n
Диагностическая матрица для SLI/SLO
СимптомПричинаПроверкаДействие
Два отчёта показывают разные SLIРазные scope или exclusionsСравнить определения eligible и good на трёх одинаковых событияхВерсионировать один контракт и убрать скрытый фильтр
Процент равен 100% при отсутствии трафикаНулевой знаменатель превращён в успехПроверить обработку пустого окнаВернуть состояние no-data и отдельное правило для него
Красный budget не связан с жалобамиSLI измеряет proxy или не тот путьСопоставить событие метрики с user journeyСузить scope либо добавить пользовательский сигнал
После смены окна исчезла деградацияСравнили несовместимые периодыПроверить версию окна и границы timestampПересчитать историю или явно разделить серии
Budget требует немедленной блокировкиНет policy и проверки контекстаНазвать owner, обратимость и тип измененияВыбрать review, rollback, сужение rollout или продолжение с контролем
\n
Связь SLI-контракта, окна и error budget: scope задаёт eligible-события, good-события формируют SLI, target задаёт допустимый budget, а policy определяет действие.
Механизм начинается с границы события. Процент появляется после определения scope, eligible, good, окна и target. Policy связывает результат с ручным решением; сама арифметика не выдаёт право блокировать релиз.
\n

Проверка знаменателя на трёх событиях

\n

Перед тем как обсуждать процент, возьмите один good, один bad и один спорный случай. Для каждого ответьте на четыре вопроса: относится ли событие к scope, почему оно eligible, какое поле доказывает good и когда событие попало в окно. Эта маленькая выборка обнаруживает скрытый фильтр быстрее, чем ещё один dashboard.

\n
Что делать при расхождении модели
НаблюдениеПроверяемРешение до новых данных
Два запроса дают разный знаменательScope, exclusions и версию фильтраПриостановить сравнение и оставить одну формулу
Процент равен 100% без событийВетку no-dataОтделить отсутствие наблюдений от успеха
Backend good, пользователь видит сбойУчасток пути после серверного ответаНазвать SLI proxy и добавить клиентский сигнал
\n

Budget не является автоматическим gate

\n

Расход бюджета — сигнал для принятия решения, а не универсальная команда остановить deploy. Policy должна назвать владельца, обязательные evidence, допустимые действия и исключения. Security fix может потребовать другого пути согласования. Обратимый rollout может потребовать сужения exposure. Неверный расчёт требует остановить интерпретацию метрики, а не обязательно остановить весь релиз.

\n

Отдельно разделяйте SLI и диагностику. SLI отвечает на вопрос, нарушается ли выбранная мера. Логи, трассы, версии, очереди и зависимости помогают искать причину. Один сигнал не обязан объяснять другой. Если после красного процента команда сразу объявляет incident, она пропускает проверку scope, времени и источника данных.

\n

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

\n
  1. Зафиксируйте симптом. Сохраните значение, timestamp, версию SLI-контракта и точный user journey. Не меняйте формулу во время расследования.
  2. Проверьте границу. Для одного good, одного bad и одного спорного события определите, попадает ли каждое в eligible и почему.
  3. Пересчитайте малую выборку. Сравните ручной подсчёт с запросом или exporter. Отдельно проверьте нулевой знаменатель и ошибочное превышение good над eligible.
  4. Проверьте окно. Сверьте начало, конец, timezone, fixed или rolling режим и задержку доставки событий.
  5. Отделите proxy от результата. Проверьте, измеряет ли событие ценность пользователя или только слой системы. Запишите расхождение.
  6. Примените policy. Назначьте owner и выберите обратимое действие: исправить источник, сузить rollout, отложить non-urgent изменение или продолжить с контролем.
  7. Повторите расчёт. Используйте ту же формулу и тот же критерий. Если сигнал не изменился ожидаемым образом, вернитесь к гипотезе.
\n

Ограничения

\n

SLI не измеряет всё качество продукта. Доступность backend не гарантирует успешный пользовательский сценарий. Error budget не показывает корневую причину и не определяет важность изменения. Target 99,5% и окно 28 дней в примере не являются рекомендацией. Для редкого трафика, пакетной обработки, долгих операций и юридического SLA нужны отдельные решения.

\n

Учебный код также не создаёт alert, не читает реальные события, не меняет CI и не принимает решение о выпуске. Его можно использовать для проверки арифметики и отрицательных веток. Production-вывод появляется только после проверки источника данных, владельца, разрешений и поведения системы на реальном трафике.

\n

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

\n

Договор готов, если инженер за несколько минут может показать scope, eligible, good, exclusions, окно, target, источник, owner и policy branch. Для трёх выбранных событий два инженера получают одинаковый ответ. Пустое окно не становится успешным автоматически. После действия та же версия формулы даёт ожидаемое изменение, а новое значение можно связать с timestamp и источником.

\n

Если хотя бы одно поле неизвестно, статус должен быть «договор не готов». Не добавляйте ещё один график поверх неопределённости. Сначала восстановите границу измерения, затем решайте, какое действие безопасно.

\n

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

\n" } diff --git a/editorial/agent-rewrites/153.json b/editorial/agent-rewrites/153.json index ec8c18e..a7454b4 100644 --- a/editorial/agent-rewrites/153.json +++ b/editorial/agent-rewrites/153.json @@ -1,7 +1,7 @@ { "index": 153, "slug": "editorial-2023-10-practice-sli-slo", - "title": "SLI и SLO: как измерять пользовательский результат, а не цвет графика", - "excerpt": "Процент успешных ответов не становится SLI сам по себе. Разбираем путь пользователя, знаменатель, хороший исход, окно и цель; показываем учебный расчёт, отрицательный путь и критерий готового договора.", - "contentHtml": "

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

\n

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

\n

Тезис: начинайте с результата пользователя

\n

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

\n

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

\n

Уptime процесса может остаться диагностическим сигналом. Он показывает состояние компонента, но не доказывает успех пользовательского маршрута. И наоборот, один backend-ответ может быть плохим, а продукт — успешно показать fallback. Поэтому название proxy должно оставаться явным. Proxy нельзя выдавать за прямое измерение результата.

\n

Механизм договора

\n

Для success ratio удобно разделить события на eligible и good. Eligible — все попытки, которые имеют право попасть в знаменатель. Good — подмножество eligible с заранее названным допустимым исходом. Тогда показатель считают так: SLI = good / eligible. SLO задаёт нижнюю границу, например 99% в выбранном окне. Error budget равен допустимой доле bad-событий в том же знаменателе и окне.

\n

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

\n
Минимальный договор для учебного пути checkout
ЧастьЗначениеПроверкаОграничение
ПутьОтправка оформленного checkoutЕсть идентификатор попытки и граница завершенияНе описывает весь сайт
EligibleЗапрос дошёл до точки завершения APIСобытие содержит request_id и итоговый статусНе включает отмену до запроса
GoodКлиент получил подтверждение приёма платежаСтатус связан с пользовательским ответом, а не только с 2xxНе доказывает фактическое списание
Окно28 дней в учебном примереВсе сравнения используют одну границу времениНе является универсальным окном
Цель и владелец99%; владелец checkoutЕсть правило пересмотра и способ связиНе даёт автоматического права блокировать релиз
\n
\"Схема
Схема показывает структуру договора до подключения отчёта. Она не является production-дашбордом и не измеряет доступность реального сервиса.
\n

Число 99% без окна и знаменателя неполно. Сто успешных попыток из ста дают 100%, но один сбой при десяти попытках меняет показатель сильнее, чем один сбой при миллионе. Для малотрафикового маршрута процент может быть шумным. Для пакетной обработки важнее throughput или время завершения. Окно выбирают вместе с типом нагрузки и решением, которое SLO должно поддержать.

\n

Учебный пример расчёта

\n

Ниже — ограниченный пример. Он проверяет только арифметику договора на заранее заданных числах. Он не читает логи, не обращается к мониторингу и не сообщает состояние какого-либо сервиса. В учебном окне есть 1 000 eligible-событий, из них 994 good. Показатель равен 99,4%. При SLO 99% допустимы 10 bad-событий, а в примере их 6. Остаток условного бюджета — 4 события.

\n
const eligible = 1000;\nconst good = 994;\nconst target = 0.99;\n\nconst bad = eligible - good;\nconst sli = good / eligible;\nconst allowedBad = eligible * (1 - target);\nconst budgetLeft = Math.max(0, allowedBad - bad);\n\nconsole.log({\n  sliPercent: sli * 100,\n  bad,\n  allowedBad,\n  budgetLeft,\n});\n// Учебный вывод: 99.4%, 6, 10, 4\n// Числа не являются измерением production-сервиса.
\n

Формула полезна только при сохранении условий. Если из знаменателя убрать неудачные попытки, показатель вырастет без улучшения пути. Если заменить good на «ответ не 5xx», можно начать считать принятый запрос успехом, хотя пользователь ещё не получил подтверждение. Если смешать 28-дневное окно с дневным числом ошибок, error budget потеряет смысл.

\n

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

\n

Симптом → причина → проверка → действие

\n
  1. Симптом: на панели есть процент, но разные люди по-разному называют путь, который он покрывает. Причина: запрос, дашборд и документация используют разные границы. Проверка: попросите показать одно исходное событие и восстановите его путь от начала до результата. Действие: зафиксируйте scope и идентификатор операции.
  2. Симптом: SLI растёт после фильтрации части ошибок. Причина: исключение добавили без правила для знаменателя. Проверка: сравните eligible до и после фильтра и разберите один спорный случай. Действие: назовите исключение в договоре или верните событие в расчёт.
  3. Симптом: «успех» означает любой ответ 2xx, но клиент не подтверждает действие. Причина: технический статус подменил пользовательский исход. Проверка: проследите, что получает клиент после ответа и где фиксируется завершение. Действие: разделите технический proxy и SLI пользовательского пути.
  4. Симптом: один сбой в малом потоке меняет решение о релизе. Причина: окно и минимальный объём данных не согласованы с трафиком. Проверка: покажите число eligible по окну и влияние одного события на процент. Действие: выберите иной метод агрегации или оставьте сигнал диагностическим.
  5. Симптом: красный график автоматически блокирует изменение. Причина: SLO смешали с policy и полномочием на действие. Проверка: найдите владельца, правило исключений и обратимый шаг. Действие: оставьте решение за владельцем; метрика поставляет evidence, а не разрешение.
\n

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

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

Последний шаг важен. SLO не объясняет корневую причину. Для неё нужны диагностические сигналы: логи, метрики, трассы или данные продукта. OpenTelemetry описывает эти сигналы как разные виды наблюдений: trace показывает путь запроса, metric — измерение во времени, log — запись события. Их можно связать контекстом, но ни один сигнал не доказывает причину без проверки источника и границы времени.

\n

Ограничения и отрицательный путь

\n

Если событие не содержит request_id, нельзя надёжно связать его с пользовательской попыткой. Если good event появляется раньше фактического завершения, SLI измеряет промежуточный шаг. Если трафика мало, процент может не поддерживать срочное решение. Если внешний провайдер недоступен, нужно заранее определить, входит ли его сбой в договор и кто владеет реакцией. Если событие потеряно, отсутствие строки нельзя считать успехом.

\n

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

\n

Учебная арифметика также не доказывает SLO compliance, availability, burn rate, incident или безопасность релиза. Она не показывает реальную стоимость простоя. Официальные документы Google помогают выбрать термины и форму описания, но не назначают target конкретному продукту. Производственный критерий должен опираться на реальные события, согласованный владелец и проверяемое правило реакции.

\n

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

\n

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

\n

Если на любой вопрос нет ответа, готовность не достигнута. Сначала исправьте границу и названия событий. Потом меняйте дашборд, алерт или policy. Такой порядок защищает от главной ошибки SLI/SLO: принять число, которое легко измерить, за результат, который действительно важен пользователю.

\n

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

\n" + "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" } diff --git a/editorial/agent-rewrites/154.json b/editorial/agent-rewrites/154.json index c27f5d1..d6f2b65 100644 --- a/editorial/agent-rewrites/154.json +++ b/editorial/agent-rewrites/154.json @@ -1,7 +1,7 @@ { "index": 154, "slug": "editorial-2023-09-field-telemetry-signals", - "title": "Ошибка без причины: как связать метрику, trace и log", - "excerpt": "График показывает класс ошибки, но не объясняет один запрос. Разбираем маршрут metric → trace → log/event, границу cardinality и проверку, которая не выдаёт учебный пример за production-доказательство.", - "contentHtml": "

График ошибок растёт, но инженер не может назвать запрос и этап, на котором возник отказ. В журналах много похожих сообщений, а в trace-поиске нет понятного ключа. Самая дорогая ошибка в этот момент — принять громкий сигнал за причину: увеличить timeout, добавить retry или обвинить downstream. Сбой может остаться, а новые записи и задержки вырастут.

\n

Проблема возникает, когда metric, trace и log описывают один путь разными словами. Метрика считает класс исходов. Trace показывает путь запроса через операции. Log или event фиксирует событие и его контекст. Если между ними нет общего договора, команда видит три витрины, а не одну проверяемую цепочку.

\n

Тезис: каждый сигнал отвечает на свой вопрос

\n

Начинайте с вопроса, а не с поиска текста ошибки. Metric отвечает: «какой класс исходов изменился?». Trace отвечает: «через какие операции прошёл один путь?». Log/event отвечает: «какое событие произошло на конкретном шаге?». Общий trace ID или другой разрешённый correlation key связывает записи. Он не превращает metric в журнал запросов.

\n

Идентификатор одного запроса нельзя бездумно добавлять в labels метрики. Каждый новый идентификатор может создавать отдельный time series. График станет дороже, агрегация — менее полезной, а проблема поиска не исчезнет. Для метрики оставляют небольшой словарь: service, route и outcome. Подробный контекст отправляют в trace или log после проверки политики доступа и хранения.

\n

Механизм маршрута

\n

Представим учебный checkout-сценарий. Metric сообщает: для маршрута authorization вырос класс rejected. Эта запись не знает пользователя, заказа и конкретного trace. Она только выбирает поле поиска. Далее trace с тем же synthetic correlation key показывает gateway span и дочерний payment span. Затем log/event указывает, что отказ произошёл на payment span, и повторяет trace ID и span ID.

\n

Каждая стрелка требует отдельной проверки. Наличие метрики не доказывает существование trace. Наличие trace не доказывает, что log экспортирован и доступен. Совпавший ID не доказывает причину отказа, если событие записалось после ошибки или относится к другому шагу. Доказательство должно состоять из наблюдаемых объектов и честного статуса каждой связи.

\n
Симптом → причина → проверка → действие
СимптомПричинаПроверкаДействие
Счётчик ошибок изменился, trace не находитсяНет перехода от route/outcome к trace или trace не экспортируетсяВзять один разрешённый outcome и проверить correlation в реальной средеПочинить передачу контекста или назвать путь неподтверждённым
В metric появились request IDИдентичность запроса использовали как labelПосчитать набор labels и рост series на выбранном окнеОстановить изменение, вернуть малый словарь labels, ID оставить в trace/log
Trace есть, событие не объясняет отказLog не содержит span ID, событие относится к другому шагу или потеряно при samplingСверить trace ID, span ID, имя события и времяИсправить корреляцию; не объявлять downstream причиной
Свободный текст не ищется стабильноСообщение меняется между версиями и не имеет event nameПроверить структурированные поля и стабильное имя событияДобавить минимальную схему и сохранить текст как дополнительный context
Учебный тест зелёный, production неизвестенПроверили форму записей в памяти, а не экспорт и поискОтделить fixture от реальной выборки и явно отметить границуНазначить проверку в разрешённой среде; не публиковать результат как incident evidence
\n

Конкретный пример

\n

Ниже — классификатор учебных записей. Он проверяет только договор между объектами в памяти. Значения synthetic-* выдуманы для примера. Код не обращается к приложению, не создаёт telemetry и не подтверждает, что downstream действительно вернул ошибку.

\n
function checkRoute({ metric, trace, event }) {\n  const labels = Object.keys(metric.labels);\n  const allowed = ['service', 'route', 'outcome'];\n  const metricShape = labels.length === 3\n    && labels.every((name) => allowed.includes(name))\n    && !labels.includes('trace_id');\n  const traceShape = trace.root.traceId === trace.payment.traceId;\n  const eventShape = event.traceId === trace.payment.traceId\n    && event.spanId === trace.payment.spanId;\n  return {\n    metricShape,\n    traceShape,\n    eventShape,\n    readyForRealCheck: metricShape && traceShape && eventShape,\n  };\n}\n\nconst result = checkRoute({\n  metric: { labels: { service: 'checkout', route: 'authorization', outcome: 'rejected' } },\n  trace: {\n    root: { traceId: 'synthetic-trace-1' },\n    payment: { traceId: 'synthetic-trace-1', spanId: 'synthetic-span-payment' },\n  },\n  event: { traceId: 'synthetic-trace-1', spanId: 'synthetic-span-payment' },\n});\n\nconsole.log(result);\n// readyForRealCheck: true — только договор synthetic-записей.
\n

Отрицательный путь важнее зелёного результата. Если event получит другой trace ID, eventShape станет false. Если в metric появится trace_id, metricShape станет false. Код не угадывает причину и не исправляет систему. Он останавливает вывод: сначала нужно восстановить связь или признать, что её нет.

\n
\"Учебный
Учебная схема разделяет вопросы сигналов. Она не изображает реальный alert, запрос к backend, trace search или подтверждённую production-причину.
\n

Как читать три сигнала вместе

\n

Metric полезна на первом шаге, потому что сжимает поток в устойчивые классы. Используйте route template и outcome, а не полный URL, user ID, order ID или текст ошибки. Набор dimensions должен быть заранее ограничен. Точное число допустимых series зависит от платформы, окна и числа значений, поэтому его нельзя объявлять безопасным без расчёта и проверки владельца backend.

\n

Trace нужен, когда вопрос перешёл от класса к пути. Найдите один разрешённый trace и проверьте дерево spans: gateway должен вести к payment operation, а не просто соседствовать с ней по времени. Сверьте parent-child связь, статус, длительность и границы sampling. Даже полный trace показывает путь инструментирования, а не автоматически истинную причину бизнес-ошибки.

\n

Log/event нужен для контекста шага. Структурированное событие должно иметь стабильное имя, время, trace ID и, если событие связано с конкретной операцией, span ID. Дополнительные attributes должны пройти review на чувствительные данные, redaction, retention и права доступа. «Добавим весь request на всякий случай» — плохая стратегия: она увеличивает риск и не делает гипотезу точнее.

\n

Порядок действий

\n
  1. Опишите симптом одним предложением: какой класс исходов изменился, в каком route и за какое окно.
  2. Назовите ожидаемый переход metric → trace. Проверьте, что metric не содержит per-request labels и использует малый словарь service, route, outcome.
  3. Выберите один разрешённый trace. Сверьте trace ID, root span, дочерний span и время операции. Не делайте вывод по одному графику.
  4. Найдите log/event на конкретном span. Проверьте event name, trace ID, span ID и отсутствие лишних чувствительных полей.
  5. Прогоните отрицательные проверки: mismatch trace ID, mismatch span ID, лишний label и отсутствие event. Каждый случай должен останавливать вывод.
  6. Сформулируйте действие только после проверки связи. Если trace или event отсутствует, исправляйте instrumentation и экспорт, а не таймаут downstream.
  7. Повторите проверку тем же route, окном и правилом выборки. Сравните стоимость series, доступность поиска и соседние сигналы.
  8. Запишите результат как подтверждённый, неподтверждённый или неполный. Не называйте synthetic PASS наблюдением production.
\n

Когда остановиться и что откатывать

\n

Если новый label резко расширяет cardinality или event содержит запрещённое поле, остановите распространение изменения. Сначала определите, какие записи ещё могут появляться и какие потребители уже зависят от схемы. Затем выберите обратимое действие для конкретной конфигурации: отключить добавленный label, ограничить event attributes или вернуть предыдущую версию instrumentation. Нельзя обещать удаление уже сохранённых данных, пока не известны storage, retention и политика доступа.

\n

Если metric уже есть, а trace не связывается, не добавляйте ещё один ID в счётчик. Проверьте propagation на границе сервиса, sampling, exporter и возможность поиска. Если log не содержит span ID, назовите это дефектом корреляции. Если настоящая система не позволяет безопасно проверить путь, остановите расследование на статусе «не подтверждено» и не заменяйте evidence догадкой.

\n

Ограничения и критерий готовности

\n

Пример не содержит реальных logs, metrics, traces, latency, traffic, backend records или incident data. Synthetic value и IDs не являются измерениями. Статья не утверждает, что конкретная SDK, collector, exporter или backend поддерживает одинаковые поля и поиск. Sampling может скрыть часть trace. Асинхронная очередь может разорвать контекст. Событие может прийти позже операции. Эти условия нужно проверять в выбранном контуре.

\n

Критерий готовности проверяемый: для одного разрешённого route есть metric с заранее названными dimensions; для выбранного outcome найден trace с тем же correlation key; trace содержит ожидаемый span; log/event имеет тот же trace ID и корректный span ID; отрицательные ветки дают отказ; после изменения не выросли запрещённые labels и не появились чувствительные поля. Если хотя бы одна связь не доказана, итог — неполный.

\n

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

" + "title": "Ошибка без причины: маршрут диагностики через log, metric и trace", + "excerpt": "Пошаговый маршрут, который не подменяет один correlation ID новой label-кардинальностью: как разложить симптом, гипотезу и evidence между metric, trace и log/event record.", + "contentHtml": "

Симптом для диагностики звучит знакомо: график ошибок показывает изменение, но инженер не может назвать запрос и этап, на котором оно возникло. В ответ часто начинают искать текст исключения во всех logs или добавляют request ID в metric labels. Первый путь тонет в несвязанных записях, второй смешивает счётчик с идентичностью одного запроса. Причина не становится ближе: у трёх источников нет договора, который превращает один сигнал в вопрос к следующему.

\n

Цена такого разрыва — решение на основании наиболее громкой витрины. Можно увеличить timeout, включить retry или объявить downstream виновником, хотя связь между error count, span и event не подтверждена. Эта статья не расследует реальный инцидент и не собирает telemetry. Она строит безопасный diagnostic route для одного fixed synthetic сценария, чтобы показать: evidence одного отказа складывается из разных объектов, а не из максимального количества labels.

\n

Начните не с поиска, а с вопроса

\n

У диагностики есть три уровня. Metric помогает сформулировать, какой класс исходов стоит рассматривать: например, synthetic `outcome=synthetic-rejected` для synthetic checkout route. Trace должен показать предполагаемый причинный путь из gateway к payment шагу. Log/event record должен назвать событие на payment step и сохранить тот же correlation key. Только после этого появляется evidence-card: она говорит, какую гипотезу можно проверить и чего пока нет. Ни один из объектов по отдельности не заменяет остальные.

\n
Маршрут вопросов вместо бесконечного поиска
ОчередьВопросНужное представлениеДопустимый результатЧто не делать
1какой класс результата разбираем?metric labelssynthetic route + outcomeне добавлять request ID ради фильтра
2какой путь должен ему соответствовать?trace + span treeодин synthetic trace ID, два шагане считать график доказательством причины
3какое событие произошло на шаге?log/event recordevent name + trace ID + span IDне искать по свободному тексту без correlation
4какой вывод честен?evidence cardnot-a-production-observationне объявлять hypothesis подтверждённой fixture-ом
\n
\"Учебный
Диаграмма показывает порядок вопросов и границы вывода для synthetic записи. Она не изображает реальный alert, dashboard, запрос к backend, trace search, latency или подтверждённую причину production-сбоя.
\n

Metric даёт границу разбора, а не виновника

\n

В учебном наборе metric record содержит имя `synthetic.checkout.authorization.rejected.total`, значение `1` и три labels. Значение `1` — не измеренный в системе counter, а фиксированная часть fixture. Оно нужно только чтобы показать форму: одна маленькая точка может обозначать класс outcome. По ней нельзя определить user, order, request или span. Такую границу полезно сохранять даже если UI backend позволяет кликнуть на dimensions: возможность фильтра не превращает metric в достоверный журнал событий.

\n

Если на первом шаге неизвестно, какой вопрос нужно решить, не пополняйте labels «на всякий случай». Сначала назовите route template и outcome class, которые должны быть малым словарём. Затем спросите владельца инструмента, какая реальная единица агрегации поддерживается, какие resource attributes добавляются и где будет измеряться cardinality. Без ответа status должен быть «не проверено», а не «у нас низкая cardinality». Fixture помогает удержать именно эту дисциплину: лишний `trace_id`, `request_id` или `user_id` он отвергает до того, как поле станет привычным.

\n

Trace связывает причины, log/event фиксирует контекст

\n

Дальше мы идём по `synthetic-trace-2023-09-A`. В trace object есть root span gateway и дочерний payment span; оба названия и состояния synthetic. Связь потомка с родителем — модель причинного маршрута, а не свидетельство выполнения вызова. Log/event record ссылается на payment span, имеет тот же trace ID и event name отказа. Если trace ID или span ID в log отличаются, fixture возвращает отказ. Это простое правило полезнее длинного списка полей: событие должно либо объяснять конкретный шаг пути, либо честно оставаться несвязанным.

\n

Event attributes нужны для узкой диагностики события. В примере есть `failure.class=synthetic-declined` и `retry.advice=synthetic-do-not-retry`. Они не говорят, как надо обрабатывать настоящие платежи, и не являются production error message. Их роль — показать разницу между типом отказа и точной идентичностью запроса. В реальном проекте перед добавлением любых attributes нужно отдельно решить privacy, возможность redaction, retention, доступ к поиску и стабильность названий. Нельзя прятать эти решения под словом «контекст».

\n

Прогоните одну контролируемую модель

\n

Код ниже создаёт fixed synthetic scenario в памяти. Он не может обратиться к приложению или telemetry backend, не читает clock и не создаёт telemetry. `runTelemetryFixture()` проверяет девятнадцать assertions: общий trace ID для trace и log, правильный span, три разрешённых labels, отсутствие trace ID в labels, закрытый список входных полей, отрицательные ветки mismatch и предел rollback. Это упражнение для review контракта. Его PASS не подтверждает, что downstream отказал, что metric выросла, что span записался или что log можно найти.

\n
import {\n  assembleSyntheticTelemetryScenario,\n  runTelemetryFixture,\n} from './upgrade-2023-09.mjs';\n\nconst scenario = assembleSyntheticTelemetryScenario({\n  synthetic: true,\n  traceId: 'synthetic-trace-2023-09-A',\n  rootSpanId: 'synthetic-span-gateway-A',\n  downstreamSpanId: 'synthetic-span-payment-A',\n  logTraceId: 'synthetic-trace-2023-09-A',\n  logSpanId: 'synthetic-span-payment-A',\n  eventName: 'synthetic.payment.authorization-rejected',\n  metricLabels: {\n    service: 'synthetic-checkout-api',\n    route: 'synthetic-checkout',\n    outcome: 'synthetic-rejected',\n  },\n});\n\nconst report = runTelemetryFixture();\nif (!Object.values(report.assertions).every(Boolean)) throw new Error('fixture failed');\nconsole.log(scenario.evidence.conclusion); // not-a-production-observation\n\n// Не создаёт trace/log/metric, не запускает SDK и не отправляет данные.\n\nnode web/scripts/upgrade-2023-09.mjs --verify-fixture\n\n# PASS проверяет только fixed synthetic records in memory.\n# Не доказывает incident, production latency, trace export или cardinality.
\n

После локального прогона полезно записать evidence без переобобщения. Корректная формулировка: «модель ожидает, что один correlation key соединяет заданный payment event и заданный trace; metric использует только три fixed dimensions». Некорректная: «причина ошибки найдена» или «metric безопасна для production». Разница кажется формальной только пока первое решение не затронуло retry, alert policy или пользовательские данные. В инженерном разборе неизвестное — это тоже результат, который должен пережить передачу задачи.

\n

Маршрут: симптом → причина → проверка → действие

\n
  1. Симптом. Есть числовой признак класса ошибок, но нет понятного перехода к одному пути запроса и событию, которое его объясняет.
  2. Причина. Metric, trace и log/event живут без общего contract: метрика получила per-request labels, а log не несёт trace/span correlation.
  3. Проверка. Выберите один synthetic outcome. Проверьте, что metric содержит только service/route/outcome, trace имеет один ID и два шага, а log/event повторяет trace ID и downstream span ID. Запустите fixture с отрицательными ветками.
  4. Действие. Зафиксируйте маршрут metric → trace → log/event → evidence. Поставьте trace ID в correlation fields, а не в labels метрики; детали оставьте в атрибутах события только после отдельного policy review.
  5. Проверка вывода. В реальном контуре заранее назовите, какой query или безопасная выборка может подтвердить каждую стрелку. Пока она не выполнена, conclusion остаётся не подтверждённым.
  6. Следующий шаг. Добавьте в runbook одну ветку mismatch: что делать, если metric есть, но trace или log не связываются. Это отдельная проблема instrumentation, а не приглашение добавить новый ID в счётчик.
\n

Когда останавливать, а когда откатывать

\n

Если на review обнаружился label с высокой кардинальностью или несвязанный event, первое действие — остановить распространение нового контракта. Это не равно удалить все данные: нельзя обещать удаление, не зная платформы, retention, доступа и состоявшегося rollout. Затем нужно отделить два вопроса: какие новые записи могут продолжать возникать и какие потребители уже зависят от этого поля. Только после этого владелец выбирает обратимое действие для конкретной конфигурации.

\n

Fixture умеет только вернуть snapshot synthetic полей и пометить `telemetry=not-created-or-deleted`. Он не выключает instrumentation, не меняет sampling, alert, dashboard или access policy. Такой rollback не декоративен: он ставит границу между проверкой модели и операционным изменением. В реальном runbook точка возврата должна быть названа точнее: версия конфигурации, набор approved fields, способ проверить отсутствие дальнейшего потока и владелец подтверждения.

\n

Ограничения и следующий шаг

\n

Здесь нет реальных logs, metrics, traces, latency, cardinality, traffic, backend records или incident data. Нет отправки данных, запроса, collector, exporter, storage, sampling, alerting, query, dashboard или эффекта в production. Synthetic `value: 1` не является измерением; synthetic IDs не являются request IDs. Пакет также не утверждает, что реальные error messages, user fields или маршруты допустимы для хранения. Он только различает роли полей и показывает, какую связь надо проверить позднее.

\n

Следующий шаг — провести ограниченное design review одного изменения инструментирования. Договоритесь о: одном вопросе к метрике, одном route template, малом outcome vocabulary, одном correlation key и минимальном event schema. Затем выберите реальную разрешённую среду и способ проверить путь без публикации чувствительных значений. Если итогом окажется, что trace context не проходит конкретную границу, это не поражение модели: это точная задача для следующего изменения, а не основание расширять cardinality метрики.

\n

Историческая граница сентября 2023

\n

Материал использует только OpenTelemetry Specification v1.20.0, опубликованную 7 апреля 2023 и доступную к сентябрю того года. Она уже описывала tracing API, metrics data model и logs data model, на которые опирается различение сигналов. Статья не утверждает, что конкретная SDK, transport, collector или backend имеют одинаковую зрелость, и не переносит в 2023 год более поздние договорённости команды или инструмента.

\n

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

\n" }