2 lines
16 KiB
JSON
2 lines
16 KiB
JSON
{"index":4,"slug":"editorial-2027-11-field-mistakes-revisions","title":"Incident runbook: как пройти от симптома к проверенному rollback","excerpt":"Практический маршрут инцидента: зафиксировать симптом и границу влияния, проверить одну гипотезу, выполнить обратимое действие и убедиться, что восстановилась система, а не только один график.","contentHtml":"<p>Проблема во время инцидента начинается с фразы «сервис упал после релиза». Она смешивает наблюдение, время и причинность. На практике симптом может выглядеть иначе: HTTP 503, рост p95 latency, очередь сообщений или ошибки только у одного метода и в одном регионе.</p><p>Цена такой неточности — лишние изменения и потеря следов причины. Оператор перезапускает всё подряд, меняет несколько параметров или откатывает код, не проверив совместимость со схемой базы. Ошибка исчезает на минуту, а команда не знает, что именно помогло и не осталось ли повреждённое состояние.</p><h2>Тезис: runbook должен ограничивать неопределённость</h2><p>Хороший runbook не угадывает первопричину за оператора. Он превращает тревожный сигнал в короткий цикл: зафиксировать факт, сузить область, сформулировать гипотезу, выполнить одну обратимую проверку и измерить результат. Если гипотеза не подтверждается, инструкция должна вести к следующей проверке, а не подталкивать к тому же действию.</p><p>Начинайте с полей, которые можно заполнить без интерпретации: timestamp, affected endpoint, регион, версия, доля ошибок, latency, saturation и baseline. Запись «всё медленно» не задаёт ни области поиска, ни критерия улучшения. Запись «POST /payments, EU, 5xx 8%, p95 1,8 с, baseline — предыдущее окно» уже позволяет выбрать следующий шаг.</p><h2>Механизм: сигнал, гипотеза, действие, восстановление</h2><p>Статус 500, задержка и error rate описывают разные стороны одного события. Они могут иметь общий корень, но не обязаны. Поэтому классификация сигнала должна быть детерминированной и прозрачной: высокий приоритет получает отказ или превышение error threshold, затем проверяется latency threshold. Классификатор помогает выбрать срочность, но не доказывает причину.</p><p>Гипотеза связывает наблюдение с проверяемым признаком: «после изменения лимита pool выросло ожидание соединения; проверка — метрика pool wait в том же временном окне». В формулировке должны быть условие и ожидаемый сигнал. Гипотеза «виноват backend» слишком широкая: её нельзя опровергнуть одним локальным измерением.</p><p>Первое изменение должно уменьшать blast radius. Подойдут отключение feature flag, остановка нового consumer или перевод небольшой доли трафика на старую версию — если такой путь предусмотрен архитектурой. Перезапуск без фиксации метрик может убрать симптом и одновременно удалить полезный контекст.</p><figure><img src=\"/assets/editorial/2027/mistakes-revisions-2027-evidence-handoff-loop.svg\" alt=\"Петля incident runbook: симптом и область влияния переходят в проверку гипотезы, одно обратимое действие и измерение восстановления\" loading=\"lazy\" /><figcaption>Каждое действие в runbook должно иметь ожидаемый эффект, условие возврата и отдельную проверку результата.</figcaption></figure><h2>Минимальный рабочий пример</h2><p>Ниже — небольшая чистая функция. Она принимает HTTP status, latency и error rate, применяет два явно заданных порога и возвращает классификацию. Числа в примере учебные: функция не знает бизнес-критичность endpoint, не отправляет уведомления и не выбирает rollback.</p><pre><code>function classifySignal({ status, latencyMs, errorRate, latencyLimitMs = 1000, errorLimit = 0.05 }) {\n if (!Number.isInteger(status) || !Number.isFinite(latencyMs) || !Number.isFinite(errorRate)) {\n return { ok: false, reason: 'signal-invalid' };\n }\n if (status >= 500 || errorRate >= errorLimit) {\n return { ok: true, severity: 'high', reason: 'availability-or-error-threshold' };\n }\n if (latencyMs >= latencyLimitMs) {\n return { ok: true, severity: 'medium', reason: 'latency-threshold' };\n }\n return { ok: true, severity: 'low', reason: 'signal-below-threshold' };\n}\n\nconsole.log(classifySignal({ status: 503, latencyMs: 820, errorRate: 0.08 }));\n// { ok: true, severity: 'high', reason: 'availability-or-error-threshold' }</code></pre><p>Проверяемость здесь важнее полноты. Для одинакового входа функция возвращает одинаковый результат; невалидный status или нечисловая метрика не превращаются в уверенный severity. Реальный runbook должен отдельно описать источник каждой метрики, допустимое окно и владельца действия.</p><h2>Карта диагностики</h2><div class=\"table-scroll\"><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>5xx растёт сразу после выката</td><td>Новая версия или flag затронули часть трафика</td><td>Сравнить версии, долю трафика и error rate по endpoint</td><td>Ограничить flag или traffic split, затем повторить измерение</td></tr><tr><td>p95 растёт, 5xx не меняется</td><td>Ожидание соединения, CPU или внешнего ответа</td><td>Сопоставить latency breakdown, saturation и pool wait</td><td>Не перезапускать всё; убрать один изменённый лимит, если он подтверждён</td></tr><tr><td>Очередь увеличивается после отката</td><td>Consumer остановлен или не справляется с потоком</td><td>Проверить lag, rate обработки и ошибки consumer</td><td>Вернуть только совместимый consumer или остановить входной поток по policy</td></tr><tr><td>5xx снизился, операции не завершаются</td><td>Сервис доступен, но есть незавершённое состояние</td><td>Сверить успешные операции, очередь, дубли и состояние данных</td><td>Не закрывать инцидент; запустить recovery-проверку и сохранить факты</td></tr><tr><td>Timeout при rollback</td><td>Изменение не применилось или control plane недоступен</td><td>Проверить фактическую версию и audit log, а не только код команды</td><td>Остановить новые изменения и выбрать заранее описанный ручной путь</td></tr></tbody></table></div><p>В таблице указаны направления проверки, а не универсальные причины. Один симптом может иметь несколько объяснений. Оператор должен подтвердить выбранную ветку своим сигналом и записать результат рядом с timestamp.</p><h2>Rollback не равен восстановлению</h2><p>Rollback возвращает конфигурацию, feature flag или binary. Recovery означает, что система снова выполняет допустимую работу и данные остаются согласованными. Можно успешно вернуть старую версию и оставить очередь, двойные записи или несовместимые события. Поэтому после rollback проверяйте не только 5xx, но и lag, успешность операций, saturation и консистентность данных.</p><p>До отката проверьте совместимость. Старая версия должна читать текущую схему базы и понимать события, которые уже создала новая версия. Если перед инцидентом была миграция, порядок действий нельзя восстанавливать по памяти: в runbook нужны матрица совместимости, ожидаемый эффект, риск и способ вернуть сам rollback.</p><p>Критерий завершения должен быть измеримым. Формулировка «график выглядит лучше» не подходит. Нужны конкретные условия: error rate ниже согласованного порога, latency вернулась к допустимому диапазону, очередь не продолжает расти, а контрольная бизнес-операция проходит. Интервал наблюдения зависит от окна метрики и характера нагрузки; его задают заранее, а не в момент усталости.</p><h2>Порядок действий</h2><ol><li>Запишите timestamp, scope, версию и один измеримый симптом. Сохраните ссылку на dashboard и baseline.</li><li>Проверьте границу влияния: регионы, методы, версии, долю трафика и затронутые зависимости.</li><li>Сформулируйте одну гипотезу и одну проверку. Для проверки заранее назовите сигнал, который должен измениться.</li><li>Выберите одно обратимое действие. Запишите ожидаемый эффект, риск и условие возврата до выполнения команды.</li><li>После действия выдержите заданное окно и сравните error rate, latency, saturation, очередь и бизнес-сигнал.</li><li>Если сигнал не улучшился, остановите ветку и зафиксируйте отрицательный результат. Не добавляйте второе изменение, пока не понятен эффект первого.</li><li>После стабилизации сохраните timeline, фактическое состояние версии и оставшиеся риски. Не удаляйте временные изменения до проверки recovery.</li></ol><h2>Ограничения и критерий готовности</h2><p>Этот маршрут не заменяет on-call график, права доступа, SLO, уведомления и локальную матрицу severity. Классификатор не видит traces, не отличает бизнес-критичный endpoint от второстепенного и не знает, можно ли безопасно повторить операцию. Порог 5% и latency 1000 мс в примере не являются рекомендацией для конкретной системы.</p><p>Runbook готов, если оператор может без устного контекста ответить на пять вопросов: какой факт зафиксировать, где проходит граница влияния, какую гипотезу проверить, какое действие обратимо и по какому сигналу считать recovery подтверждённым. Для каждого опасного шага должны существовать условие остановки и путь возврата.</p><p>Минимальная проверка инструкции — проиграть один сценарий на тестовом окружении с контролируемым 503 и пройти ветку до конца. Такой прогон проверяет структуру runbook, но не доказывает поведение production, отсутствие флаков или безопасность реального rollback. Фактические результаты конкретной системы нужно приложить отдельно.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-61r2.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-61 Revision 2 — Computer Security Incident Handling Guide</a> — структура обработки инцидента: подготовка, обнаружение и анализ, containment, eradication/recovery и post-incident activity. Документ не задаёт архитектуру, пороги severity или права на rollback конкретной системы.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9110 — HTTP Semantics</a> — семантика HTTP-методов, статусов и условных запросов. Документ не является runbook и не заменяет метрики, логи, traces и проверку конкретного сервиса.</li></ul>"}
|