2 lines
22 KiB
JSON
2 lines
22 KiB
JSON
{"index":150,"slug":"editorial-2023-11-practice-postmortem","title":"Postmortem, который помогает исправить систему","excerpt":"Практическая схема разбора сбоя: отделяем наблюдение от гипотезы, сохраняем контекст решения и превращаем профилактику в проверяемое действие.","contentHtml":"<p>После сбоя команда быстро находит удобную историю: «ошибка началась после релиза, значит, релиз всё сломал». История может оказаться неверной. Она смешивает наблюдение, решение дежурного и позднюю гипотезу о причине. В результате следующий инженер получает уверенный текст, но не получает способ проверить его.</p>\n<p>Цена такого postmortem — не только повторный простой. В документе теряется контекст: какие сигналы были доступны в момент решения, почему выбрали откат, какие данные ещё не проверены и что именно должно изменить систему. Обсуждение смещается к имени последнего автора изменения, а не к слабому контролю, неясному лимиту или отсутствующему сигналу.</p>\n<p>Ниже — практический маршрут для небольшого HTTP-инцидента. Он не выдаёт учебный сценарий за production-расследование. Его задача — помочь записать факты, связать действие с доступной информацией и назначить следующую проверку так, чтобы другой инженер мог воспроизвести её без устного пересказа.</p>\n<h2>Начните с симптома и цены ошибки</h2>\n<p>Первая запись должна описывать наблюдаемое событие, а не объяснение. Назовите путь или компонент, окно времени, сигнал и последствия для пользователя. «С 10:03 до 10:08 шлюз вернул 502 на 18% запросов к <code>/checkout</code>» уже можно проверять по метрике. «Новый таймаут сломал оплату» — пока гипотеза.</p>\n<p>Цена ошибки нужна, чтобы выбрать глубину разбора. Ею может быть число недоступных операций, объём ручного восстановления, потеря данных или задержка обработки. Не подменяйте измерение оценкой: если количество затронутых запросов неизвестно, так и напишите и назначьте способ его посчитать.</p>\n<table><caption>Четыре записи, которые нельзя смешивать</caption><thead><tr><th>Запись</th><th>На какой вопрос отвечает</th><th>Минимальные поля</th><th>Граница вывода</th></tr></thead><tbody><tr><td>Симптом</td><td>Что увидел пользователь или мониторинг?</td><td>путь, окно, сигнал, воздействие</td><td>не объясняет причину</td></tr><tr><td>Факт</td><td>Что подтверждено источником?</td><td>timestamp, наблюдение, ссылка на лог, метрику или конфигурацию</td><td>не назначает виновника</td></tr><tr><td>Решение</td><td>Что сделали с доступной информацией?</td><td>время, действие, список доступных fact ID, обратимость</td><td>не доказывает, что решение устранило причину</td></tr><tr><td>Проверка</td><td>Как отделить гипотезу от альтернативы?</td><td>вход, метод, критерий PASS/FAIL, остановка, владелец</td><td>не обещает результат до прогона</td></tr></tbody></table>\n<h2>Сохраните временную границу</h2>\n<p>В postmortem полезно различать две временные шкалы. Первая — события системы: ошибка, изменение конфигурации, запрос к upstream, откат. Вторая — знания команды: что было видно до решения и что выяснилось уже после. Поздний trace может объяснить механизм, но не был основанием для решения, принятого пять минут раньше.</p>\n<figure><img src=\"/assets/editorial/2023/postmortem-2023-fact-timeline.svg\" alt=\"Учебная временная шкала: факты 10:03 и 10:04 предшествуют решению в 10:05, а эксперимент запланирован после отката\" /><figcaption>Учебная схема временной границы: решение связано только с фактами, доступными до него; эксперимент проверяет гипотезу после временного восстановления.</figcaption></figure>\n<p>Для каждого события храните источник и время, а для решения — список фактов, которыми дежурный действительно располагал. Это защищает от hindsight bias, то есть от подмены прежней неопределённости знанием, появившимся позже. Так команда оценивает не личную «внимательность», а качество сигналов и доступных процедур.</p>\n<h2>Разберите учебный HTTP-инцидент</h2>\n<p>Возьмём изолированный пример. В 10:03 шлюз заметил рост 502 на <code>/checkout</code>. В 10:04 команда увидела, что сервис использует версию конфигурации 17 с новым таймаутом. В 10:05 дежурный вернул версию 16. В 10:08 доля 502 снизилась. Последнее наблюдение подтверждает эффект отката во времени, но не доказывает, что именно таймаут был единственной причиной: мог закончиться всплеск нагрузки, восстановиться upstream или исчезнуть другая ошибка маршрутизации.</p>\n<p>Следующий фрагмент можно запустить локально: ему нужен только Node.js. Он проверяет узкое правило — решение не может ссылаться на факт, появившийся позже. Команда не обращается к логам и не меняет конфигурацию, поэтому результат запуска не является доказательством состояния настоящего сервиса.</p>\n<pre><code>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</code></pre>\n<p>В shell этот блок завершится строкой <code>PASS</code> и напечатает запись. Если заменить список <code>availableFactIds</code> на <code>['f-1', 'f-3']</code>, проверка завершится ошибкой: факт о снижении ошибок возник после решения. Это и есть полезный отрицательный тест. Он не доказывает root cause, зато не позволяет задним числом приписать дежурному знание, которого у него не было.</p>\n<p>Для реального расследования к этой структуре добавьте идентификатор инцидента, часовой пояс, единицы измерения и ссылки с понятным сроком хранения. Источник должен вести к разрешённому артефакту. Ссылка на поиск в логах без фиксированного окна и запроса через неделю может вернуть уже другой результат.</p>\n<h2>Не принимайте откат за доказанную причину</h2>\n<p>Откат — это mitigation: действие, которое уменьшает воздействие прямо сейчас. Причина — объяснение механизма, подтверждённое несколькими наблюдениями или безопасным экспериментом. Между ними может быть связь, но её нельзя объявлять установленной только потому, что симптом ослаб после отката.</p>\n<table><caption>Маршрут от наблюдения к следующему действию</caption><thead><tr><th>Наблюдение</th><th>Что пока нельзя утверждать</th><th>Проверка</th><th>Действие и критерий</th></tr></thead><tbody><tr><td>502 вырос после релиза</td><td>релиз — единственная причина</td><td>сопоставить версии, трафик, upstream-коды и соседние изменения в одном окне</td><td>сохранить откат как mitigation; PASS — все сравниваемые источники согласованы</td></tr><tr><td>ошибки снизились после возврата конфигурации</td><td>новый таймаут доказан как root cause</td><td>повторить на тестовом трафике тот же класс запросов с версиями 16 и 17</td><td>остановить эксперимент при росте 5xx; PASS — записаны ответ шлюза и статус upstream для каждой попытки</td></tr><tr><td>в логе есть имя инженера</td><td>человек объясняет техническую причину</td><td>проверить, какой контроль разрешил изменение и какая информация была доступна</td><td>заменить имя в причинной цепочке на изменение, контроль и владельца follow-up</td></tr><tr><td>в отчёте написано «добавить мониторинг»</td><td>новый сигнал предотвратит повтор</td><td>назвать маршрут, окно, порог, канал и тест тревоги</td><td>PASS — тестовый сигнал срабатывает на искусственном нарушении; FAIL — открыть действие с rollback</td></tr></tbody></table>\n<p>Гипотеза должна быть узкой: «при одинаковом классе запроса версия 17 увеличивает долю 502 из-за таймаута ожидания upstream». Она лучше общего «конфигурация ненадёжна», потому что задаёт входы и наблюдения. Альтернатива тоже нужна: например, «502 вызван исчерпанием соединений независимо от таймаута». Иначе эксперимент будет подтверждать любимое объяснение, а не различать варианты.</p>\n<h2>Сделайте action item проверяемым</h2>\n<p>Строка «разобраться с таймаутами» не является планом. Хорошая corrective action меняет контроль или код и имеет наблюдаемый критерий. В ней должны быть один владелец, срок, ссылка на место изменения, способ проверки и обратное действие. Если результат нельзя выразить через PASS/FAIL, сначала уточните, какой артефакт должен появиться.</p>\n<pre><code>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.</code></pre>\n<p>Пример описывает будущую проверку, а не уже достигнутый результат. До запуска нельзя писать «тест защитил пользователей». После запуска сохраните фактический статус, версию теста и условия прогона. Если staged replay не повторяет production-нагрузку или upstream недоступен, результат ограничен именно этой средой.</p>\n<h2>Выберите момент для postmortem</h2>\n<p>Postmortem стоит запускать по заранее известным признакам, а не только по субъективному ощущению масштаба. Google SRE перечисляет среди типовых триггеров заметную деградацию для пользователей, потерю данных, вмешательство дежурного вроде отката или перенаправления трафика, длинное восстановление и отказ мониторинга. Это ориентир, а не обязательный порог для каждой команды: его нужно сопоставить с риском сервиса, договорённостями об уровне доступности и правилами приватности.</p>\n<p>До инцидента зафиксируйте, кто открывает документ, где лежит timeline, кто делает технический review и что считается закрытым follow-up. После инцидента сначала сохраните артефакты с нужным сроком хранения, затем попросите review у людей, которые могут проверить полноту воздействия, глубину причины и реалистичность действий. Не закрывайте запись лишь потому, что пользовательский симптом исчез.</p>\n<h2>Ограничения и границы применимости</h2>\n<p>Blameless не означает отсутствие ответственности. Такой подход убирает обвинение человека из причинной цепочки, но оставляет владельца действия, срок, контроль и критерий завершения. Если изменение нарушило правило доступа или процесс, документируйте сам факт нарушения и слабость контроля; не приписывайте мотив, не публикуйте лишние персональные данные и не превращайте имя в техническое объяснение.</p>\n<p>В распределённой системе один trace не равен полной истории. Сообщение могло потеряться до входа в сервис, часы на узлах могли расходиться, а метрика шлюза могла не учитывать ошибки до него. Откат может убрать симптом и оставить повреждённые данные. Поэтому для операций записи отдельно проверяйте целостность, повторяемость и идемпотентность; для приватных инцидентов — доступ, retention и допустимый объём публикации.</p>\n<p>Учебный Node.js-код проверяет только порядок двух timestamp. Он не подключается к HTTP, не читает production-метрики, не выполняет откат и не устанавливает причинность. Приведённые имена маршрута, версии и проценты вымышлены для воспроизводимости. В своём проекте замените их реальными источниками и укажите версию среды: синтетический прогон на Node.js не подтверждает поведение конкретного шлюза, базы данных или провайдера.</p>\n<h2>Проверьте документ перед разбором</h2>\n<ol><li><strong>Назовите симптом.</strong> Запишите путь, окно, единицы измерения и воздействие. Отделите неизвестное от нулевого значения.</li><li><strong>Соберите факты.</strong> Привяжите каждое наблюдение к источнику, timestamp и сроку хранения. Исправьте часовые пояса.</li><li><strong>Восстановите решение.</strong> Укажите только те fact ID, которые существовали до действия, и опишите его обратимость.</li><li><strong>Разделите временное и постоянное.</strong> Отметьте откат или переключение как mitigation, а root cause оставьте гипотезой, пока её не подтверждает проверка.</li><li><strong>Оформите follow-up.</strong> Добавьте узкую гипотезу, вход, метод, PASS/FAIL, условие остановки, владельца и evidence.</li><li><strong>Проведите review.</strong> Попросите другого инженера пройти timeline и выполнить локальный пример с отрицательной веткой.</li><li><strong>Закройте действие по факту.</strong> Сохраните результат прогона, ссылку на изменение и остаточный риск. Если критерий не выполнен, это FAIL, а не «почти готово».</li></ol>\n<p>Готовность postmortem — это не обещание, что инцидент больше не повторится. Более узкий и честный критерий таков: инженер, не участвовавший в сбое, видит, что произошло, что было известно в момент решения, какое объяснение ещё проверяется и какое действие даст следующий измеримый сигнал.</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 и follow-up.</li><li><a href=\"https://sre.google/workbook/postmortem-culture/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Workbook: Postmortem Culture</a> — официальный практический разбор с шаблонами, case study и ограничением «one size fits all».</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/61/r3/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-61 Rev. 3: Incident Response Recommendations and Considerations</a> — официальный документ NIST по организации incident response; применяйте его рекомендации с учётом типа риска, среды и действующих требований.</li></ul>"}
|