Files

8 lines
20 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 149,
"slug": "editorial-2023-11-mechanism-postmortem",
"title": "Postmortem без поиска виноватого: как отделить факт от решения",
"excerpt": "Разбираем, как сохранить в postmortem границу знания: что команда видела до действия, какую гипотезу проверяет и как связать профилактику с измеримым критерием.",
"contentHtml": "<p>Проблема postmortem часто начинается с одной убедительной фразы: «релиз сломал обработку, поэтому инженер его откатил». В ней сразу смешаны событие, причина, решение и оценка человека. После инцидента команда уже знает больше, чем в момент отката, и легко принимает позднее объяснение за причину, которую можно было увидеть заранее.</p>\n<p>Такой разбор плохо меняет систему. Команда ставит новый алерт на самый заметный объект, хотя сбой мог вызвать лимит партнёра, неполный runbook или неизвестный формат данных. Следующий инцидент получает тот же набор объяснений. Рабочая модель должна разделить факт, решение в моменте и следующую проверяемую гипотезу.</p>\n<h2>Что именно делает postmortem полезным</h2>\n<p>Postmortem — это запись события, влияния, действий по смягчению и последующих изменений. Это не стенограмма поиска виноватого и не доказательство того, что одна правка устранит все будущие отказы. В официальном описании Google SRE цель такого документа — сохранить данные об инциденте, разобраться в способствующих причинах и поставить профилактические действия. Конкретный шаблон и пороги команда выбирает сама.</p>\n<p>Термин <em>blameless</em> здесь означает отсутствие обвинения людей за решения, принятые с доступной им информацией. Ответственность не исчезает: у каждого действия есть владелец, срок и критерий. Меняется предмет разговора. Вместо «кто допустил ошибку?» появляются вопросы «какой сигнал был доступен?», «какая инструкция действовала?» и «какое системное ограничение подтолкнуло к этому выбору?».</p>\n<table><caption>Четыре слоя записи и допустимый вывод</caption><thead><tr><th scope=\"col\">Слой</th><th scope=\"col\">Минимальное содержимое</th><th scope=\"col\">Что можно утверждать</th><th scope=\"col\">Чего пока нельзя утверждать</th></tr></thead><tbody><tr><td>Факт</td><td>Время, наблюдение, источник и идентификатор.</td><td>Событие зафиксировано указанным источником.</td><td>Факт сам по себе доказывает причинность.</td></tr><tr><td>Решение</td><td>Действие и факты, доступные до него.</td><td>Команда выбрала действие при данном контексте.</td><td>Выбор был очевидным после появления поздних данных.</td></tr><tr><td>Гипотеза</td><td>Предполагаемый механизм и способ проверки.</td><td>Назван вопрос для следующего эксперимента.</td><td>Гипотеза уже является подтверждённой причиной.</td></tr><tr><td>Action item</td><td>Изменение, владелец роли, критерий и откат.</td><td>Понятно, какой артефакт должен измениться.</td><td>Изменение гарантирует отсутствие повторения.</td></tr></tbody></table>\n<h2>Граница знания: решение нельзя оценивать задним числом</h2>\n<p>Для каждого факта введём <code>occurredAt</code> и <code>source</code>. Для решения сохраним <code>decidedAt</code> и список <code>availableFactIds</code>. Правило простое: факт можно связать с решением только тогда, когда его время не позже времени решения. Если запись появилась после отката, она может изменить гипотезу о механизме, но не должна притворяться частью исходного контекста.</p>\n<p>Время нужно хранить в одной зоне или в явном формате UTC. Для распределённых систем одной отметки мало: полезно указать точность часов и версию источника. Если dashboard пересчитывает прошлый период, сохраните запрос или снимок, иначе читатель не восстановит, что именно было видно дежурному.</p>\n<figure><img src=\"/assets/editorial/2023/postmortem-2023-decision-record.svg\" alt=\"Схема из трёх временных фактов, карточки решения и профилактического эксперимента; имя участника отделено от объяснения причины\" loading=\"lazy\" /><figcaption>Учебная схема: в решение входят только факты из его временного контекста, а впереди остаётся отдельный эксперимент с критерием и откатом. Иллюстрация не описывает настоящий инцидент.</figcaption></figure>\n<h2>Воспроизводимый контракт записи</h2>\n<p>Ниже — самостоятельный учебный пример. Он не читает логи, не обращается к сети и не доказывает, что версия конфигурации вызвала рост ошибок. Скрипт проверяет только внутреннее правило временной границы: ссылка решения не может указывать на факт из будущего.</p>\n<pre><code>node &lt;&lt;'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) =&gt; [fact.id, fact]));\nconst future = decision.availableFactIds.filter((id) =&gt; {\n const fact = known.get(id);\n return !fact || fact.occurredAt &gt; 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</code></pre>\n<p>В примере <code>f-03</code> появляется в 10:06 и намеренно не входит в решение в 10:05. Позднее он может поддержать другую гипотезу — например, что главным ограничением был внешний лимит. Но это не превращает исходный откат в ошибку и не позволяет переписать его контекст задним числом.</p>\n<p>Проверка не валидирует правдивость источников. Строка <code>source</code> лишь связывает запись с ожидаемым артефактом. На рабочем проекте нужно отдельно проверить доступ, сохранность и авторство метрики, change record или лога. Идентификаторы из примера нельзя переносить в production.</p>\n<h2>Как не перепутать корреляцию с причиной</h2>\n<p>Временная последовательность сужает поиск, но не устанавливает причинность. Если конфигурация изменилась перед ростом 5xx, остаются альтернативы: тот же период мог совпасть с пиком трафика, исчерпанием квоты или изменением у партнёра. В postmortem полезно хранить эти альтернативы, пока проверка не отсеет их.</p>\n<table><caption>От симптома к проверке без скачка к verdict</caption><thead><tr><th scope=\"col\">Наблюдение</th><th scope=\"col\">Гипотеза</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие после результата</th></tr></thead><tbody><tr><td>5xx выросли сразу после изменения.</td><td>Изменение несовместимо с частью входных данных.</td><td>Сравнить фиксированный набор запросов до и после, не используя персональные данные.</td><td>Добавить тест на найденный формат или отклонить гипотезу.</td></tr><tr><td>Ошибки совпали с ростом трафика.</td><td>Ресурсный лимит ниже фактической нагрузки.</td><td>Сверить rate, quota и saturation в одном временном окне.</td><td>Ограничить нагрузку или изменить capacity после review.</td></tr><tr><td>Дежурный узнал о сбое вручную.</td><td>Сигнал не покрывает пользовательский симптом.</td><td>Проверить alert на синтетическом сценарии и его маршрут доставки.</td><td>Изменить сигнал, порог или инструкцию, сохранив rollback.</td></tr><tr><td>Источник появился после решения.</td><td>Причина реконструирована позднее.</td><td>Убрать ссылку из контекста решения и обозначить unknown.</td><td>Поставить отдельный эксперимент для проверки механизма.</td></tr></tbody></table>\n<p>Формулировка «причина не подтверждена» не является провалом документа. Это точная граница знания. Неподтверждённая причина лучше красивого, но ложного вывода: она показывает, какую телеметрию, тест или безопасный эксперимент нужно добавить.</p>\n<h2>Action item должен менять систему</h2>\n<p>«Добавить мониторинг» — намерение, а не действие. Запись становится проверяемой, если в ней названы изменяемый артефакт, владелец роли, критерий и обратный путь. Например: «до следующего тестового релиза добавить alert на долю 5xx для маршрута X; критерий — synthetic-запрос вызывает сигнал за пять минут; откат — удалить правило и вернуть прежний порог». Такой пункт проверяет наличие защиты, но ещё не доказывает снижение production-инцидентов.</p>\n<p>Полезно разделить исправление и измерение. Исправление меняет код, конфигурацию, runbook или процесс. Измерение показывает, сработала ли защита в выбранном окне. Для измерения нужно назвать baseline, вариант сравнения и длительность наблюдения. Без них фраза «ошибок стало меньше» неотличима от впечатления.</p>\n<p>У каждого action item есть стоимость и риск. Алерт дешевле изменения протокола, но создаёт шум, если команда не определила владельца реакции. Регрессионный тест ловит известный класс входа раньше production, но не покрывает неизвестный внешний отказ. Перечислите альтернативы и выберите одну по риску, обратимости и доступному доказательству.</p>\n<h2>Порядок разбора</h2>\n<ol><li>Запишите влияние на пользователя и наблюдаемый симптом: маршрут, период, измерение и источник.</li><li>Соберите timeline из неизменяемых или версионируемых артефактов. Для каждой записи сохраните временную зону и точность.</li><li>Для каждого решения укажите только факты, появившиеся до действия. Поздние находки поместите в отдельный блок.</li><li>Назовите несколько правдоподобных механизмов и проверку, которая различает их.</li><li>Сформулируйте одно действие на один системный пробел: сигнал, тест, лимит, документация или безопасный rollback.</li><li>Добавьте владельца роли, срок, критерий успеха, область действия и способ отмены.</li><li>Проведите review с человеком, который не участвовал в инциденте. Он должен найти источник факта и восстановить ход решения.</li><li>После выполнения action item вернитесь к критерию и запишите измеренный результат или сохраните статус «не проверено».</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта модель не заменяет incident command, расследование безопасности, юридическую оценку или правила работы с персональными данными. В security-контуре доступ к логам, копирование доказательств и сроки хранения задаются отдельной политикой. Руководство NIST SP 800-61 Rev. 2 относится к компьютерным инцидентам безопасности и не является универсальным шаблоном для любой деградации продукта.</p>\n<p>В распределённой системе часы могут расходиться, логи могут быть потеряны, а dashboard — пересчитать историю. Тогда честная запись должна указать неопределённость и версию источника. Если нельзя безопасно воспроизвести сценарий, не называйте учебную проверку экспериментом в production. Если откат сам создаёт риск, сначала получите одобрение и подготовьте промежуточный защитный шаг.</p>\n<p>Blameless-подход также не отменяет расследование умышленного нарушения, контроля доступа или требований закона. Он отвечает на другой вопрос: как извлечь технический урок из условий, в которых система и люди действовали. Для дисциплинарных и юридических решений нужна отдельная процедура с соответствующими полномочиями.</p>\n<h2>Критерий готовности</h2>\n<p>Разбор можно отдавать на технический review, если независимый читатель открывает источник каждого факта, видит границу между временем решения и поздними находками, понимает, какая гипотеза ещё не доказана, и может проверить один action item по критерию и rollback. Если не хватает источника, временной связи или способа измерения, статус должен оставаться «не готов», а следующий шаг — устранение конкретного пробела.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://sre.google/sre-book/postmortem-culture/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE, Postmortem Culture: Learning from Failure</a> — официальный материал о целях postmortem, blameless-подходе, review и профилактических действиях. Описанные практики отражают опыт Google, а не обязательный стандарт для каждой команды.</li><li><a href=\"https://sre.google/sre-book/example-postmortem/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE, Example Postmortem</a> — официальный учебный пример с impact, timeline, lessons learned и action items. Его вымышленные данные нельзя принимать за доказательство или копировать в рабочий инцидент.</li><li><a href=\"https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-61r2.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-61 Rev. 2, Computer Security Incident Handling Guide</a> — официальное руководство NIST по подготовке, обработке и lessons learned для security incident response; область применения ограничена компьютерными инцидентами безопасности.</li></ul>"
}