8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 254,
|
||
"slug": "editorial-2020-12-mechanism-incident-review",
|
||
"title": "Разбор инцидента: как не перепутать симптом с причиной",
|
||
"excerpt": "После восстановления сервиса команда часто фиксирует обход как окончательное решение. Разбираем цепочку от наблюдения до проверки и показываем, как сохранить отрицательный результат и превратить вывод в проверяемое действие.",
|
||
"contentHtml": "<p>Сервис вернул ошибку, очередь выросла, а после отката показатели снова стали нормальными. На этом месте разбор часто заканчивается фразой: «вернули старую настройку и добавили тест». Через месяц тот же симптом появляется после другого изменения. Команда повторяет обход, но не знает, что именно сломалось и какой сигнал подтверждает исправление.</p>\n<p>Цена ошибки — не только повторный сбой. Временное действие становится частью обычного пути. Оно может скрывать потерю данных, увеличивать задержку или переносить отказ на следующую границу. Отчёт сохраняет уверенный рассказ, но не сохраняет ход проверки. Следующий инженер восстанавливает историю по памяти.</p>\n<p>Надёжный разбор разделяет пять состояний: наблюдение, гипотезу, действие, проверку и профилактику. Наблюдение описывает факт. Гипотеза связывает факты и остаётся опровержимой. Действие меняет одну ограниченную часть системы. Проверка измеряет результат этого действия. Профилактика меняет будущий путь и считается выполненной только после отдельной проверки.</p>\n<h2>Сначала фиксируем границу и симптом</h2>\n<p>Разбор начинается не со слова «причина». Сначала нужно назвать границу, на которой появился эффект. Это может быть переход между mapper и адаптером, запись в базу, публикация сообщения или вызов внешнего API. Затем нужно записать наблюдаемый результат и вход, на котором он возник.</p>\n<p>Рассмотрим учебный пример. Объект заказа проходит через mapper и приходит в <code>pricing-adapter</code>. Адаптер ожидает обязательное поле <code>currency</code>. После изменения mapper поле исчезает, а учебная операция <code>preview-order</code> возвращает <code>blocked</code>. Эти два факта не доказывают, что mapper — единственная причина. Они задают узкую ветку для проверки.</p>\n<p>Учебный пример ниже синтетический. Он не описывает production-систему, реальный трафик, время восстановления или фактический инцидент. Его задача — показать форму рассуждения на маленьком входе.</p>\n<h2>Механизм: пять состояний не смешиваются</h2>\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><code>preview-order</code> вернул <code>blocked</code></td><td>После mapper отсутствует обязательное поле</td><td>Сравнить вход и объект на границе адаптера</td><td>Зафиксировать потерю поля и не расширять retry без отдельной причины</td></tr><tr><td><code>currency</code> отсутствует после преобразования</td><td>Новый mapper не перенёс поле</td><td>Подать минимальный объект в старый и новый mapper</td><td>Временно вернуть известный вариант, если это обратимо и разрешено</td></tr><tr><td>Старый mapper вернул <code>allowed</code></td><td>Обход работает только для данного входа</td><td>Проверить наличие <code>currency</code> и ожидаемый результат</td><td>Оставить обход временным и назначить контрактную проверку</td></tr><tr><td>Новая версия снова теряет поле</td><td>Контракт не защищает обязательный атрибут</td><td>Запустить проверку на минимальном входе до изменения</td><td>Отклонять преобразование без <code>currency</code></td></tr></tbody></table>\n<p>В первой строке есть симптом, но нет диагноза. Во второй появляется гипотеза. В третьей проверка подтверждает только выбранное действие для конкретного входа. Она не доказывает, что исправлены все пути заказа. В четвёртой появляется профилактика. Такая граница не даёт назвать совпадение устранением причины.</p>\n<p>Наблюдение должно пережить неудачную гипотезу. Запись «после преобразования поле отсутствует» останется верной, даже если поле пропало раньше mapper. Запись «mapper отбросил поле» уже утверждает больше, чем показал сигнал. Если проверка опровергнет эту гипотезу, меняется гипотеза, а не прошлое наблюдение.</p>\n<h2>Конкретный пример</h2>\n<p>Минимальный код должен показывать только учебный контракт. Он не имитирует сеть и не создаёт видимость реального журнала:</p>\n<pre><code>const input = { amount: 1250, currency: 'RUB' };\n\nfunction mapOrder(order) {\n return { amount: order.amount };\n}\n\nfunction validateForPricing(order) {\n return order.currency ? 'allowed' : 'blocked';\n}\n\nconst mapped = mapOrder(input);\nconst symptom = validateForPricing(mapped);\n\nif (symptom !== 'blocked') throw new Error('expected the exercise symptom');\nif ('currency' in mapped) throw new Error('the field should be absent here');\n\n// Учебный об�\nод: используем известное преобразование.\nconst restored = { amount: input.amount, currency: input.currency };\nconst check = validateForPricing(restored);\n\nif (check !== 'allowed') throw new Error('the bounded check failed');</code></pre>\n<p>Этот код показывает две разные вещи. Сначала он воспроизводит симптом: mapper возвращает объект без <code>currency</code>. Затем он проверяет обратимый обход на том же входе. Успешный <code>allowed</code> подтверждает только действие для учебного сценария. Он не подтверждает новую реализацию, все валюты, другие версии контракта или production-эффект.</p>\n<p>Не стоит добавлять к этому же действию повторные попытки, увеличенный таймаут и новый кеш. Каждая мера меняет другую переменную. Если результат улучшится, команда не узнает, что повлияло на него. Retry уместен, когда наблюдение указывает на временную ошибку и есть защита от дублей. Таймаут уместен, когда проверена задержка, а не потеря поля. Для каждой меры нужна отдельная гипотеза и отдельная проверка.</p>\n<figure><img src=\"/assets/editorial/2020/incident-decision-record-2020.svg\" alt=\"Схема decision record: наблюдение E2 ведёт к гипотезе H1, ограниченному действию A1 и проверке V1; профилактика P1 имеет собственный критерий\" loading=\"lazy\" /><figcaption>Проверяемая цепочка отделяет временный обход от профилактики и не назначает виноватого.</figcaption></figure>\n<h2>Решение должно иметь условие и ожидаемый сигнал</h2>\n<p>Во время инцидента действие выбирают быстро. Это не отменяет его границ. Запишите три вещи: какой симптом запускает действие, что именно изменится и какой сигнал должен появиться. Добавьте условие возврата. Тогда через час можно отличить результат действия от случайного совпадения.</p>\n<p>В учебном случае решение выглядит так: если на границе адаптера нет <code>currency</code>, не расширять retry и не увеличивать timeout; временно использовать известное преобразование; проверить наличие поля и статус <code>allowed</code> на том же входе; при отрицательном результате вернуться к новой гипотезе. Это не утверждает, что retry или timeout вредны всегда. Они просто не проверяют данную гипотезу.</p>\n<p>Проверка обязана иметь отрицательный путь. Если старый mapper тоже возвращает <code>blocked</code>, обход не подтвердился. Нельзя переписать это как «система всё равно восстановилась». Нужно сохранить новый факт: действие не объяснило симптом. Затем проверить вход до mapper, версию контракта и другую ветку обработки. Отрицательный результат сокращает пространство поиска.</p>\n<h2>Профилактика меняет будущий путь</h2>\n<p>Фраза «добавить тест» не описывает работу. Назовите защищаемый контракт, место проверки и ожидаемый отказ. Например: преобразование должно отклоняться, если не передало <code>currency</code>; проверка должна выполняться на границе <code>pricing-adapter</code>; минимальный вход должен приводить к явной ошибке до вызова адаптера.</p>\n<p>Профилактика не закрыта в момент, когда её записали. Она готова после того, как изменение появилось в коде, проверка запускается в нужном пути и отрицательный сценарий действительно ломает сборку или тест. До этого состояние нужно назвать планом. Иначе отчёт приписывает системе защиту, которой ещё нет.</p>\n<h2>Порядок действий</h2>\n<ol><li>Назовите одну границу и один наблюдаемый симптом. Запишите вход и время наблюдения.</li><li>Снимите данные до изменения: поля объекта, ответ, код ошибки, версию и доступный контекст.</li><li>Сформулируйте одну гипотезу, которая объясняет конкретное наблюдение и допускает маленький эксперимент.</li><li>Выберите обратимое действие с ограниченным радиусом. Запишите trigger, ожидаемый сигнал и условие возврата.</li><li>Проверьте действие на том же входе. Не расширяйте результат на неизвестные пути.</li><li>Если проверка отрицательна, сохраните этот факт и вернитесь к следующей гипотезе. Не объявляйте обход успешным задним числом.</li><li>Сформулируйте профилактику как изменение контракта, теста или наблюдения. Укажите отдельный критерий её выполнения.</li></ol>\n<h2>Ограничения модели</h2>\n<p>Разделение состояний не заменяет мониторинг, резервирование, контроль доступа, процедуру отката и техническое расследование. Оно не вычисляет корневую причину автоматически. Одна ошибка может иметь несколько условий: потерю поля, отсутствие проверки и слишком широкий обход. Для каждого условия нужны собственные наблюдения и проверки.</p>\n<p>Учебный код не измеряет доступность, задержку, нагрузку, время восстановления или влияние на пользователей. Он не подтверждает, что конкретный mapper вызвал реальный отказ. В production нужно проверить трассировку поля, фактический контракт адаптера, версию схемы, права, данные и поведение внешних зависимостей. Если этих данных нет, вывод нужно ограничить известным сценарием.</p>\n<h2>Критерий готовности</h2>\n<p>Разбор можно считать технически готовым, когда другой инженер без устного пересказа может назвать симптом, увидеть evidence, понять проверяемую гипотезу, повторить действие на ограниченном входе и получить ожидаемый сигнал. Он также должен видеть, что не проверено, какой отрицательный результат меняет направление поиска и какое изменение защищает систему в будущем.</p>\n<p>Практическая финальная проверка проста: удалите из текста слова «исправили» и «добавили тест» и замените их конкретными условиями. Если после этого остаются вход, граница, действие, сигнал, откат и критерий профилактики, запись пригодна для работы. Если остаётся только уверенный пересказ, расследование ещё не закончено.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://sre.google/sre-book/managing-incidents/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Book: Managing Incidents</a> — официальный материал о живом состоянии инцидента, разделении ответственности, восстановлении и сохранении контекста.</li><li><a href=\"https://sre.google/sre-book/postmortem-culture/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Book: Postmortem Culture</a> — официальный материал о разборе влияния, причин и follow-up без персонального обвинения.</li><li><a href=\"https://sre.google/sre-book/effective-troubleshooting/\" target=\"_blank\" rel=\"noopener noreferrer\">Google SRE Book: Effective Troubleshooting</a> — официальный материал о наблюдениях, проверяемых гипотезах и ценности отрицательного результата.</li></ul>"
|
||
}
|