{ "index": 149, "slug": "editorial-2023-11-mechanism-postmortem", "title": "Postmortem без поиска виноватого: как отделить факт от решения", "excerpt": "Разбираем, как сохранить в postmortem границу знания: что команда видела до действия, какую гипотезу проверяет и как связать профилактику с измеримым критерием.", "contentHtml": "
Проблема postmortem часто начинается с одной убедительной фразы: «релиз сломал обработку, поэтому инженер его откатил». В ней сразу смешаны событие, причина, решение и оценка человека. После инцидента команда уже знает больше, чем в момент отката, и легко принимает позднее объяснение за причину, которую можно было увидеть заранее.
\nТакой разбор плохо меняет систему. Команда ставит новый алерт на самый заметный объект, хотя сбой мог вызвать лимит партнёра, неполный runbook или неизвестный формат данных. Следующий инцидент получает тот же набор объяснений. Рабочая модель должна разделить факт, решение в моменте и следующую проверяемую гипотезу.
\nPostmortem — это запись события, влияния, действий по смягчению и последующих изменений. Это не стенограмма поиска виноватого и не доказательство того, что одна правка устранит все будущие отказы. В официальном описании Google SRE цель такого документа — сохранить данные об инциденте, разобраться в способствующих причинах и поставить профилактические действия. Конкретный шаблон и пороги команда выбирает сама.
\nТермин blameless здесь означает отсутствие обвинения людей за решения, принятые с доступной им информацией. Ответственность не исчезает: у каждого действия есть владелец, срок и критерий. Меняется предмет разговора. Вместо «кто допустил ошибку?» появляются вопросы «какой сигнал был доступен?», «какая инструкция действовала?» и «какое системное ограничение подтолкнуло к этому выбору?».
\n| Слой | Минимальное содержимое | Что можно утверждать | Чего пока нельзя утверждать |
|---|---|---|---|
| Факт | Время, наблюдение, источник и идентификатор. | Событие зафиксировано указанным источником. | Факт сам по себе доказывает причинность. |
| Решение | Действие и факты, доступные до него. | Команда выбрала действие при данном контексте. | Выбор был очевидным после появления поздних данных. |
| Гипотеза | Предполагаемый механизм и способ проверки. | Назван вопрос для следующего эксперимента. | Гипотеза уже является подтверждённой причиной. |
| Action item | Изменение, владелец роли, критерий и откат. | Понятно, какой артефакт должен измениться. | Изменение гарантирует отсутствие повторения. |
Для каждого факта введём occurredAt и source. Для решения сохраним decidedAt и список availableFactIds. Правило простое: факт можно связать с решением только тогда, когда его время не позже времени решения. Если запись появилась после отката, она может изменить гипотезу о механизме, но не должна притворяться частью исходного контекста.
Время нужно хранить в одной зоне или в явном формате UTC. Для распределённых систем одной отметки мало: полезно указать точность часов и версию источника. Если dashboard пересчитывает прошлый период, сохраните запрос или снимок, иначе читатель не восстановит, что именно было видно дежурному.
\nНиже — самостоятельный учебный пример. Он не читает логи, не обращается к сети и не доказывает, что версия конфигурации вызвала рост ошибок. Скрипт проверяет только внутреннее правило временной границы: ссылка решения не может указывать на факт из будущего.
\nnode <<'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. Позднее он может поддержать другую гипотезу — например, что главным ограничением был внешний лимит. Но это не превращает исходный откат в ошибку и не позволяет переписать его контекст задним числом.
Проверка не валидирует правдивость источников. Строка source лишь связывает запись с ожидаемым артефактом. На рабочем проекте нужно отдельно проверить доступ, сохранность и авторство метрики, change record или лога. Идентификаторы из примера нельзя переносить в production.
Временная последовательность сужает поиск, но не устанавливает причинность. Если конфигурация изменилась перед ростом 5xx, остаются альтернативы: тот же период мог совпасть с пиком трафика, исчерпанием квоты или изменением у партнёра. В postmortem полезно хранить эти альтернативы, пока проверка не отсеет их.
\n| Наблюдение | Гипотеза | Проверка | Действие после результата |
|---|---|---|---|
| 5xx выросли сразу после изменения. | Изменение несовместимо с частью входных данных. | Сравнить фиксированный набор запросов до и после, не используя персональные данные. | Добавить тест на найденный формат или отклонить гипотезу. |
| Ошибки совпали с ростом трафика. | Ресурсный лимит ниже фактической нагрузки. | Сверить rate, quota и saturation в одном временном окне. | Ограничить нагрузку или изменить capacity после review. |
| Дежурный узнал о сбое вручную. | Сигнал не покрывает пользовательский симптом. | Проверить alert на синтетическом сценарии и его маршрут доставки. | Изменить сигнал, порог или инструкцию, сохранив rollback. |
| Источник появился после решения. | Причина реконструирована позднее. | Убрать ссылку из контекста решения и обозначить unknown. | Поставить отдельный эксперимент для проверки механизма. |
Формулировка «причина не подтверждена» не является провалом документа. Это точная граница знания. Неподтверждённая причина лучше красивого, но ложного вывода: она показывает, какую телеметрию, тест или безопасный эксперимент нужно добавить.
\n«Добавить мониторинг» — намерение, а не действие. Запись становится проверяемой, если в ней названы изменяемый артефакт, владелец роли, критерий и обратный путь. Например: «до следующего тестового релиза добавить alert на долю 5xx для маршрута X; критерий — synthetic-запрос вызывает сигнал за пять минут; откат — удалить правило и вернуть прежний порог». Такой пункт проверяет наличие защиты, но ещё не доказывает снижение production-инцидентов.
\nПолезно разделить исправление и измерение. Исправление меняет код, конфигурацию, runbook или процесс. Измерение показывает, сработала ли защита в выбранном окне. Для измерения нужно назвать baseline, вариант сравнения и длительность наблюдения. Без них фраза «ошибок стало меньше» неотличима от впечатления.
\nУ каждого action item есть стоимость и риск. Алерт дешевле изменения протокола, но создаёт шум, если команда не определила владельца реакции. Регрессионный тест ловит известный класс входа раньше production, но не покрывает неизвестный внешний отказ. Перечислите альтернативы и выберите одну по риску, обратимости и доступному доказательству.
\nЭта модель не заменяет incident command, расследование безопасности, юридическую оценку или правила работы с персональными данными. В security-контуре доступ к логам, копирование доказательств и сроки хранения задаются отдельной политикой. Руководство NIST SP 800-61 Rev. 2 относится к компьютерным инцидентам безопасности и не является универсальным шаблоном для любой деградации продукта.
\nВ распределённой системе часы могут расходиться, логи могут быть потеряны, а dashboard — пересчитать историю. Тогда честная запись должна указать неопределённость и версию источника. Если нельзя безопасно воспроизвести сценарий, не называйте учебную проверку экспериментом в production. Если откат сам создаёт риск, сначала получите одобрение и подготовьте промежуточный защитный шаг.
\nBlameless-подход также не отменяет расследование умышленного нарушения, контроля доступа или требований закона. Он отвечает на другой вопрос: как извлечь технический урок из условий, в которых система и люди действовали. Для дисциплинарных и юридических решений нужна отдельная процедура с соответствующими полномочиями.
\nРазбор можно отдавать на технический review, если независимый читатель открывает источник каждого факта, видит границу между временем решения и поздними находками, понимает, какая гипотеза ещё не доказана, и может проверить один action item по критерию и rollback. Если не хватает источника, временной связи или способа измерения, статус должен оставаться «не готов», а следующий шаг — устранение конкретного пробела.
\n