{ "index": 253, "slug": "editorial-2020-12-field-incident-review", "title": "Разбор инцидента: как не принять обход за исправление", "excerpt": "Запрос возвращает blocked, потому что обязательное поле теряется на границе mapper. Разбираем, как отделить факт от гипотезы, проверить обратимый обход и оставить защиту от повторения.", "contentHtml": "
Представим разбор инцидента: запрос preview-order возвращает blocked, хотя вход выглядит допустимым. После преобразования объекта пропадает обязательное поле currency. Возврат к прежнему mapper даёт allowed, и команда закрывает задачу. Цена ошибки проявится позже: новый mapper снова попадёт в путь, поле снова исчезнет, а временный обход уже будут считать исправлением.
Восстановление сервиса и устранение причины — разные события. Временное действие должно иметь условие запуска, ожидаемый результат и путь отмены. Отдельно нужна проверка, которая не даст известному нарушению контракта вернуться. Ниже — учебный сценарий с воспроизводимой fixture, а не заявление о конкретном production-инциденте.
\nРассмотрим только переход от mapper к локальной функции previewOrder, которая имитирует границу pricing-adapter. Стенд не обращается к сети, базе, очереди или внешнему API. Вход содержит сумму, идентификатор товара и валюту. Новый mapper намеренно теряет currency, старый сохраняет все поля. Так мы проверяем одну гипотезу и не выдаём результат fixture за результат системы.
const input = { amount: 1000, itemId: 'demo-1', currency: 'RUB' };\n\nfunction newMapper(order) {\n return { amount: order.amount, itemId: order.itemId };\n}\n\nfunction oldMapper(order) {\n return { ...order };\n}\n\nfunction previewOrder(mapped) {\n if (!mapped.currency) {\n return { status: 'blocked', reason: 'currency_missing' };\n }\n return { status: 'allowed', currency: mapped.currency };\n}\n\nconst broken = previewOrder(newMapper(input));\nconst fallback = previewOrder(oldMapper(input));\n\nif (broken.status !== 'blocked' || fallback.status !== 'allowed') {\n throw new Error('fixture contract failed');\n}\n\nconsole.log({ broken, fallback });\nОжидаемый вывод — broken.status === 'blocked' с причиной currency_missing и fallback.status === 'allowed' с валютой RUB. Важна не удачная строка в консоли, а контраст двух результатов на одном входе. Если изменить вход или правило в previewOrder, вывод уже нельзя переносить на прежний сценарий.
Функции в примере определены прямо в fixture, поэтому её можно сохранить как небольшой файл Node.js и запустить без зависимостей. Это не интеграционный тест: он не проверяет сериализацию, сеть, базу или реальный код mapper. Эти границы нужно проверять отдельно.
\nНаблюдение описывает то, что можно увидеть: один и тот же вход дошёл до границы, после mapper поле отсутствует, а previewOrder вернул blocked. Гипотеза связывает наблюдения: новый mapper не переносит обязательное поле. Она правдоподобна, но не доказана, пока не сравнены вход и результат на этой границе.
До изменения сохраните четыре значения: входной объект, результат mapper, ответ проверки и идентификатор попытки. Если в настоящем сервисе есть лог или trace, запишите ссылку на него; если есть только локальная fixture, назовите её fixture. Не заменяйте отсутствующее наблюдение уверенной фразой «причина найдена».
\nПроверка должна менять одну переменную. В нашем случае новая версия mapper заменяется старой только на ограниченном пути. Если тот же вход даёт allowed и возвращает currency, гипотеза получает поддержку. Но это ещё не доказывает, что она объясняет все отказы: нужно проверить другие обязательные поля и реальные точки сериализации.
| Наблюдение | Гипотеза | Проверка | Владелец | Решение |
|---|---|---|---|---|
preview-order вернул blocked | Нарушен контракт обязательного поля или сработала другая ветка отказа | Сохранить ответ и проверить вход на границе mapper | Инженер, ведущий диагностику | Не менять retry и timeout до локализации причины |
После нового mapper нет currency | Mapper мог отбросить поле при сборке объекта | Сравнить вход, результат нового mapper и результат старого | Владелец адаптера | Сформулировать узкую гипотезу H1 |
Старый mapper даёт allowed | Обход возвращает нужный контракт, но причина не устранена | Повторить fixture на том же входе и проверить сохранение поля | Владелец релиза | Оставить обход ограниченным и записать условие отмены |
| Проверка обхода не прошла | Гипотеза неполна или отказ возник на другой границе | Собрать новое наблюдение до следующего изменения | Ведущий диагностики | Отменить вывод и проверить альтернативную ветку |
| Ошибка вернулась после изменения mapper | Для контракта нет автоматической защиты | Запустить тест на отсутствие обязательного поля | Владелец компонента | Добавить контрактную проверку и связать её с релизом |
Возврат к старому mapper может быть правильным способом уменьшить влияние ошибки. У него должны быть границы: какой маршрут переключается, какой сигнал включает обход, кто его подтверждает и по какому условию он снимается. Без этого временное решение превращается в новый постоянный путь, который никто не проверяет.
\nПосле восстановления отдельно зафиксируйте причинный вывод. В нашем примере он звучит узко: при входе с currency новый mapper возвращает объект без этого поля, а старый mapper поле сохраняет. Формулировка не обещает, что найден единственный дефект во всей системе. Она указывает конкретную границу, которую можно проверить.
Такое разделение совпадает с практикой управления инцидентами: во время сбоя нужны отдельные роли для операционной работы, коммуникации и координации, а после восстановления — живой документ с действиями и состоянием. Это снижает риск, что несколько инженеров одновременно изменят систему и сотрут причинную связь между изменением и результатом.
\nУвеличение timeout скрывает медленный ответ, но не возвращает пропавшее поле. Дополнительный retry повторяет тот же неверный объект и может умножить побочный эффект. Одновременная правка mapper, retry и timeout стирает причинную связь: если результат изменится, станет непонятно, какая правка помогла.
Это не запрет на retry или timeout. Они уместны, когда наблюдение указывает на временную сетевую ошибку или ограничение времени ответа. Но у такого решения должны быть собственный trigger, лимит повторов и собственная проверка. Инструмент выбирают по наблюдаемому механизму, а не по привычке.
\nПредположим, возврат к старому mapper не дал allowed. Это не повод объявить старый код неисправным. Новый факт говорит только о том, что выбранный обход не подтвердил H1. Поле могло отсутствовать уже во входе. Отказ мог зависеть от другой обязательной величины. Следующая проверка должна отличить эти варианты.
Проверим первый вариант: уберём currency из самого входа и запустим старый mapper. Поле останется отсутствующим, поэтому результат blocked больше не подтверждает гипотезу о новом mapper. Проверим второй вариант: оставим валюту, но уберём другой обязательный атрибут. Если отказ сохранится, причина находится не в потерянной валюте.
Хороший review не прячет отрицательный результат. Запись «A1 не подтвердил H1 на входе E1» полезнее, чем «фикс не сработал»: первая формулировка указывает границу нового исследования. Она также не обвиняет автора mapper и оставляет следующий шаг проверяемым.
\nblocked на переходе к pricing-adapter.currency и проходить на корректном объекте.Fixture не измеряет доступность, нагрузку, время восстановления, права доступа или поведение внешней зависимости. Она не заменяет журнал, мониторинг, резервирование и процедуру отката. Схема поясняет последовательность, но не является доказательством результата. Учебный код ограничен одним объектом, двумя mapper и одним обязательным полем.
\nНельзя объявлять production-инцидент исправленным по одному успешному примеру. В реальной системе нужно подтвердить границу на фактическом запросе, проверить безопасный rollout и посмотреть на отрицательные случаи. Нельзя также превращать разбор в поиск виноватого: смена автора mapper не объясняет, почему контракт оказался без защиты.
\nИсточники ниже подтверждают практики incident management и postmortem, но не подтверждают вымышленный запрос preview-order или состояние конкретного сервиса. Факты такого инцидента должны подтверждаться собственными логами, изменениями и конфигурацией мониторинга.
Работа готова, когда симптом воспроизводится на известном входе, действие меняет только заявленную границу и проходит свою проверку, а отрицательный путь приводит к новой гипотезе. Дополнительно автоматическая проверка должна падать на результате mapper без currency и проходить на корректном результате.
Этот критерий не обещает, что система больше никогда не откажет. Он делает более узкое утверждение: конкретный контракт виден, действие проверено, а известный способ регрессии получает автоматическую защиту. Такой результат можно связать с последующим изменением и проверить повторно.
\n