Инциденты и восстановление: как вернуть сервис к нормальной работе
В 14:02 вышла новая версия приложения заказов. Через несколько минут запросы к списку заказов стали медленными, а часть пользователей получила ошибку. PostgreSQL перегружен. Фоновые задачи выполняются с задержкой.
Кажется, достаточно вернуть предыдущую версию приложения. Но перед этим нужно понять, что именно нарушено. Возможно, запросы продолжают накапливаться и откат не разгрузит базу сразу. Возможно, часть заказов сохранилась, хотя клиенты увидели таймаут и повторили отправку. Возможно, HTTP-ответы уже восстановились, но задачи в очереди всё ещё отстают.
Инцидент начинается с нарушения результата, который система обещала пользователю. Он заканчивается, когда этот результат восстановлен и проверен, включая последствия сбоя. Между этими точками инженер решает четыре разные задачи:
обнаружить нарушение → ограничить ущерб → восстановить работу → подтвердить восстановление.
После этого остаётся ещё одна работа: выяснить, почему защита не сработала раньше, и снизить вероятность повторения. Её удобнее выполнять после стабилизации сервиса.
Система заказов и обещание пользователю
В сквозном примере цикла запрос приходит через Caddy в приложение orders-api на FastAPI. Приложение читает кэш из Redis, хранит заказы в PostgreSQL и передаёт фоновые задачи через RabbitMQ обработчику orders-worker. Компоненты запущены в контейнерах Docker.
У этой системы несколько отдельных пользовательских обещаний:
| Действие | Что должно получиться |
|---|---|
Открыть GET /orders |
Получить корректный список заказов за приемлемое время |
Отправить POST /orders |
Узнать, был ли заказ сохранён, и не создать дубль при повторе |
| Дождаться подтверждения | Получить результат фоновой обработки в допустимое время |
Нормальная скорость GET /orders не доказывает, что можно создать заказ. Успешный POST /orders не доказывает, что отработала задача в RabbitMQ. Поэтому инцидент нельзя оценивать единственным показателем «API отвечает».
Для учебного сценария предположим следующее. В пике система получает 90 запросов GET /orders в секунду. Обычно около 70 % чтений обслуживаются из Redis, поэтому к PostgreSQL поступает приблизительно 27 таких запросов в секунду. По результатам испытаний база сохраняет нужную задержку примерно до 50 чтений этого типа в секунду при обычной нагрузке записей.
Эти числа пригодятся для решения во время инцидента. Если доля попаданий в кэш станет нулевой, к базе направятся все 90 чтений в секунду. Тогда одна ошибка в приложении способна перегрузить компонент, который сам по себе исправен.
Вывод: до поиска «упавшего сервера» определите нарушенную пользовательскую операцию и её путь по системе. Это задаёт и нужные сигналы, и критерии восстановления.
Как обнаружить инцидент до жалобы пользователя
Хорошее оповещение сообщает, что страдает пользовательская операция. Высокая загрузка CPU, заполненная очередь или недоступный экспортёр метрик помогают искать причину, но сами по себе не всегда означают нарушение пользовательского результата.
Для системы заказов полезны три основных индикатора:
- доля неуспешных
GET /ordersиPOST /orders, измеренная как можно ближе к входу в систему; - задержка ответов, например p95 по каждому важному маршруту;
- время от принятия заказа до завершения фонового подтверждения.
Допустим, для GET /orders принято учебное правило: оповестить дежурного, если при достаточном трафике более 2 % запросов завершаются серверной ошибкой в течение пяти минут. Долю считают так:
Доля ошибок = число неуспешных попыток / число всех попыток за тот же интервал.
Если за пять минут пришло 27 000 запросов, а 810 закончились ошибкой, доля равна 810 / 27 000 = 0,03, то есть 3 %. Порог и интервал здесь пример, а не универсальная норма: их выбирают исходя из допустимого пользовательского ущерба. При очень редких запросах один сбой может дать огромный процент, поэтому правило должно учитывать и минимальный объём трафика.
Измерение на Caddy или другом входном уровне важно потому, что приложение не зарегистрирует запрос, который до него не дошёл. В то же время внутренние метрики нужны для поиска причины: попадания в Redis, время SQL, ожидание соединения с PostgreSQL, длина и возраст очереди RabbitMQ.
Prometheus рекомендует оповещать прежде всего о симптомах, например о пользовательских ошибках и задержке. Внутренние графики затем помогают определить виновное звено. Внешняя проверка, которая периодически выполняет реальный сценарий, дополняет внутренние измерения: она может заметить проблему даже тогда, когда само приложение перестало отправлять метрики. Это различие между проверкой снаружи и наблюдением внутренних компонентов разобрано в книге Google SRE.
Для важных операций стоит заранее определить уровень обслуживания: какую долю попыток система должна выполнить успешно и за какое время. Тогда серьёзность инцидента определяется не цветом графика, а нарушением конкретного обещания.
Выводы раздела:
- Оповещение должно указывать на пользовательский ущерб и содержать ссылку на графики для диагностики.
- Следите за созданием заказа и завершением фоновой работы отдельно от чтения списка.
- Отсутствие метрик само по себе требует проверки: сломаться может и система наблюдения.
Первые минуты: что известно, а что пока лишь гипотеза
В 14:06 срабатывает оповещение: доля 5xx у GET /orders растёт, p95 увеличился с обычных 0,25 до 2,2 секунды. Сообщение «PostgreSQL перегружен из-за релиза» пока слишком сильное. Мы знаем симптом и видим совпадение по времени, но ещё не установили причинную связь.
В первые минуты полезно записать короткое состояние инцидента:
| Вопрос | Ответ в нашем случае |
|---|---|
| Что нарушено? | Чтение списка заказов медленное и иногда ошибочное |
| Когда началось? | Первые плохие запросы около 14:02 |
| Каков охват? | Все экземпляры orders-api, маршрут GET /orders; создание заказов пока требует отдельной проверки |
| Что изменилось? | В 14:02 выпущена версия v17 |
| Какие действия уже выполнены? | Пока никаких |
| Кто принимает решения об изменениях? | Назначенный ответственный за инцидент |
Даже небольшой команде полезно закрепить одного человека, который держит общую картину, и одного исполнителя, который меняет систему. Иначе несколько инженеров могут одновременно откатить выпуск, перезапустить базу и изменить лимиты; после этого трудно понять, какое действие помогло или навредило. Роли ответственного, технического исполнителя и человека, ведущего хронологию, описаны в практике управления инцидентами Google SRE. В маленькой команде роли можно совмещать, но решение о каждом изменении должно оставаться явным.
Хронология нужна не для отчётности задним числом. Она позволяет не повторить неудачное действие и увидеть, какой показатель изменился после конкретного шага:
14:02 Начался выпуск v17.
14:06 Оповещение по ошибкам GET /orders.
14:07 Подтверждён рост p95 и обращений к PostgreSQL.
14:08 Выпуск остановлен. Новые изменения заморожены.
14:10 Временно ограничены резервные чтения из PostgreSQL.
14:12 Начат возврат проверенного образа v16.
14:17 Пользовательские показатели вернулись к норме.
14:30 Завершена первичная проверка заказов и очереди.
В момент 14:17 восстановилась работа маршрута. Это ещё не означает, что в 14:17 уже известна первопричина или проверены все данные.
Вывод: при инциденте отделяйте подтверждённые факты от гипотез и фиксируйте время изменений. Скорость реакции важна, но несогласованные изменения увеличивают риск.
Диагностика: как связать симптомы в одну причинную цепочку
Предыдущая глава посвящена тому, как использовать логи, метрики и трассировку. Здесь применим готовый порядок к конкретному инциденту, не повторяя устройство каждого инструмента.
Сначала сравниваем показатели до и после 14:02:
| Показатель | До выпуска | После выпуска | Что это подсказывает |
|---|---|---|---|
Поток GET /orders |
90/с | 90/с | Внешняя нагрузка не выросла |
| Попадания в кэш | 70 % | Менее 1 % | Почти каждый запрос обходит Redis |
| Чтения PostgreSQL | Около 27/с | Близко к 90/с | База получает более чем втрое больше работы |
p95 GET /orders |
0,25 с | 2,2 с | Задержка заметна пользователям |
Ошибки GET /orders |
Менее 0,5 % | 18 % | Часть попыток не завершается успешно |
Одного совпадения всё ещё мало. Проверяем несколько медленных трасс: на них Redis отвечает быстро, но фиксируется промах кэша, затем запрос долго ожидает выполнение SQL или соединение из пула. В логах версии v17 почти каждое чтение списка имеет cache_hit=false.
Сравнение кода выявляет изменение ключа кэша. Стабильный ключ должен одинаково обозначать одинаковый запрос и различать запросы с разными фильтрами:
# Стабильное представление логического запроса
key = f"orders:list:v2:{customer_id}:{filter_hash}"
В версии v17 при формировании ключа по ошибке использован идентификатор конкретного HTTP-запроса:
# Ошибочное представление: request_id новый для каждого вызова
key = f"orders:list:v2:{customer_id}:{request_id}"
Даже если два клиента запрашивают один и тот же список с одинаковыми фильтрами, request_id различается. Следующий запрос не найдёт только что сохранённое значение. Кэш остаётся доступным, но почти не разгружает базу. Так объясняется и падение доли попаданий, и рост обращений к PostgreSQL.
При этом не нужно делать вывод, что в PostgreSQL «сломался SQL». База получила поток, на который система не была рассчитана. Её перегрузка стала следствием изменения в API. Дополнительные проверки PostgreSQL помогают исключить независимую проблему с блокировками или диском, но главное доказательство здесь: изменение ключа породило промахи, они увеличили поток к базе, затем выросли задержка и ошибки.
Важна последовательность: выпуск v17 → нестабильный ключ кэша → промахи → рост чтений PostgreSQL → ожидание и таймауты → ошибки пользователей. Каждый переход в ней должен подтверждаться кодом или измерением.
Выводы раздела:
- Близость по времени помогает выбрать гипотезу, но не доказывает её.
- Причина на входе цепочки может проявиться как авария другого компонента.
- Для конкретного исправления нужно проверить весь путь от изменения до пользовательского симптома.
Сдерживание: снизить ущерб до окончательного исправления
После обнаружения ошибки можно сразу откатить выпуск. Но PostgreSQL уже получает слишком много чтений, а некоторые запросы ждут соединения. Возврат старой версии займёт время и не обязательно мгновенно снимет накопленную работу. Поэтому сначала стоит решить, как защитить критичные операции.
В предыдущей главе мы измерили: при обычном потоке Redis обслуживает 70 % из 90 чтений в секунду, оставляя базе около 27. Без работающего кэша все 90 чтений идут к базе, хотя безопасный уровень для этого типа нагрузки оценён примерно в 50 при обычном потоке записей.
Временная мера: ограничить резервные чтения GET /orders, направляемые в PostgreSQL, например до 35 в секунду. Это значение оставляет измеренный запас для записей и другой работы. Излишек чтений получает быстрый временный отказ. Такая мера не выглядит приятной: пока кэш не восстановлен, часть пользователей по-прежнему не увидит список. Но она не даёт необязательным чтениям вытеснить создание заказов и не превращает локальную ошибку в остановку всей базы.
Здесь необходимо честно считать цену меры. Если поступает 90 чтений в секунду и только 35 допускаются к базе, до 55 чтений в секунду могут получить отказ. Это снижение общего ущерба, а не мгновенное восстановление чтения. Поэтому ограничение должно быть временным, наблюдаемым и снятым после проверки ёмкости. Его нельзя оставлять как скрытую «норму».
В зависимости от системы меры сдерживания могут быть другими:
| Мера | Когда подходит | Риск |
|---|---|---|
| Остановить дальнейший выпуск | Сбой совпал с изменением, распространение ещё продолжается | Уже обновлённые экземпляры остаются проблемными |
| Вернуть предыдущий образ | Изменение кода изолировано и прежняя версия совместима с текущими данными | Откат не исправит накопленную очередь или несовместимую миграцию |
| Ограничить дорогие чтения | База близка к перегрузке, нужно сохранить записи | Часть пользователей получит явный отказ |
| Отключить необязательную функцию | Она создаёт значительную нагрузку и её можно временно убрать | Требуется заранее подготовленный переключатель и понятный эффект |
| Перенаправить трафик на другой экземпляр | Неисправен отдельный экземпляр, остальные здоровы | Другие экземпляры должны выдержать дополнительную нагрузку |
| Увеличить число API-экземпляров | Предел достигнут именно в API | При перегруженной базе усилит давление на неё |
Здесь увеличение числа API-экземпляров было бы неправильной первой мерой. Оно не восстановит попадания в кэш. Напротив, новые экземпляры способны создать дополнительные соединения и обращения к PostgreSQL.
Не стоит также без диагностики перезапускать PostgreSQL, очищать Redis целиком или удалять сообщения RabbitMQ. Такие действия меняют состояние системы и могут осложнить восстановление. Сдерживание должно быть по возможности обратимым и иметь измеряемый эффект.
Вывод: сначала защитите наиболее важную пользовательскую операцию и общий ресурс. Управляемый временный отказ части запросов может предотвратить полный отказ системы.
Восстановление: откат, исправление или переключение
В нашем случае обнаружена ошибка кода версии v17, а схема PostgreSQL не менялась. Поэтому самый быстрый проверяемый путь: вернуть известный рабочий образ v16. Выпуск останавливают, фиксируют точный образ предыдущей версии и последовательно заменяют экземпляры API, проверяя готовность каждого до подачи трафика.
Фиксация точного образа важна: тег вроде latest может указывать уже на другое содержимое. Формат образа с неизменяемым digest поддерживается в спецификации Docker Compose. Однако сам способ замены контейнеров зависит от используемого развёртывания. Нельзя объявлять произвольную команду «универсальным откатом» без проверки текущих файлов конфигурации и схемы выпуска.
Перед откатом нужно выяснить, что версия v17 успела изменить за пределами собственного кода:
- применялись ли миграции схемы;
- изменился ли формат сообщений RabbitMQ;
- появилась ли запись данных, которую
v16не умеет читать; - менялись ли ключи и значения в Redis;
- совместима ли старая версия с текущей конфигурацией и секретами.
Если новое приложение изменило схему или формат данных несовместимым образом, старый образ может не запуститься или начать ошибочно обрабатывать новые записи. Тогда нужен исправляющий выпуск, совместимая миграция или отдельный план возврата данных. Выбор определяется конкретным изменением, а не предпочтением «всегда откатывать».
Почему восстановление из резервной копии здесь не подходит
PostgreSQL не потерял заказы. Его перегрузили лишние чтения. Восстановление базы из вчерашней копии устранило бы не причину промахов кэша, а новые данные: заказы, созданные после копии. Оно может причинить больше ущерба, чем исходный инцидент.
Резервная копия нужна, когда данные действительно повреждены или потеряны, а не когда приложение медленно обращается к исправной базе. Даже при настоящей потере данных восстановление требует выбранной точки, оценки допустимой потери и проверки результата. Тренировки отката и восстановления из резервных копий рекомендуются как отдельные испытания в руководстве Google Cloud по восстановлению после отказов.
Возврат нагрузки после отката
Допустим, v16 снова формирует стабильный ключ. Если старые значения в Redis ещё доступны, доля попаданий начнёт быстро расти. Но считать это гарантированным нельзя: часть ключей могла истечь, а после массового отката много клиентов одновременно запросят отсутствующие записи.
Поэтому временное ограничение обращений к базе снимают постепенно. Сначала проверяют, что доля попаданий растёт, число чтений PostgreSQL падает, его время ответа и ожидание соединений нормализуются. Затем увеличивают допуск для GET /orders и наблюдают эффект. Быстрое снятие всех ограничений до прогрева кэша способно вызвать новый всплеск.
После возвращения пользовательского HTTP-ответа отдельно проверяют очередь. Если orders-worker замедлился, пока PostgreSQL был перегружен, задачи могли накопиться. Рост скорости обработки после отката хорош, но до исчезновения отставания пользовательский процесс ещё не полностью нормален.
Выводы раздела:
- Восстанавливайте тот слой, в котором находится причина; не трогайте сохранные данные ради исправления кода.
- Откат безопасен только после проверки совместимости старой версии с текущим состоянием.
- После исправления причины снимайте временные ограничения по измерениям, чтобы не создать второй пик.
Проверка данных: что произошло с запросами во время сбоя
Есть разница между восстановлением доступности и восстановлением корректного результата каждой операции.
Для GET /orders ошибка означает, что пользователь мог не увидеть данные. Для POST /orders неопределённость опаснее. Клиент мог получить таймаут после того, как PostgreSQL уже сохранил заказ. Если клиент отправил запрос повторно без ключа идемпотентности, мог появиться дубль. Если заказ сохранился, но сообщение в RabbitMQ не было надёжно опубликовано, подтверждение может не прийти.
Проверку ведут по интервалу воздействия, например с 14:02 до 14:17. Сначала собирают список операций из внешних HTTP-журналов и сопоставляют его с сохранёнными заказами. Особенно важны:
- успешные ответы
POST /orders, для которых запись должна существовать; - таймауты
POST /orders, результат которых клиент не знает; - повторные запросы с тем же ключом идемпотентности;
- заказы, созданные в период инцидента, но не получившие ожидаемого дальнейшего статуса.
Считать только количество строк недостаточно. Два повторных заказа могут увеличить счётчик, хотя одна исходная операция осталась без правильного результата. Связывать запрос и запись помогают ключ идемпотентности, идентификатор заказа и корреляционный идентификатор в логах.
Если в системе реализован transactional outbox, можно проверить, что у каждого заказа, требующего фоновой обработки, есть соответствующее событие. Ниже пример для предполагаемой схемы orders и outbox_events; реальные названия полей должны соответствовать проекту:
SELECT o.id
FROM orders AS o
LEFT JOIN outbox_events AS e
ON e.order_id = o.id
AND e.event_type = 'order.created'
WHERE o.created_at >= :incident_start
AND o.created_at < :incident_end
AND e.id IS NULL;
incident_start и incident_end ограничивают период, order.created обозначает нужный тип события. Результат запроса требует разбора: некоторые заказы могли законно не требовать такого события, а неправильная запись outbox может существовать, не означая успешную публикацию. Если outbox в проекте не реализован, этот запрос неприменим; нельзя делать вид, что журнал приложения доказывает гарантированную доставку.
У RabbitMQ отдельно проверяют сообщения, готовые к выдаче, и выданные, но ещё не подтверждённые. Одинаковое общее число сообщений может означать разные проблемы: нехватку работающих обработчиков или задачи, которые обработчики получили и долго не завершают. Смысл этих показателей описан в руководстве RabbitMQ по мониторингу. Также проверяют возраст самой старой задачи и итоговые статусы заказов.
Повторная доставка сообщения во время восстановления возможна. Обработчик должен распознавать уже выполненную операцию, а не слепо повторять побочный эффект. Подтверждения публикации и обработки решают разные задачи; RabbitMQ отдельно описывает их гарантии.
Выводы раздела:
- После таймаута записи её результат может быть неизвестен клиенту, но известен базе.
- Восстановление HTTP не доказывает отсутствия пропущенных или повторных фоновых действий.
- Сверка должна опираться на устойчивые идентификаторы операций и состояние данных, а не только на число ошибок в логах.
Как доказать, что инцидент завершён
В 14:17 график p95 вернулся к норме. Стоит ли закрывать инцидент? Пока рано: короткий спокойный отрезок может быть результатом временного падения трафика, а очередь ещё может отставать.
Для нашего сценария заранее определим критерии. Значения ниже учебные; в реальной системе их согласуют с пользовательскими требованиями и обычным профилем нагрузки.
| Область | Критерий восстановления |
|---|---|
| Входной уровень | GET /orders и POST /orders отвечают корректно при обычном и пиковом потоке |
| Ошибки | Доля неуспешных пользовательских операций вернулась к допустимому уровню и держится в выбранном окне наблюдения |
| Задержка | p95 и p99 важных маршрутов близки к рабочему уровню, а не улучшились только на одном запросе |
| Кэш | Доля попаданий восстановилась; новые и старые экземпляры используют ожидаемые ключи |
| PostgreSQL | Поток чтений и ожидание соединений вернулись в безопасную область |
| Фоновые задачи | Возраст ожидающих задач снижается, обработчики завершают работу, число постоянных отказов не растёт |
| Данные | Принятые заказы существуют, неизвестные результаты разобраны, пропущенные события восстановлены или явно учтены |
| Наблюдаемость | Внешние проверки, метрики, логи и трассы снова поступают и согласуются между собой |
Проверяйте и внешнюю операцию, и внутреннее состояние. Внешний тест должен пройти тем же путём, что пользователь: через Caddy к API, а для создания заказа ещё проверить появление записи и последующее состояние. Внутренние графики подтвердят, что быстрый ответ не получен ценой скрытого роста очереди или повторов.
Сохраните период наблюдения после снятия временных ограничений. Например, проверьте показатели не только в первую минуту после отката, но и при возвращении типичного трафика. Если из 90 запросов в секунду осталось лишь 10, низкая задержка сама по себе не подтверждает прежнюю ёмкость.
Для количественной оценки реакции полезны две временные точки:
- время начала влияния: первый подтверждённый пользовательский сбой, около 14:02;
- время восстановления обещанного результата: момент, когда выполнены критерии, допустим около 14:17.
Разница составляет 15 минут. Это наблюдаемое время восстановления в конкретном случае. Оно не равно времени от получения оповещения в 14:06 и не сокращается от того, что причина была окончательно оформлена позже.
Если для сервиса установлен RTO, то есть предельное допустимое время восстановления, эти 15 минут сравнивают с ним. Если была потеря данных, её сравнивают с RPO, предельным допустимым объёмом потери последних данных во времени. Для нашего инцидента нельзя объявить «RPO равен нулю» только потому, что PostgreSQL не падал: сначала нужно завершить сверку подтверждённых заказов и неизвестных результатов.
Вывод: инцидент закрывают по проверенному восстановлению пользовательских операций, зависимостей и данных, а не по исчезновению одного красного графика.
Разбор после инцидента: исправить систему, а не найти виновного
Во время сбоя приоритетом было вернуть сервис. После стабилизации можно спокойно разобрать, почему одна ошибка ключа кэша оказала такое влияние.
В отчёте полезно разделить четыре группы факторов:
| Группа | В нашем сценарии |
|---|---|
| Запускающее изменение | В v17 в ключ кэша попал уникальный request_id |
| Механизм распространения | Каждый запрос обходил кэш и увеличивал поток к PostgreSQL |
| Усиливающие условия | Выпуск получил весь трафик; не было своевременного ограничения резервных чтений |
| Обнаружение и реакция | Оповещение сработало через несколько минут; потребовалось время связать промахи кэша с выпуском |
Фраза «разработчик ошибся в строке кода» почти не помогает предотвратить повтор. Полезнее спросить, почему тесты не проверяли свойство ключа: одинаковый логический запрос должен давать одинаковый ключ, а разные фильтры не должны конфликтовать. Почему выпуск не остановился при резком падении доли попаданий и росте обращений к базе? Был ли готов безопасный откат? Был ли заранее измерен предел PostgreSQL для обхода кэша?
Меры после разбора должны иметь проверяемый результат:
- добавить тест стабильности и различимости ключей кэша;
- выпускать такую перемену сначала на малую долю трафика и проверять кэш, базу и пользовательские ошибки;
- настроить ограничение резервных чтений так, чтобы оно защищало запись заказов;
- регулярно проверять возврат предыдущего образа с реальной конфигурацией;
- добавить автоматическую сверку незавершённых фоновых событий после нарушения работы.
Для каждой меры нужны владелец, срок и способ доказать, что она действует. «Усилить мониторинг» без названия показателя, порога и проверочного сценария остаётся намерением. Практика postmortem в Google SRE предлагает разбирать причины и условия без поиска виновного, а затем доводить корректирующие действия до выполнения.
Отчёт также должен сохранить реальные времена, пользовательский охват и неопределённость. Если число затронутых пользователей неизвестно, нужно указать диапазон или ограничение метода подсчёта. Например, число HTTP-ошибок не равно числу уникальных пользователей: один человек мог повторить запрос несколько раз.
Вывод: качественный разбор отвечает, почему система допустила распространение ошибки и какое проверяемое изменение сократит вероятность или тяжесть следующего инцидента.
Подготовка к следующему инциденту
План восстановления считается рабочим, когда его проверили. Для системы заказов полезны отдельные упражнения: неудачный выпуск и откат, потеря одного экземпляра API, недоступность Redis, замедление PostgreSQL, накопление сообщений RabbitMQ и восстановление данных из резервной копии в изолированной среде.
У каждого упражнения должны быть:
- начальное состояние и ожидаемое нарушение;
- способ безопасно вызвать его;
- сигнал, который обязан сработать;
- допустимый пользовательский результат во время сбоя;
- шаги возврата;
- критерии проверки данных и окончания;
- фактически измеренное время восстановления.
Так обнаруживаются неудобные вещи: у команды нет доступа к нужному образу, образ есть, но несовместим со схемой, резервная копия существует, но восстановление не проверялось, или оповещение не пришло из-за отказа самого мониторинга. Google Cloud рекомендует регулярно испытывать откат, переключение и восстановление данных, а не рассчитывать на них впервые во время аварии.
Для быстрого применения оставьте рядом с системой короткую последовательность:
подтвердить пользовательский симптом → зафиксировать охват и изменения → назначить ответственного → остановить распространение сбоя → защитить общие ресурсы → восстановить нужный компонент → сверить операции и данные → снять временные меры → проверить нормальную нагрузку → разобрать причины.
Этот порядок не требует знать первопричину в первую минуту. Но каждый шаг должен сопровождаться измерением: какое предположение проверяли, что изменили и какой результат получили.
Итог
Инцидент нельзя свести к команде перезапуска или отката. Сначала нужно увидеть нарушенную пользовательскую операцию, затем не дать ошибке распространиться на общие зависимости. После восстановления компонента требуется проверить накопленную работу и возможные неопределённые результаты записей. Лишь затем можно утверждать, что система вернулась к норме.
В нашем примере ошибка ключа кэша выглядела как перегрузка PostgreSQL. Быстрый поиск причинной цепочки подсказал правильное восстановление: остановить выпуск, временно защитить базу, вернуть совместимую версию API, постепенно снять ограничения и проверить заказы с фоновыми задачами. Такой порядок превращает набор действий «на всякий случай» в последовательность проверяемых решений.