8 октября 2026 года наш облачный провайдер потерял дата-центр, в котором работала вся наша инфраструктура. Через трое суток то же самое случилось со второй площадкой, на которую мы к тому моменту успели переехать. За неделю мы дважды поднимали платформу почти с нуля. Как говорится в меме: да, это я, и как я докатился до жизни такой…
Небольшой спойлер итога ситуации, которую я буду разбирать дальше: мы безвозвратно потеряли данные за 20 часов, прошедших с последнего резервного копирования. Сервисы переехали сначала в соседнюю зону того же облака, потом к другому провайдеру, а миграцию в Kubernetes, начатую год назад, мы закончили за двое суток. Базы данных теперь хранятся в двух копиях у разных провайдеров. Ниже разобрал, как всё было, что помогло ускориться при переезде и где мы потеряли время на собственной ошибке.
Немного контекста: я руковожу разработкой в BotHelp — SaaS-сервисе рассылок и ботов в мессенджерах. Когда платформа лежит, у клиентов не уходят рассылки и не обрабатываются оплаты их подписчиков. Поэтому максимально важно быстро реагировать и восстанавливать работоспособность платформы.
Утром 8 октября на телефон пришёл алерт: платформа недоступна. Внутри компании инцидент объявили в 04:30 по Москве. Не отвечала вся наша инфраструктура в Yandex Cloud: ни серверы, ни базы данных.
Первым в плане восстановления стоял приём внешних событий: вебхуки мессенджеров и платёжных сервисов. Пока недоступен интерфейс, клиент теряет только управление воронками, а пока не принимаются события, часть из них не доедет уже никогда. Поэтому вебхуки мы подняли раньше, чем отправку сообщений.
Восстановление после первого инцидента
Мы выбрали самый быстрый из доступных вариантов: поднять копию по кусочкам в другой зоне доступности того же облака. Дальше мы начали наступать на грабли:
Конфиги. Проект немолодой, в нём хватает легаси и не самых удачных архитектурных решений. При переезде у всех машин поменялись IP-адреса, и конфигурацию всех сервисов пришлось прописывать заново. Это очень много ручной рутины, в которой нельзя ошибиться. Часть делали с помощью ИИ, но внимательность всё равно была на людях.
Зоопарк запуска. В проде всё было развёрнуто по-разному: что-то в Docker; Node.js-сервисы — часть через PM2, часть через Forever. Пока всё работает, разнобой не мешает. Когда нужно поднять всё и сразу в новом месте, каждый сервис превращается в отдельную задачу.
Лимиты облака. Создать ресурсы в соседних зонах удавалось не с первого раза: облако отвечало ошибками вида «сейчас вы этого сделать не можете». Площадка встала не у нас одних, и в соседние зоны одновременно переезжали и другие её клиенты. Кроме квот, у этого был второй эффект. К 09:27, примерно через пять часов после начала аварии, часть сервисов уже стояла в новой зоне, но сеть внутри облака работала с большими задержками, и это замедляло само восстановление. Наш сайт мы подняли и почти сразу потеряли снова: он лёг из-за той же сети.
К 11:37 веб-интерфейс для клиентов (личный кабинет) уже работал, но вход мы держали закрытым, чтобы пользователи не добавляли нагрузки на сеть. Боты тогда работали наполовину, рассылки — на 30%. К 19:10 задержки в ботах упали ниже 10 секунд, а в худший момент доходили до пяти минут. К вечеру 8 октября было восстановлено 70% кабинетов, а к 22:57 открылись вход и регистрация и заработали все основные сервисы. На то, чтобы собрать платформу заново в другом месте, ушли сутки.
С данными была отдельная история: ночной бэкап за 8 октября на момент аварии ещё не завершился. Последней целой точкой оказалась копия за 7-е, и между ней и остановкой площадки осталось 20 часов данных, которые мы уже не вернём. Пропали и кабинеты, зарегистрированные после точки бэкапа: с 7 октября люди регистрировались, а в восстановленной базе их просто нет.
В части таблиц у нас автоинкрементные идентификаторы, и потеря данных ударила по ним. Допустим, 7 октября в системе появился подписчик с ID 60, за ним 61 и 62. Мы откатились на бэкап, в котором этих записей нет, — и следующий новый подписчик снова получил ID 60. Но это уже другой человек.
А у первого «шестидесятого» за 7 октября могла быть история: он что-то смотрел, ему что-то продавали, он общался с ботом. Его ID мог быть передан наружу — например, в CRM клиента. После отката CRM продолжает считать, что по этому ID у неё Иван, а на другом конце провода уже Маргарита.
Следующие двое суток ушли на хвосты. Утром 9 октября я написал, что всё должно быть в порядке, и весь день после этого поддержка и QA находили места, где порядка не было: мы пустили сплошной чек-лист по всему функционалу и за сутки завели несколько десятков багов. Если будете восстанавливаться после большой аварии, заложите на это отдельные сутки и отдельных людей: инфраструктура к этому моменту уже стоит, а платформа ещё нет.
Ловили в основном рутину. Не открывались мини-лендинги на клиентских доменах: при переезде поменялся наш IP, а DNS меняется на стороне клиента. Ещё отваливались автоматизации в одной из соцсетей и где-то задерживались сообщения. С рассылками за время простоя мы поступили жёстко и, думаю, правильно. Всё, что было запланировано на часы аварии, мы отменили, а не отправили с опозданием: сообщение «вебинар начинается через час», доставленное на следующий день, хуже, чем не доставленное вовсе.
Одну потерю мы сделали себе сами уже после восстановления. У очереди рассылок стояло хранение четыре часа и 128 мегабайт на партицию. В обычной жизни это не ограничение: сообщение забирают из очереди за секунды, и топик почти всегда пуст. Но в первый вечер обработчики (консюмеры) отставали на сотни тысяч сообщений, и очередь начала удалять старые сегменты раньше, чем их успевали прочитать.
Дальше сработала вторая настройка, о которой никто не думал. Когда консюмер запросил offset, которого на брокере уже не было, политика сброса отправила его в конец топика. Он перешагнул не только удалённое, но и всё, что ещё лежало на брокере: пропущенного оказалось почти в десять раз больше, чем стёртого. Для клиента это выглядело так: рассылка в личном кабинете помечена отправленной, а подписчики её не получили.
Обе настройки в обычной работе ничего не ломали. Лимиты хранения, рассчитанные на спокойный режим, при аварийном отставании консюмеров превращаются в потерю данных, поэтому перед открытием платформы их нужно проверить. Политику сброса offset у консюмера стоит ставить в earliest, если дубль сообщения обходится дешевле его потери.
Восстановление после второго инцидента
11 октября в 03:09 я проверял платформу, всё работало. В 03:45 наш мониторинг зафиксировал недоступность сервисов. Сначала мы решили, что это ненадолго. В 05:14 стало ясно, что «чинить» нечего. Провайдер сообщил, что дата-центр остановлен полностью, назвал оставшуюся конфигурацию ресурсов неустойчивой и рекомендовал клиентам задействовать любые альтернативные планы аварийного восстановления. Тем же утром мы начали поднимать платформу у другого облачного провайдера с оценкой в сутки.
Главный вывод, который мы сделали всей командой: резервирование по зонам доступности внутри одного облака в таком сценарии не работает. Даже если бы у нас всё было устроено идеально, по учебнику, с репликами, автоматическим переключением и отработанным DR-планом, но целиком в одном облаке, мы бы всё равно легли в воскресенье утром. Зоны доступности защищают от отказа оборудования, ошибки в одном ЦОД или обрыва питания на одной площадке, но не от того, что площадки выключаются одна за другой.
Переезд к другому провайдеру не был гладким, хотя тестовые серверы у нас там уже были. На новой площадке мы поймали проблемы сетевой связности. Серверы периодически не могли достучаться друг до друга, причём мигающе и непонятно. Мы собрали дашборд, на котором видно, какой сервер до какого не дотягивается, и разбирались вместе с провайдером. Первопричина была на стороне площадки, поэтому исправлял её провайдер, а мы по дашборду показывали, где пропадает связь.
Те самые тестовые серверы, из-за которых мы и оказались у этого провайдера, стали спасательной шлюпкой. Мы не повторяли продакшен на новом месте, а взяли тестовый контур и подставили в него конфигурацию продакшена. Ещё из плюсов: у нас уже был отработанный план восстановления.
Из минусов: поднять сервисы оказалось быстрее, чем перевезти данные. Когда клиент фильтрует подписчиков, данные берутся не из основной базы (для фильтров она медленная), а из отдельного Elasticsearch. При переезде мы его не перевозили, а загружали заново: процесс шёл около двух суток, а клиентов с базами на миллионы подписчиков догружали ещё часами.
Что изменилось после аварий
У аварии нашёлся и полезный побочный эффект: мониторинг стал единым. Раньше каждый сервис и каждый сервер подключали к наблюдению отдельно. Теперь сервисы на Node.js, Python и PHP живут по одинаковым правилам и наблюдаются одинаково. Сервисы платформы поднимаются в новом кластере за 2–3 часа, данные переезжают отдельно и дольше. Сами данные мы теперь развезли, чтобы не зависеть от потери одной реплики.
Ещё один эффект оказался менее ожидаемым: ИИ переехал из разработки в операционку. Вечером 11-го он за час собрал по коду и конфигам полный список наших сервисов с режимами запуска — такого списка у нас не было. Потом находил в конфигах адреса старого облака, писал скрипты и runbook’и.
Но в хосты и кластер он не ходит. Все команды мы запускали сами, по одной, и правильно делали: в первый день он уверенно заявил, что на сервере нет Kafka, потому что там нет Docker, а на второй предложил pkill, который снёс контейнеры чатов. Так что роль у него такая: быстро прочитать много и предложить, а решать и нажимать остаётся человеку.
По той же схеме мы теперь разбираем инциденты. Когда QA или поддержка сообщают о задержках, первым делом мы спрашиваем ассистента. Человеку пришлось бы открывать сервисы по очереди, а ассистент сразу просматривает все, и на первом шаге разбора это экономит время.
Итог: что сработало и чего не хватило
Сработали ежедневные бэкапы, копии данных в сторонних облаках, часть платформы, которая уже жила в Kubernetes, и тестовые серверы. Не хватило бэкапов Redis: там лежали счётчики ID и часть очередей отправки, пересчитать их из базы нельзя, поэтому Redis подняли пустым, и отсюда часть коллизий ID. Теперь мы работаем над тем, чтобы платформа не зависела ни от одной площадки и ни от одного провайдера.
ссылка на оригинал статьи https://habr.com/ru/articles/1100554/