Files

2 lines
22 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"index":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 &lt;&lt;'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) =&gt; [fact.id, fact]));\nfor (const factId of decision.availableFactIds) {\n const fact = byId.get(factId);\n if (!fact || new Date(fact.at) &gt;= 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>"}